Shield Security for WordPress - Version 11.0.0

Version Description

Download this release

Release Info

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

Code changes from version 10.2.6 to 11.0.0

Files changed (361) hide show
  1. cl.json +92 -0
  2. icwp-wpsf.php +1 -1
  3. init.php +1 -11
  4. plugin-spec.php +95 -35
  5. readme.txt +17 -11
  6. resources/css/bootstrap-select.min.css +0 -6
  7. resources/css/chartist.min.css +0 -1
  8. resources/css/introjs.min.css +0 -1
  9. resources/css/plugin.css +12 -21
  10. resources/css/shield/charts.css +27 -0
  11. resources/images/bootstrap/graph-up.svg +3 -0
  12. resources/js/base64.min.js +0 -1
  13. resources/js/bootstrap-select.min.js +0 -9
  14. resources/js/chartist.min.js +0 -10
  15. resources/js/charts.js +0 -131
  16. resources/js/introjs.min.js +0 -1
  17. resources/js/plugin.js +8 -11
  18. resources/js/shield-comments.js +0 -137
  19. resources/js/shield/antibot.js +62 -0
  20. resources/js/shield/charts.js +138 -0
  21. resources/js/shield/comments.js +137 -0
  22. resources/js/shield/ipanalyse.js +9 -6
  23. resources/js/{shield-antibot.js → shield/loginbot.js} +31 -23
  24. resources/js/{shield-card-shuffle.js → shield/shuffle.js} +0 -0
  25. resources/js/shield/tours.js +43 -0
  26. resources/js/shuffle.js +0 -2190
  27. resources/js/shuffle.min.js +0 -2
  28. resources/js/whitelabel.js +0 -8
  29. resources/js/wizard.js +0 -0
  30. src/config/feature-audit_trail.php +17 -5
  31. src/config/feature-comments_filter.php +44 -29
  32. src/config/feature-events.php +7 -0
  33. src/config/feature-firewall.php +2 -0
  34. src/config/feature-hack_protect.php +53 -25
  35. src/config/feature-integrations.php +82 -0
  36. src/config/feature-ips.php +143 -10
  37. src/config/feature-license.php +24 -8
  38. src/config/feature-login_protect.php +15 -1
  39. src/config/feature-plugin.php +38 -20
  40. src/config/feature-reporting.php +28 -12
  41. src/config/feature-sessions.php +23 -1
  42. src/config/feature-traffic.php +19 -7
  43. src/lib/functions/functions.php +39 -0
  44. src/lib/src/Controller/Admin/MainAdminMenu.php +7 -7
  45. src/lib/src/Controller/Ajax/Init.php +13 -4
  46. src/lib/src/Controller/Assets/Enqueue.php +22 -6
  47. src/lib/src/Controller/Assets/Paths.php +38 -0
  48. src/lib/src/Controller/Assets/Urls.php +33 -21
  49. src/lib/src/Controller/Config/ConfigVO.php +13 -8
  50. src/lib/src/Controller/Controller.php +129 -159
  51. src/lib/src/Controller/Utilities/CaptureMyUpgrade.php +37 -0
  52. src/lib/src/Controller/Utilities/DebugMode.php +3 -3
  53. src/lib/src/Crons/DailyCron.php +7 -7
  54. src/lib/src/Databases/AdminNotes/Handler.php +1 -10
  55. src/lib/src/Databases/AdminNotes/Insert.php +7 -11
  56. src/lib/src/Databases/AuditTrail/EntryVO.php +2 -3
  57. src/lib/src/Databases/AuditTrail/Insert.php +8 -8
  58. src/lib/src/Databases/AuditTrail/Select.php +14 -12
  59. src/lib/src/Databases/AuditTrail/Update.php +7 -11
  60. src/lib/src/Databases/Base/BaseQuery.php +19 -22
  61. src/lib/src/Databases/Base/Delete.php +6 -6
  62. src/lib/src/Databases/Base/EntryVO.php +34 -46
  63. src/lib/src/Databases/Base/Handler.php +126 -60
  64. src/lib/src/Databases/Base/Insert.php +11 -24
  65. src/lib/src/Databases/Base/Iterator.php +111 -0
  66. src/lib/src/Databases/Base/Traits/Select_IPTable.php +24 -0
  67. src/lib/src/Databases/Base/Update.php +34 -26
  68. src/lib/src/Databases/BotSignals/Common.php +34 -0
  69. src/lib/src/Databases/BotSignals/Delete.php +10 -0
  70. src/lib/src/Databases/BotSignals/EntryVO.php +67 -0
  71. src/lib/src/Databases/BotSignals/Handler.php +10 -0
  72. src/lib/src/Databases/BotSignals/Insert.php +9 -0
  73. src/lib/src/Databases/BotSignals/Select.php +21 -0
  74. src/lib/src/Databases/BotSignals/Update.php +7 -0
  75. src/lib/src/Databases/ChangeTracking/EntryVO.php +9 -12
  76. src/lib/src/Databases/Common/TableSchema.php +74 -11
  77. src/lib/src/Databases/Events/Select.php +3 -3
  78. src/lib/src/Databases/Events/Update.php +2 -4
  79. src/lib/src/Databases/FileLocker/EntryVO.php +11 -20
  80. src/lib/src/Databases/FileLocker/Insert.php +2 -4
  81. src/lib/src/Databases/FileLocker/Update.php +15 -16
  82. src/lib/src/Databases/GeoIp/EntryVO.php +11 -14
  83. src/lib/src/Databases/GeoIp/Handler.php +2 -12
  84. src/lib/src/Databases/GeoIp/Select.php +1 -12
  85. src/lib/src/Databases/IPs/EntryVO.php +1 -1
  86. src/lib/src/Databases/IPs/Handler.php +15 -3
  87. src/lib/src/Databases/IPs/Insert.php +6 -6
  88. src/lib/src/Databases/IPs/Update.php +17 -20
  89. src/lib/src/Databases/Reports/Handler.php +2 -14
  90. src/lib/src/Databases/ScanQueue/EntryVO.php +18 -21
  91. src/lib/src/Databases/ScanQueue/Handler.php +1 -14
  92. src/lib/src/Databases/ScanQueue/Update.php +10 -10
  93. src/lib/src/Databases/Scanner/Handler.php +1 -14
  94. src/lib/src/Databases/Scanner/Update.php +12 -12
  95. src/lib/src/Databases/Session/Insert.php +15 -18
  96. src/lib/src/Databases/Session/Select.php +1 -6
  97. src/lib/src/Databases/Session/Update.php +3 -3
  98. src/lib/src/Databases/Tally/Delete.php +0 -9
  99. src/lib/src/Databases/Tally/EntryVO.php +0 -16
  100. src/lib/src/Databases/Tally/Handler.php +0 -32
  101. src/lib/src/Databases/Tally/Insert.php +0 -45
  102. src/lib/src/Databases/Tally/Select.php +0 -39
  103. src/lib/src/Databases/Tally/Update.php +0 -17
  104. src/lib/src/Databases/Traffic/EntryVO.php +12 -13
  105. src/lib/src/Databases/Traffic/Insert.php +3 -3
  106. src/lib/src/Databases/Traffic/Select.php +1 -12
  107. src/lib/src/Modules/AuditTrail/AjaxHandler.php +13 -27
  108. src/lib/src/Modules/AuditTrail/Lib/AuditMessageBuilder.php +25 -0
  109. src/lib/src/Modules/AuditTrail/Lib/Ops/Commit.php +20 -23
  110. src/lib/src/Modules/AuditTrail/Lib/Utility/AutoWhitelistParamFromAuditEntry.php +55 -0
  111. src/lib/src/Modules/AuditTrail/ModCon.php +17 -5
  112. src/lib/src/Modules/Base/AjaxHandler.php +7 -3
  113. src/lib/src/Modules/Base/BaseProcessor.php +0 -183
  114. src/lib/src/Modules/Base/ModCon.php +104 -98
  115. src/lib/src/Modules/Base/Options.php +5 -6
  116. src/lib/src/Modules/Base/Processor.php +2 -2
  117. src/lib/src/Modules/Base/UI.php +14 -15
  118. src/lib/src/Modules/Base/Upgrade.php +2 -1
  119. src/lib/src/Modules/Base/WpCli.php +4 -3
  120. src/lib/src/Modules/Base/WpCli/BaseWpCliCmd.php +31 -56
  121. src/lib/src/Modules/Base/WpCli/ModuleStandard.php +17 -17
  122. src/lib/src/Modules/BaseShield/ModCon.php +40 -44
  123. src/lib/src/Modules/BaseShield/ShieldProcessor.php +0 -14
  124. src/lib/src/Modules/BaseShield/UI.php +6 -5
  125. src/lib/src/Modules/CommentsFilter/AjaxHandler.php +9 -14
  126. src/lib/src/Modules/CommentsFilter/Forms/Gasp.php +56 -58
  127. src/lib/src/Modules/CommentsFilter/Forms/GoogleRecaptcha.php +3 -3
  128. src/lib/src/Modules/CommentsFilter/Insights/OverviewCards.php +3 -2
  129. src/lib/src/Modules/CommentsFilter/ModCon.php +5 -0
  130. src/lib/src/Modules/CommentsFilter/Options.php +7 -2
  131. src/lib/src/Modules/CommentsFilter/Processor.php +8 -4
  132. src/lib/src/Modules/CommentsFilter/Scan/AntiBot.php +25 -0
  133. src/lib/src/Modules/CommentsFilter/Scan/Bot.php +26 -26
  134. src/lib/src/Modules/CommentsFilter/Scan/CommentAdditiveCleaner.php +26 -0
  135. src/lib/src/Modules/CommentsFilter/Scan/Human.php +21 -61
  136. src/lib/src/Modules/CommentsFilter/Scan/Scanner.php +63 -46
  137. src/lib/src/Modules/CommentsFilter/Strings.php +74 -50
  138. src/lib/src/Modules/CommentsFilter/Upgrade.php +6 -6
  139. src/lib/src/Modules/Events/AjaxHandler.php +0 -66
  140. src/lib/src/Modules/Events/Charts/BuildData.php +0 -131
  141. src/lib/src/Modules/Events/Charts/ChartRequestVO.php +0 -20
  142. src/lib/src/Modules/Events/Lib/Reports/KeyStats.php +1 -0
  143. src/lib/src/Modules/Events/Lib/StatsWriter.php +3 -3
  144. src/lib/src/Modules/Events/Processor.php +17 -19
  145. src/lib/src/Modules/Events/Strings.php +14 -11
  146. src/lib/src/Modules/GeoIp/Lookup.php +4 -5
  147. src/lib/src/Modules/HackGuard/AjaxHandler.php +20 -20
  148. src/lib/src/Modules/HackGuard/Debug.php +0 -7
  149. src/lib/src/Modules/HackGuard/Lib/FileLocker/File.php +10 -13
  150. src/lib/src/Modules/HackGuard/Lib/FileLocker/FileLockerController.php +107 -84
  151. src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/Accept.php +10 -10
  152. src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/AssessLocks.php +18 -18
  153. src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/BaseOps.php +4 -4
  154. src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/CreateFileLocks.php +20 -18
  155. src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/Diff.php +17 -13
  156. src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/LoadFileLocks.php +10 -10
  157. src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/PerformAction.php +13 -13
  158. src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/ReadOriginalFileContent.php +16 -16
  159. src/lib/src/Modules/HackGuard/Lib/Utility/FileDownloadHandler.php +11 -11
  160. src/lib/src/Modules/HackGuard/ModCon.php +12 -14
  161. src/lib/src/Modules/HackGuard/Scan/Controller/Base.php +1 -5
  162. src/lib/src/Modules/HackGuard/Scan/Queue/CompleteQueue.php +13 -13
  163. src/lib/src/Modules/HackGuard/Scan/Queue/ConvertBetweenTypes.php +1 -1
  164. src/lib/src/Modules/HackGuard/Scan/Queue/QueueProcessor.php +19 -20
  165. src/lib/src/Modules/HackGuard/Scan/Results/ResultsUpdate.php +1 -1
  166. src/lib/src/Modules/HackGuard/Scan/ScansController.php +35 -21
  167. src/lib/src/Modules/HackGuard/Scan/Utilities/WpvAddPluginRows.php +3 -3
  168. src/lib/src/Modules/HackGuard/Strings.php +35 -33
  169. src/lib/src/Modules/HackGuard/UI.php +3 -3
  170. src/lib/src/Modules/Headers/ModCon.php +8 -65
  171. src/lib/src/Modules/Headers/Strings.php +0 -40
  172. src/lib/src/Modules/IPs/AjaxHandler.php +77 -35
  173. src/lib/src/Modules/IPs/BotTrack/Base.php +10 -8
  174. src/lib/src/Modules/IPs/BotTrack/Track404.php +1 -1
  175. src/lib/src/Modules/IPs/BotTrack/TrackCommentSpam.php +40 -0
  176. src/lib/src/Modules/IPs/BotTrack/TrackFakeWebCrawler.php +23 -18
  177. src/lib/src/Modules/IPs/BotTrack/TrackInvalidScriptLoad.php +58 -0
  178. src/lib/src/Modules/IPs/BotTrack/TrackLinkCheese.php +45 -44
  179. src/lib/src/Modules/IPs/Components/ImportIpsFromFile.php +1 -1
  180. src/lib/src/Modules/IPs/Components/QueryIpBlock.php +1 -1
  181. src/lib/src/Modules/IPs/Components/UnblockIpByFlag.php +2 -2
  182. src/lib/src/Modules/IPs/Lib/AutoUnblock.php +28 -8
  183. src/lib/src/Modules/IPs/Lib/BlacklistHandler.php +41 -31
  184. src/lib/src/Modules/IPs/Lib/BlockRequest.php +8 -6
  185. src/lib/src/Modules/IPs/Lib/Bots/BotSignalsController.php +69 -0
  186. src/lib/src/Modules/IPs/Lib/Bots/BotSignalsRecord.php +105 -0
  187. src/lib/src/Modules/IPs/Lib/Bots/Calculator/BuildScores.php +297 -0
  188. src/lib/src/Modules/IPs/Lib/Bots/Calculator/CalculateVisitorBotScores.php +60 -0
  189. src/lib/src/Modules/IPs/Lib/Bots/EventListener.php +80 -0
  190. src/lib/src/Modules/IPs/Lib/Bots/NotBot/InsertNotBotJs.php +46 -0
  191. src/lib/src/Modules/IPs/Lib/Bots/NotBot/NotBotHandler.php +96 -0
  192. src/lib/src/Modules/IPs/Lib/IpAnalyse/BuildDisplay.php +137 -44
  193. src/lib/src/Modules/IPs/Lib/IpAnalyse/FindAllPluginIps.php +7 -0
  194. src/lib/src/Modules/IPs/Lib/Ops/AddIp.php +53 -53
  195. src/lib/src/Modules/IPs/Lib/Ops/DeleteIp.php +8 -9
  196. src/lib/src/Modules/IPs/Lib/Ops/LookupIpOnList.php +21 -3
  197. src/lib/src/Modules/IPs/Lib/ProcessOffenses.php +7 -1
  198. src/lib/src/Modules/IPs/ModCon.php +30 -1
  199. src/lib/src/Modules/IPs/Options.php +13 -37
  200. src/lib/src/Modules/IPs/Processor.php +1 -0
  201. src/lib/src/Modules/IPs/Strings.php +120 -56
  202. src/lib/src/Modules/IPs/UI.php +5 -0
  203. src/lib/src/Modules/IPs/Upgrade.php +1 -1
  204. src/lib/src/Modules/IPs/WpCli/BaseAddRemove.php +3 -1
  205. src/lib/src/Modules/IPs/WpCli/Remove.php +1 -1
  206. src/lib/src/Modules/Insights/ModCon.php +15 -23
  207. src/lib/src/Modules/Insights/Strings.php +4 -3
  208. src/lib/src/Modules/Insights/UI.php +17 -13
  209. src/lib/src/Modules/Integrations/Lib/MainWP/Common/MWPExtensionVO.php +8 -12
  210. src/lib/src/Modules/Integrations/Lib/MainWP/Common/MWPSiteVO.php +9 -13
  211. src/lib/src/Modules/Integrations/Lib/MainWP/Common/MainWPVO.php +9 -13
  212. src/lib/src/Modules/Integrations/Lib/MainWP/Common/SyncMetaVO.php +2 -2
  213. src/lib/src/Modules/Integrations/Lib/MainWP/Common/SyncVO.php +6 -10
  214. src/lib/src/Modules/Integrations/Lib/MainWP/Controller.php +2 -2
  215. src/lib/src/Modules/Integrations/Lib/MainWP/Server/Data/ClientPluginStatus.php +1 -1
  216. src/lib/src/Modules/Integrations/Lib/MainWP/Server/Data/LoadShieldSyncData.php +1 -1
  217. src/lib/src/Modules/Integrations/Lib/MainWP/Server/Data/SyncHandler.php +2 -2
  218. src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/ExtensionSettingsPage.php +3 -3
  219. src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/ManageSites/SitesListTableHandler.php +2 -2
  220. src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/SitesList.php +1 -1
  221. src/lib/src/Modules/Integrations/Lib/Spam/Handlers/Base.php +65 -0
  222. src/lib/src/Modules/Integrations/Lib/Spam/Handlers/ContactForm7.php +20 -0
  223. src/lib/src/Modules/Integrations/Lib/Spam/Handlers/ElementorPro.php +26 -0
  224. src/lib/src/Modules/Integrations/Lib/Spam/Handlers/FluentForms.php +28 -0
  225. src/lib/src/Modules/Integrations/Lib/Spam/Handlers/FormidableForms.php +29 -0
  226. src/lib/src/Modules/Integrations/Lib/Spam/Handlers/Forminator.php +20 -0
  227. src/lib/src/Modules/Integrations/Lib/Spam/Handlers/GravityForms.php +21 -0
  228. src/lib/src/Modules/Integrations/Lib/Spam/Handlers/Helpers/NinjaForms_ShieldSpamAction.php +47 -0
  229. src/lib/src/Modules/Integrations/Lib/Spam/Handlers/KaliForms.php +27 -0
  230. src/lib/src/Modules/Integrations/Lib/Spam/Handlers/NinjaForms.php +48 -0
  231. src/lib/src/Modules/Integrations/Lib/Spam/Handlers/WPForms.php +37 -0
  232. src/lib/src/Modules/Integrations/Lib/Spam/Handlers/WpForo.php +40 -0
  233. src/lib/src/Modules/Integrations/Lib/Spam/SpamController.php +46 -0
  234. src/lib/src/Modules/Integrations/Processor.php +7 -4
  235. src/lib/src/Modules/Integrations/Strings.php +14 -0
  236. src/lib/src/Modules/License/Lib/LicenseHandler.php +24 -61
  237. src/lib/src/Modules/License/Lib/PluginNameSuffix.php +3 -3
  238. src/lib/src/Modules/License/Lib/Verify.php +4 -4
  239. src/lib/src/Modules/License/Lib/WpHashes/ApiTokenManager.php +4 -4
  240. src/lib/src/Modules/License/WpCli/License.php +2 -3
  241. src/lib/src/Modules/Lockdown/Lib/CleanRubbish.php +12 -16
  242. src/lib/src/Modules/Lockdown/Processor.php +6 -6
  243. src/lib/src/Modules/LoginGuard/Insights/OverviewCards.php +10 -10
  244. src/lib/src/Modules/LoginGuard/Lib/AntiBot/AntibotSetup.php +25 -22
  245. src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/BaseFormProvider.php +5 -10
  246. src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/BuddyPress.php +1 -1
  247. src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/EasyDigitalDownloads.php +2 -2
  248. src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/LearnPress.php +3 -3
  249. src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/MemberPress.php +4 -4
  250. src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/PaidMemberSubscriptions.php +1 -1
  251. src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/ProfileBuilder.php +1 -1
  252. src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/UltimateMember.php +3 -3
  253. src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/UserRegistration.php +3 -3
  254. src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WooCommerce.php +4 -4
  255. src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WordPress.php +13 -15
  256. src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/AntiBot.php +29 -0
  257. src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/BaseProtectionProvider.php +29 -10
  258. src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/GaspJs.php +67 -57
  259. src/lib/src/Modules/LoginGuard/Lib/CooldownFlagFile.php +8 -12
  260. src/lib/src/Modules/LoginGuard/Lib/Rename/RenameLogin.php +7 -7
  261. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/LoginIntentPage.php +4 -4
  262. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Profiles/CustomForms.php +2 -2
  263. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/Yubikey.php +0 -1
  264. src/lib/src/Modules/LoginGuard/ModCon.php +13 -7
  265. src/lib/src/Modules/LoginGuard/Options.php +12 -16
  266. src/lib/src/Modules/LoginGuard/Processor.php +11 -1
  267. src/lib/src/Modules/LoginGuard/Strings.php +12 -0
  268. src/lib/src/Modules/Plugin/AjaxHandler.php +20 -20
  269. src/lib/src/Modules/Plugin/Components/PluginBadge.php +1 -1
  270. src/lib/src/Modules/Plugin/Debug.php +0 -8
  271. src/lib/src/Modules/Plugin/Insights/DashboardCards.php +59 -28
  272. src/lib/src/Modules/Plugin/Insights/OverviewCards.php +3 -3
  273. src/lib/src/Modules/Plugin/Lib/Captcha/CaptchaConfigVO.php +9 -12
  274. src/lib/src/Modules/Plugin/Lib/Debug/Collate.php +21 -11
  275. src/lib/src/Modules/Plugin/Lib/ImportExport/Export.php +1 -2
  276. src/lib/src/Modules/Plugin/Lib/ImportExport/ImportExportController.php +1 -11
  277. src/lib/src/Modules/Plugin/Lib/PluginTelemetry.php +6 -4
  278. src/lib/src/Modules/Plugin/Lib/TestCacheDirWrite.php +34 -38
  279. src/lib/src/Modules/Plugin/Lib/TourManager.php +29 -27
  280. src/lib/src/Modules/Plugin/ModCon.php +39 -28
  281. src/lib/src/Modules/Plugin/Processor.php +5 -5
  282. src/lib/src/Modules/Plugin/Strings.php +12 -6
  283. src/lib/src/Modules/Plugin/WpCli/Reset.php +2 -2
  284. src/lib/src/Modules/Plugin/WpCli/ToggleDebug.php +2 -2
  285. src/lib/src/Modules/Reporting/AjaxHandler.php +62 -0
  286. src/lib/src/Modules/Reporting/Charts/BaseBuildChartData.php +162 -0
  287. src/lib/src/Modules/Reporting/Charts/ChartRequestConsumer.php +27 -0
  288. src/lib/src/Modules/Reporting/Charts/ChartRequestVO.php +35 -0
  289. src/lib/src/Modules/Reporting/Charts/CustomChartData.php +43 -0
  290. src/lib/src/Modules/Reporting/Charts/CustomChartRequestVO.php +12 -0
  291. src/lib/src/Modules/Reporting/Lib/ReportingController.php +3 -6
  292. src/lib/src/Modules/Reporting/Lib/Reports/ReportVO.php +2 -2
  293. src/lib/src/Modules/Reporting/UI.php +76 -47
  294. src/lib/src/Modules/SecurityAdmin/AjaxHandler.php +22 -35
  295. src/lib/src/Modules/SecurityAdmin/Lib/WhiteLabel/ApplyLabels.php +3 -3
  296. src/lib/src/Modules/SecurityAdmin/ModCon.php +18 -8
  297. src/lib/src/Modules/SecurityAdmin/Strings.php +103 -95
  298. src/lib/src/Modules/Sessions/ModCon.php +2 -1
  299. src/lib/src/Modules/Sessions/Processor.php +1 -0
  300. src/lib/src/Modules/Traffic/AjaxHandler.php +3 -3
  301. src/lib/src/Modules/Traffic/Lib/Logger.php +5 -18
  302. src/lib/src/Modules/Traffic/ModCon.php +11 -0
  303. src/lib/src/Modules/Traffic/Processor.php +0 -5
  304. src/lib/src/Modules/Traffic/Strings.php +60 -54
  305. src/lib/src/Modules/Traffic/UI.php +10 -14
  306. src/lib/src/Modules/UserManagement/Lib/Password/UserPasswordHandler.php +3 -3
  307. src/lib/src/Modules/UserManagement/Lib/Registration/EmailValidate.php +18 -18
  308. src/lib/src/Modules/UserManagement/Lib/Session/UserSessionHandler.php +3 -3
  309. src/lib/src/Modules/UserManagement/Lib/Suspend/UserSuspendController.php +3 -3
  310. src/lib/src/Scans/Apc/ResultItem.php +5 -7
  311. src/lib/src/Scans/Apc/Scan.php +1 -1
  312. src/lib/src/Scans/Apc/ScanActionVO.php +2 -4
  313. src/lib/src/Scans/Base/BaseMergeItems.php +1 -1
  314. src/lib/src/Scans/Base/BaseResultItem.php +7 -13
  315. src/lib/src/Scans/Base/BaseScanActionVO.php +12 -15
  316. src/lib/src/Scans/Base/Files/BaseFileMapScan.php +1 -1
  317. src/lib/src/Scans/Base/Table/BaseEntryFormatter.php +1 -1
  318. src/lib/src/Scans/Base/Utilities/ItemActionHandler.php +3 -3
  319. src/lib/src/Scans/Mal/ResultItem.php +5 -13
  320. src/lib/src/Scans/Mal/ScanActionVO.php +1 -3
  321. src/lib/src/Scans/Ptg/ResultItem.php +3 -8
  322. src/lib/src/Scans/Ptg/ScanActionVO.php +2 -4
  323. src/lib/src/Scans/Ufc/ResultItem.php +5 -10
  324. src/lib/src/Scans/Ufc/ScanActionVO.php +2 -4
  325. src/lib/src/Scans/Wcf/ResultItem.php +9 -17
  326. src/lib/src/Scans/Wcf/ScanActionVO.php +2 -4
  327. src/lib/src/Scans/Wpv/ResultItem.php +8 -15
  328. src/lib/src/Scans/Wpv/Scan.php +3 -3
  329. src/lib/src/Scans/Wpv/ScanActionVO.php +2 -4
  330. src/lib/src/Scans/Wpv/WpVulnDb/WpVulnVO.php +2 -2
  331. src/lib/src/ShieldNetApi/Common/BaseApi.php +15 -18
  332. src/lib/src/ShieldNetApi/Common/BaseShieldNetApi.php +10 -10
  333. src/lib/src/ShieldNetApi/FileLocker/DecryptFile.php +5 -6
  334. src/lib/src/ShieldNetApi/FileLocker/GetPublicKey.php +6 -6
  335. src/lib/src/ShieldNetApi/Handshake/Verify.php +4 -7
  336. src/lib/src/ShieldNetApi/HandshakingNonce.php +24 -31
  337. src/lib/src/ShieldNetApi/ShieldNetApiController.php +26 -29
  338. src/lib/src/ShieldNetApi/ShieldNetApiDataVO.php +10 -14
  339. src/lib/src/Tables/Build/AdminNotes.php +7 -7
  340. src/lib/src/Tables/Build/AuditTrail.php +64 -72
  341. src/lib/src/Tables/Build/Ip.php +21 -21
  342. src/lib/src/Tables/Build/ScanApc.php +7 -7
  343. src/lib/src/Tables/Build/ScanPtg.php +4 -4
  344. src/lib/src/Tables/Build/ScanWpv.php +7 -7
  345. src/lib/src/Tables/Build/Sessions.php +8 -8
  346. src/lib/src/Tables/Build/Traffic.php +9 -8
  347. src/lib/src/Tables/Render/WpListTable/AuditTrail.php +6 -6
  348. src/lib/src/Utilities/AdminNotices/Controller.php +9 -9
  349. src/lib/src/Utilities/AdminNotices/NoticeVO.php +3 -3
  350. src/lib/src/Utilities/HCaptcha/TestRequest.php +8 -8
  351. src/lib/src/Utilities/HumanSpam/TestContent.php +75 -0
  352. src/lib/src/Utilities/Options/CleanStorage.php +4 -4
  353. src/lib/src/Utilities/ReCaptcha/Enqueue.php +4 -4
  354. src/lib/src/Utilities/ReCaptcha/TestRequest.php +5 -6
  355. src/lib/src/Utilities/Resources/Dynamic.php +56 -0
  356. src/lib/src/Utilities/Time/WorldTimeApi.php +2 -2
  357. src/lib/src/Utilities/Tool/DbTableExport.php +50 -0
  358. src/lib/src/Utilities/Tool/IpListSort.php +9 -9
  359. src/lib/vendor/composer/autoload_classmap.php +48 -11
  360. src/lib/vendor/composer/autoload_files.php +1 -0
  361. src/lib/vendor/composer/autoload_static.php +49 -11
cl.json CHANGED
@@ -1,4 +1,96 @@
1
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  "10.2": {
3
  "version": "10.2",
4
  "released_at": 1613037000,
1
  {
2
+ "11.0": {
3
+ "version": "11.0",
4
+ "released_at": 1616666000,
5
+ "hrefs": {
6
+ "release": "https://shsec.io/shieldrelease1100",
7
+ "upgrade": "https://shsec.io/shieldupgradeguide1100"
8
+ },
9
+ "href": "https://shsec.io/",
10
+ "title": "All-New Shield AntiBot Detection Engine",
11
+ "description": [
12
+ "WordPress security nearly always starts with bots - detecting bad bots and blocking them.",
13
+ "This release delivers your new and exclusive AntiBot Detection Engine allowing Shield to more quickly identify bad bots and block their requests."
14
+ ],
15
+ "items": [
16
+ {
17
+ "type": "new",
18
+ "pro_only": false,
19
+ "title": "AntiBot Detection Engine",
20
+ "description": [
21
+ "Detecting bad bots on you WordPress site is a huge challenge, but it's notoriously difficult to do this.",
22
+ "We have developed an exclusive system for the detection of bad bots and the option to block requests from them."
23
+ ],
24
+ "href": "https://shsec.io/jb"
25
+ },
26
+ {
27
+ "type": "new",
28
+ "title": "Contact Form SPAM Protection",
29
+ "description": [
30
+ "With the arrival of our AntiBot Detection Engine, we can now more easily integrate with 3rd party plugins.",
31
+ "You can add Shield's SPAM protection to Elementor PRO Gravity Forms, Contact Form 7, Ninja Forms, and many more."
32
+ ]
33
+ },
34
+ {
35
+ "type": "new",
36
+ "title": "Charts and Stats.",
37
+ "description": [
38
+ "We've added a page in Shield to allow you to chart some of your favourite Shield Stats."
39
+ ]
40
+ },
41
+ {
42
+ "type": "new",
43
+ "title": "Download Audit Trail, Traffic Log and IP DB as CSV.",
44
+ "description": [
45
+ "A long-requested feature is the ability to download the raw database data - you can now do this with a single click."
46
+ ]
47
+ },
48
+ {
49
+ "type": "new",
50
+ "title": "Added some new filters and hooks to allow customisation.",
51
+ "description": [
52
+ "For example, you can override the hour at which the Shield crons run, including the scans."
53
+ ],
54
+ "href": "https://shsec.io/jv"
55
+ },
56
+ {
57
+ "type": "new",
58
+ "title": "Allow webmaster to specify certain web crawlers and search engines that aren't automatically whitelisted.",
59
+ "description": [],
60
+ "href": "https://shsec.io/jt"
61
+ },
62
+ {
63
+ "type": "improved",
64
+ "title": "Big improvements in the reliability of Shield's Database handling.",
65
+ "description": []
66
+ },
67
+ {
68
+ "type": "improved",
69
+ "title": "Use CDNJS to supply important plugin Javascript/CSS assets.",
70
+ "description": [
71
+ "Using a CDN to deliver assets reduces the plugin footprint on your site, while also speeding up admin page loading."
72
+ ]
73
+ },
74
+ {
75
+ "type": "improved",
76
+ "title": "New and improved guided tour upon plugin activation.",
77
+ "description": []
78
+ },
79
+ {
80
+ "type": "improved",
81
+ "title": "Link Cheese Robots additions use enhanced Robots API in WordPress 5.7.",
82
+ "description": []
83
+ },
84
+ {
85
+ "type": "fixed",
86
+ "title": "Various bug fixes and enhancements.",
87
+ "description": [
88
+ "WP-Config FileLocker system is more reliable with requests in the case of database problems",
89
+ "Lots of code cleanup"
90
+ ]
91
+ }
92
+ ]
93
+ },
94
  "10.2": {
95
  "version": "10.2",
96
  "released_at": 1613037000,
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: 10.2.6
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: 11.0.0
7
  * Text Domain: wp-simple-firewall
8
  * Domain Path: /languages
9
  * Author: Shield Security
init.php CHANGED
@@ -56,14 +56,4 @@ catch ( \Exception $e ) {
56
  error_log( 'Perhaps due to a failed upgrade, the Shield plugin failed to load certain component(s) - you should remove the plugin and reinstall.' );
57
  error_log( $e->getMessage() );
58
  }
59
- }
60
-
61
- if ( !function_exists( 'shield_security_get_plugin' ) ) {
62
- /**
63
- * @return ICWP_WPSF_Shield_Security|null
64
- */
65
- function shield_security_get_plugin() {
66
- global $oICWP_Wpsf;
67
- return ( $oICWP_Wpsf instanceof \ICWP_WPSF_Shield_Security ) ? $oICWP_Wpsf : null;
68
- }
69
- }
56
  error_log( 'Perhaps due to a failed upgrade, the Shield plugin failed to load certain component(s) - you should remove the plugin and reinstall.' );
57
  error_log( $e->getMessage() );
58
  }
59
+ }
 
 
 
 
 
 
 
 
 
 
plugin-spec.php CHANGED
@@ -1,8 +1,8 @@
1
  {
2
  "properties": {
3
- "version": "10.2.6",
4
- "release_timestamp": 1613991385,
5
- "build": "202102.2202",
6
  "slug_parent": "icwp",
7
  "slug_plugin": "wpsf",
8
  "human_name": "Shield Security",
@@ -49,17 +49,17 @@
49
  },
50
  "plugin_admin": {
51
  "css": [
52
- "bootstrap-select.min",
53
  "plugin",
54
- "featherlight"
 
55
  ],
56
  "js": [
57
- "bootstrap-select.min",
58
  "plugin",
59
- "base64.min",
60
- "lz-string.min",
61
  "featherlight",
62
- "jquery.fileDownload"
 
63
  ]
64
  },
65
  "frontend": {
@@ -68,58 +68,69 @@
68
  },
69
  "register": {
70
  "css": {
71
- "bootstrap4.min": {
72
  "url": "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/css/bootstrap.min.css"
73
  },
74
- "bootstrap-select.min": {
 
75
  "deps": [
76
- "bootstrap4.min"
77
  ]
78
  },
79
  "bootstrap-datepicker": {
80
  "url": "https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.8.0/css/bootstrap-datepicker.min.css",
81
  "deps": [
82
- "bootstrap4.min"
83
  ]
84
  },
85
  "global-plugin": {},
86
  "plugin": {
87
  "deps": [
88
- "bootstrap4.min",
89
  "global-plugin"
90
  ]
91
  },
92
  "wizard": {
93
  "deps": [
94
- "bootstrap4.min",
95
  "global-plugin"
96
  ]
97
  },
98
  "featherlight": {},
99
- "chartist.min": {},
 
 
100
  "chartist-plugin-legend": {
101
  "deps": [
102
- "chartist.min"
103
  ]
104
  },
105
- "introjs.min": {}
 
 
 
 
 
 
 
106
  },
107
  "js": {
108
- "bootstrap4.bundle.min": {
109
  "url": "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.bundle.min.js",
110
  "deps": [
111
  "wp-jquery"
112
  ]
113
  },
114
- "bootstrap-select.min": {
 
115
  "deps": [
116
- "bootstrap4.bundle.min"
117
  ]
118
  },
119
  "bootstrap-datepicker": {
120
  "url": "https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.8.0/js/bootstrap-datepicker.min.js",
121
  "deps": [
122
- "bootstrap4.bundle.min"
123
  ]
124
  },
125
  "global-plugin": {
@@ -129,42 +140,77 @@
129
  },
130
  "plugin": {
131
  "deps": [
132
- "bootstrap4.bundle.min",
133
- "global-plugin"
 
 
134
  ]
135
  },
136
- "base64.min": {},
 
 
137
  "lz-string.min": {},
138
- "jquery.fileDownload": {},
139
- "wizard": {},
 
 
 
140
  "featherlight": {
141
  "deps": [
142
  "wp-jquery"
143
  ]
144
  },
145
- "chartist.min": {},
 
 
146
  "chartist-plugin-legend": {
147
  "deps": [
148
- "chartist.min"
149
  ]
150
  },
151
- "charts": {
 
 
 
152
  "deps": [
153
- "chartist-plugin-legend"
 
 
154
  ]
155
  },
156
- "shuffle": {},
157
- "shield-card-shuffle": {
 
 
158
  "deps": [
159
  "shuffle"
160
  ]
161
  },
162
- "introjs.min": {},
 
 
 
 
 
 
 
 
 
 
163
  "shield/tables": {
164
  "deps": [
165
  "plugin"
166
  ]
167
  },
 
 
 
 
 
 
 
 
 
168
  "shield/scans": {
169
  "deps": [
170
  "shield/tables"
@@ -182,7 +228,7 @@
182
  },
183
  "shield/mainwp-extension": {
184
  "deps": [
185
- "jquery"
186
  ]
187
  },
188
  "shield/userprofile": {
@@ -196,6 +242,20 @@
196
  "u2f-bundle",
197
  "wp-jquery"
198
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  }
200
  }
201
  }
1
  {
2
  "properties": {
3
+ "version": "11.0.0",
4
+ "release_timestamp": 1616666000,
5
+ "build": "202103.2501",
6
  "slug_parent": "icwp",
7
  "slug_plugin": "wpsf",
8
  "human_name": "Shield Security",
49
  },
50
  "plugin_admin": {
51
  "css": [
52
+ "select2",
53
  "plugin",
54
+ "featherlight",
55
+ "introjs"
56
  ],
57
  "js": [
58
+ "select2",
59
  "plugin",
 
 
60
  "featherlight",
61
+ "jquery.fileDownload",
62
+ "shield/tours"
63
  ]
64
  },
65
  "frontend": {
68
  },
69
  "register": {
70
  "css": {
71
+ "bootstrap": {
72
  "url": "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/css/bootstrap.min.css"
73
  },
74
+ "select2": {
75
+ "url": "https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css",
76
  "deps": [
77
+ "plugin"
78
  ]
79
  },
80
  "bootstrap-datepicker": {
81
  "url": "https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.8.0/css/bootstrap-datepicker.min.css",
82
  "deps": [
83
+ "bootstrap"
84
  ]
85
  },
86
  "global-plugin": {},
87
  "plugin": {
88
  "deps": [
89
+ "bootstrap",
90
  "global-plugin"
91
  ]
92
  },
93
  "wizard": {
94
  "deps": [
95
+ "bootstrap",
96
  "global-plugin"
97
  ]
98
  },
99
  "featherlight": {},
100
+ "chartist": {
101
+ "url": "https://cdnjs.cloudflare.com/ajax/libs/chartist/0.11.4/chartist.min.css"
102
+ },
103
  "chartist-plugin-legend": {
104
  "deps": [
105
+ "chartist"
106
  ]
107
  },
108
+ "introjs": {
109
+ "url": "https://cdnjs.cloudflare.com/ajax/libs/intro.js/3.3.1/introjs.min.css"
110
+ },
111
+ "shield/charts": {
112
+ "deps": [
113
+ "plugin"
114
+ ]
115
+ }
116
  },
117
  "js": {
118
+ "bootstrap": {
119
  "url": "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.bundle.min.js",
120
  "deps": [
121
  "wp-jquery"
122
  ]
123
  },
124
+ "select2": {
125
+ "url": "https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js",
126
  "deps": [
127
+ "plugin"
128
  ]
129
  },
130
  "bootstrap-datepicker": {
131
  "url": "https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.8.0/js/bootstrap-datepicker.min.js",
132
  "deps": [
133
+ "bootstrap"
134
  ]
135
  },
136
  "global-plugin": {
140
  },
141
  "plugin": {
142
  "deps": [
143
+ "bootstrap",
144
+ "global-plugin",
145
+ "base64.min",
146
+ "lz-string.min"
147
  ]
148
  },
149
+ "base64.min": {
150
+ "url": "https://cdn.jsdelivr.net/npm/js-base64@2.6.4/base64.min.js"
151
+ },
152
  "lz-string.min": {},
153
+ "jquery.fileDownload": {
154
+ "deps": [
155
+ "wp-jquery"
156
+ ]
157
+ },
158
  "featherlight": {
159
  "deps": [
160
  "wp-jquery"
161
  ]
162
  },
163
+ "chartist": {
164
+ "url": "https://cdnjs.cloudflare.com/ajax/libs/chartist/0.11.4/chartist.min.js"
165
+ },
166
  "chartist-plugin-legend": {
167
  "deps": [
168
+ "chartist"
169
  ]
170
  },
171
+ "introjs": {
172
+ "url": "https://cdnjs.cloudflare.com/ajax/libs/intro.js/3.3.1/intro.min.js"
173
+ },
174
+ "shield/charts": {
175
  "deps": [
176
+ "chartist",
177
+ "chartist-plugin-legend",
178
+ "plugin"
179
  ]
180
  },
181
+ "shuffle": {
182
+ "url": "https://cdnjs.cloudflare.com/ajax/libs/Shuffle/5.3.0/shuffle.min.js"
183
+ },
184
+ "shield/shuffle": {
185
  "deps": [
186
  "shuffle"
187
  ]
188
  },
189
+ "shield/comments": {
190
+ "deps": [
191
+ "wp-jquery"
192
+ ],
193
+ "footer": true
194
+ },
195
+ "shield/loginbot": {
196
+ "deps": [
197
+ "wp-jquery"
198
+ ]
199
+ },
200
  "shield/tables": {
201
  "deps": [
202
  "plugin"
203
  ]
204
  },
205
+ "shield/tours": {
206
+ "deps": [
207
+ "plugin",
208
+ "introjs"
209
+ ]
210
+ },
211
+ "shield/antibot": {
212
+ "footer": true
213
+ },
214
  "shield/scans": {
215
  "deps": [
216
  "shield/tables"
228
  },
229
  "shield/mainwp-extension": {
230
  "deps": [
231
+ "wp-jquery"
232
  ]
233
  },
234
  "shield/userprofile": {
242
  "u2f-bundle",
243
  "wp-jquery"
244
  ]
245
+ },
246
+ "tp/grecaptcha": {
247
+ "url": "https://www.google.com/recaptcha/api.js",
248
+ "attributes": {
249
+ "async": "async",
250
+ "defer": "defer"
251
+ }
252
+ },
253
+ "tp/hcaptcha": {
254
+ "url": "https://hcaptcha.com/1/api.js",
255
+ "attributes": {
256
+ "async": "async",
257
+ "defer": "defer"
258
+ }
259
  }
260
  }
261
  }
readme.txt CHANGED
@@ -3,24 +3,23 @@ Contributors: paultgoodchild, getshieldsecurity
3
  Donate link: https://shsec.io/bw
4
  License: GPLv3
5
  License URI: http://www.gnu.org/licenses/gpl.html
6
- Tags: scan, malware, firewall, two factor authentication, login protection, security
7
  Requires at least: 3.5.2
8
  Requires PHP: 7.0
9
  Recommended PHP: 7.4
10
  Tested up to: 5.7
11
- Stable tag: 10.2.6
12
-
13
  Security against hackers and brute force bots with firewall, login security hiding and hardening, Antispam, Audit Trail, Live Traffic, and much more...
14
 
15
  == Description ==
16
 
17
- Shield Security helps you add expert security to all your WordPress sites, without being a security expert.
18
 
19
  #### Get the highest rated 5* Security Plugin for WordPress
20
 
21
  Per download, Shield Security [has the highest 5* rating](https://shsec.io/jl) in the WordPress plugin repository.
22
 
23
- ## Your Security Goal Is Freedom From Hackers
24
 
25
  To be free from hackers, your WordPress Security need to be smarter, adaptive and uncomplicated.
26
 
@@ -35,9 +34,13 @@ Shield Security uses 2 simple key strategies to protect your WordPress sites:
35
 
36
  #### Key Security Strategy #1: Hacking Prevention
37
 
38
- Blocking malicious bots before they can do any damage is the most effective strategy to enhance security on a WordPress site.
 
 
 
 
39
 
40
- Many Shield features focus on detecting these malicious visitors, then blocking access to your site altogether. This involves analysing different bot-signals and combining them to confidently identify a visitor as malicious.
41
 
42
  These signals include:
43
 
@@ -46,9 +49,11 @@ These signals include:
46
  * logins with invalid usernames
47
  * xml-rpc access
48
  * fake search engine web crawlers
 
 
49
  * and many more signals our security team have identified...
50
 
51
- Early identification and blocking of malicious bots reduces your WordPress site's susceptibility to attack.
52
 
53
  #### Key Strategy #2: Hacking Cure
54
 
@@ -85,14 +90,15 @@ Shield Security handles many problems for you, making intelligent security decis
85
 
86
  #### WordPress Security Features You'll Absolutely Love
87
 
88
- * [Automatic Bot & IP Blocking](https://shsec.io/j0) - points-based system (that you control) to detect bad bots and block them.
 
89
  * Add Security To Important Forms To Block Bots:
90
  * Login
91
  * Registration
92
  * Password Reset
93
  * [ShieldPRO] WooCommerce & Easy Digital Downloads
94
  * [ShieldPRO] Memberpress, LearnPress, BuddyPress, WP Members, ProfileBuilder
95
- * [Limit Login Attempts + Login Cooldown System](https://shsec.io/iw)
96
  * Powerful Firewall Security Rules
97
  * Restricted Security Admin Access
98
  * [Prevents Unauthorized Changes To Site Even By Admins](https://shsec.io/ix).
@@ -111,7 +117,7 @@ Shield Security handles many problems for you, making intelligent security decis
111
  * Block or Bypass individual IPs
112
  * Block or Bypass IP Subnets
113
  * Full IP Analysis in 1 place to see their activity on your sites
114
- * Complete WordPress Scanning for Intrusions and Hacks
115
  * Detect File Changes - [Scan & Repair WordPress Core Files](https://shsec.io/j1)
116
  * [Detect Unknown/Suspicious PHP Files](https://shsec.io/j2)
117
  * Detect Abandoned Plugins.
3
  Donate link: https://shsec.io/bw
4
  License: GPLv3
5
  License URI: http://www.gnu.org/licenses/gpl.html
6
+ Tags: scan, malware, firewall, two factor authentication, login protection
7
  Requires at least: 3.5.2
8
  Requires PHP: 7.0
9
  Recommended PHP: 7.4
10
  Tested up to: 5.7
11
+ Stable tag: 11.0.0
 
12
  Security against hackers and brute force bots with firewall, login security hiding and hardening, Antispam, Audit Trail, Live Traffic, and much more...
13
 
14
  == Description ==
15
 
16
+ Add expert security to all your WordPress sites with Shield Security, without being a security expert.
17
 
18
  #### Get the highest rated 5* Security Plugin for WordPress
19
 
20
  Per download, Shield Security [has the highest 5* rating](https://shsec.io/jl) in the WordPress plugin repository.
21
 
22
+ ## Your Goal: Security Is Peace Of Mind and Freedom From Hackers
23
 
24
  To be free from hackers, your WordPress Security need to be smarter, adaptive and uncomplicated.
25
 
34
 
35
  #### Key Security Strategy #1: Hacking Prevention
36
 
37
+ Bots cause nearly all our security troubles - they're relentless, automatic and powerful.
38
+
39
+ Shield Security is the only plugin dedicated to their detection and erradication from your WordPress site.
40
+
41
+ Blocking malicious bots before they can do damage is the key strategy to protect and enhance security on a WordPress site.
42
 
43
+ Shield uses its features to detect these malicious visitors, then block access to your site altogether. This involves analysing different bot-signals and combining them to confidently identify a visitor as malicious.
44
 
45
  These signals include:
46
 
49
  * logins with invalid usernames
50
  * xml-rpc access
51
  * fake search engine web crawlers
52
+ * invalid user agents
53
+ * excessive website requests and resource abuse
54
  * and many more signals our security team have identified...
55
 
56
+ Early identification and blocking of malicious bots reduces your WordPress site's vulnerability to attack.
57
 
58
  #### Key Strategy #2: Hacking Cure
59
 
90
 
91
  #### WordPress Security Features You'll Absolutely Love
92
 
93
+ * Exclusive [AntiBot Detection Engine](https://shsec.io/ju) - The most powerful Bot Detection system on any WordPress plugin.
94
+ * [Automatic Bot & IP Blocking](https://shsec.io/j0) - points-based security system to block bad bots.
95
  * Add Security To Important Forms To Block Bots:
96
  * Login
97
  * Registration
98
  * Password Reset
99
  * [ShieldPRO] WooCommerce & Easy Digital Downloads
100
  * [ShieldPRO] Memberpress, LearnPress, BuddyPress, WP Members, ProfileBuilder
101
+ * [Brute Force Protection, Limit Login Attempts + Login Cooldown System](https://shsec.io/iw)
102
  * Powerful Firewall Security Rules
103
  * Restricted Security Admin Access
104
  * [Prevents Unauthorized Changes To Site Even By Admins](https://shsec.io/ix).
117
  * Block or Bypass individual IPs
118
  * Block or Bypass IP Subnets
119
  * Full IP Analysis in 1 place to see their activity on your sites
120
+ * Comprehensive WordPress File Scanner for Intrusions and Hacks
121
  * Detect File Changes - [Scan & Repair WordPress Core Files](https://shsec.io/j1)
122
  * [Detect Unknown/Suspicious PHP Files](https://shsec.io/j2)
123
  * Detect Abandoned Plugins.
resources/css/bootstrap-select.min.css DELETED
@@ -1,6 +0,0 @@
1
- /*!
2
- * Bootstrap-select v1.13.18 (https://developer.snapappointments.com/bootstrap-select)
3
- *
4
- * Copyright 2012-2020 SnapAppointments, LLC
5
- * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
6
- */@-webkit-keyframes bs-notify-fadeOut{0%{opacity:.9}100%{opacity:0}}@-o-keyframes bs-notify-fadeOut{0%{opacity:.9}100%{opacity:0}}@keyframes bs-notify-fadeOut{0%{opacity:.9}100%{opacity:0}}.bootstrap-select>select.bs-select-hidden,select.bs-select-hidden,select.selectpicker{display:none!important}.bootstrap-select{width:220px\0;vertical-align:middle}.bootstrap-select>.dropdown-toggle{position:relative;width:100%;text-align:right;white-space:nowrap;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.bootstrap-select>.dropdown-toggle:after{margin-top:-1px}.bootstrap-select>.dropdown-toggle.bs-placeholder,.bootstrap-select>.dropdown-toggle.bs-placeholder:active,.bootstrap-select>.dropdown-toggle.bs-placeholder:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder:hover{color:#999}.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:hover{color:rgba(255,255,255,.5)}.bootstrap-select>select{position:absolute!important;bottom:0;left:50%;display:block!important;width:.5px!important;height:100%!important;padding:0!important;opacity:0!important;border:none;z-index:0!important}.bootstrap-select>select.mobile-device{top:0;left:0;display:block!important;width:100%!important;z-index:2!important}.bootstrap-select.is-invalid .dropdown-toggle,.error .bootstrap-select .dropdown-toggle,.has-error .bootstrap-select .dropdown-toggle,.was-validated .bootstrap-select select:invalid+.dropdown-toggle{border-color:#b94a48}.bootstrap-select.is-valid .dropdown-toggle,.was-validated .bootstrap-select select:valid+.dropdown-toggle{border-color:#28a745}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select:not([class*=col-]):not([class*=form-control]):not(.input-group-btn){width:220px}.bootstrap-select .dropdown-toggle:focus,.bootstrap-select>select.mobile-device:focus+.dropdown-toggle{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none;height:auto}:not(.input-group)>.bootstrap-select.form-control:not([class*=col-]){width:100%}.bootstrap-select.form-control.input-group-btn{float:none;z-index:auto}.form-inline .bootstrap-select,.form-inline .bootstrap-select.form-control:not([class*=col-]){width:auto}.bootstrap-select:not(.input-group-btn),.bootstrap-select[class*=col-]{float:none;display:inline-block;margin-left:0}.bootstrap-select.dropdown-menu-right,.bootstrap-select[class*=col-].dropdown-menu-right,.row .bootstrap-select[class*=col-].dropdown-menu-right{float:right}.form-group .bootstrap-select,.form-horizontal .bootstrap-select,.form-inline .bootstrap-select{margin-bottom:0}.form-group-lg .bootstrap-select.form-control,.form-group-sm .bootstrap-select.form-control{padding:0}.form-group-lg .bootstrap-select.form-control .dropdown-toggle,.form-group-sm .bootstrap-select.form-control .dropdown-toggle{height:100%;font-size:inherit;line-height:inherit;border-radius:inherit}.bootstrap-select.form-control-lg .dropdown-toggle,.bootstrap-select.form-control-sm .dropdown-toggle{font-size:inherit;line-height:inherit;border-radius:inherit}.bootstrap-select.form-control-sm .dropdown-toggle{padding:.25rem .5rem}.bootstrap-select.form-control-lg .dropdown-toggle{padding:.5rem 1rem}.form-inline .bootstrap-select .form-control{width:100%}.bootstrap-select.disabled,.bootstrap-select>.disabled{cursor:not-allowed}.bootstrap-select.disabled:focus,.bootstrap-select>.disabled:focus{outline:0!important}.bootstrap-select.bs-container{position:absolute;top:0;left:0;height:0!important;padding:0!important}.bootstrap-select.bs-container .dropdown-menu{z-index:1060}.bootstrap-select .dropdown-toggle .filter-option{position:static;top:0;left:0;float:left;height:100%;width:100%;text-align:left;overflow:hidden;-webkit-box-flex:0;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto}.bs3.bootstrap-select .dropdown-toggle .filter-option{padding-right:inherit}.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option{position:absolute;padding-top:inherit;padding-bottom:inherit;padding-left:inherit;float:none}.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option .filter-option-inner{padding-right:inherit}.bootstrap-select .dropdown-toggle .filter-option-inner-inner{overflow:hidden}.bootstrap-select .dropdown-toggle .filter-expand{width:0!important;float:left;opacity:0!important;overflow:hidden}.bootstrap-select .dropdown-toggle .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.input-group .bootstrap-select.form-control .dropdown-toggle{border-radius:inherit}.bootstrap-select[class*=col-] .dropdown-toggle{width:100%}.bootstrap-select .dropdown-menu{min-width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select .dropdown-menu>.inner:focus{outline:0!important}.bootstrap-select .dropdown-menu.inner{position:static;float:none;border:0;padding:0;margin:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.bootstrap-select .dropdown-menu li{position:relative}.bootstrap-select .dropdown-menu li.active small{color:rgba(255,255,255,.5)!important}.bootstrap-select .dropdown-menu li.disabled a{cursor:not-allowed}.bootstrap-select .dropdown-menu li a{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.bootstrap-select .dropdown-menu li a.opt{position:relative;padding-left:2.25em}.bootstrap-select .dropdown-menu li a span.check-mark{display:none}.bootstrap-select .dropdown-menu li a span.text{display:inline-block}.bootstrap-select .dropdown-menu li small{padding-left:.5em}.bootstrap-select .dropdown-menu .notify{position:absolute;bottom:5px;width:96%;margin:0 2%;min-height:26px;padding:3px 5px;background:#f5f5f5;border:1px solid #e3e3e3;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05);pointer-events:none;opacity:.9;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select .dropdown-menu .notify.fadeOut{-webkit-animation:.3s linear 750ms forwards bs-notify-fadeOut;-o-animation:.3s linear 750ms forwards bs-notify-fadeOut;animation:.3s linear 750ms forwards bs-notify-fadeOut}.bootstrap-select .no-results{padding:3px;background:#f5f5f5;margin:0 5px;white-space:nowrap}.bootstrap-select.fit-width .dropdown-toggle .filter-option{position:static;display:inline;padding:0}.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner,.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner-inner{display:inline}.bootstrap-select.fit-width .dropdown-toggle .bs-caret:before{content:'\00a0'}.bootstrap-select.fit-width .dropdown-toggle .caret{position:static;top:auto;margin-top:-1px}.bootstrap-select.show-tick .dropdown-menu .selected span.check-mark{position:absolute;display:inline-block;right:15px;top:5px}.bootstrap-select.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select .bs-ok-default:after{content:'';display:block;width:.5em;height:1em;border-style:solid;border-width:0 .26em .26em 0;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle{z-index:1061}.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:before{content:'';border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(204,204,204,.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:after{content:'';border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:before{bottom:auto;top:-4px;border-top:7px solid rgba(204,204,204,.2);border-bottom:0}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:after{bottom:auto;top:-4px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle .filter-option:after,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle .filter-option:before,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle .filter-option:after,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle .filter-option:before{display:block}.bs-actionsbox,.bs-donebutton,.bs-searchbox{padding:4px 8px}.bs-actionsbox{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-actionsbox .btn-group button{width:50%}.bs-donebutton{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-donebutton .btn-group button{width:100%}.bs-searchbox+.bs-actionsbox{padding:0 8px 4px}.bs-searchbox .form-control{margin-bottom:0;width:100%;float:none}
 
 
 
 
 
 
resources/css/chartist.min.css DELETED
@@ -1 +0,0 @@
1
- .ct-double-octave:after,.ct-golden-section:after,.ct-major-eleventh:after,.ct-major-second:after,.ct-major-seventh:after,.ct-major-sixth:after,.ct-major-tenth:after,.ct-major-third:after,.ct-major-twelfth:after,.ct-minor-second:after,.ct-minor-seventh:after,.ct-minor-sixth:after,.ct-minor-third:after,.ct-octave:after,.ct-perfect-fifth:after,.ct-perfect-fourth:after,.ct-square:after{content:"";clear:both}.ct-label{fill:rgba(0,0,0,.4);color:rgba(0,0,0,.4);font-size:.75rem;line-height:1}.ct-chart-bar .ct-label,.ct-chart-line .ct-label{display:block;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}.ct-chart-donut .ct-label,.ct-chart-pie .ct-label{dominant-baseline:central}.ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-label.ct-vertical.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-end;-webkit-justify-content:flex-end;-ms-flex-pack:flex-end;justify-content:flex-end;text-align:right;text-anchor:end}.ct-label.ct-vertical.ct-end{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar .ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;text-anchor:start}.ct-chart-bar .ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-start{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:flex-end;-webkit-justify-content:flex-end;-ms-flex-pack:flex-end;justify-content:flex-end;text-align:right;text-anchor:end}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-end{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:end}.ct-grid{stroke:rgba(0,0,0,.2);stroke-width:1px;stroke-dasharray:2px}.ct-grid-background{fill:none}.ct-point{stroke-width:10px;stroke-linecap:round}.ct-line{fill:none;stroke-width:4px}.ct-area{stroke:none;fill-opacity:.1}.ct-bar{fill:none;stroke-width:10px}.ct-slice-donut{fill:none;stroke-width:60px}.ct-series-a .ct-bar,.ct-series-a .ct-line,.ct-series-a .ct-point,.ct-series-a .ct-slice-donut{stroke:#d70206}.ct-series-a .ct-area,.ct-series-a .ct-slice-donut-solid,.ct-series-a .ct-slice-pie{fill:#d70206}.ct-series-b .ct-bar,.ct-series-b .ct-line,.ct-series-b .ct-point,.ct-series-b .ct-slice-donut{stroke:#f05b4f}.ct-series-b .ct-area,.ct-series-b .ct-slice-donut-solid,.ct-series-b .ct-slice-pie{fill:#f05b4f}.ct-series-c .ct-bar,.ct-series-c .ct-line,.ct-series-c .ct-point,.ct-series-c .ct-slice-donut{stroke:#f4c63d}.ct-series-c .ct-area,.ct-series-c .ct-slice-donut-solid,.ct-series-c .ct-slice-pie{fill:#f4c63d}.ct-series-d .ct-bar,.ct-series-d .ct-line,.ct-series-d .ct-point,.ct-series-d .ct-slice-donut{stroke:#d17905}.ct-series-d .ct-area,.ct-series-d .ct-slice-donut-solid,.ct-series-d .ct-slice-pie{fill:#d17905}.ct-series-e .ct-bar,.ct-series-e .ct-line,.ct-series-e .ct-point,.ct-series-e .ct-slice-donut{stroke:#453d3f}.ct-series-e .ct-area,.ct-series-e .ct-slice-donut-solid,.ct-series-e .ct-slice-pie{fill:#453d3f}.ct-series-f .ct-bar,.ct-series-f .ct-line,.ct-series-f .ct-point,.ct-series-f .ct-slice-donut{stroke:#59922b}.ct-series-f .ct-area,.ct-series-f .ct-slice-donut-solid,.ct-series-f .ct-slice-pie{fill:#59922b}.ct-series-g .ct-bar,.ct-series-g .ct-line,.ct-series-g .ct-point,.ct-series-g .ct-slice-donut{stroke:#0544d3}.ct-series-g .ct-area,.ct-series-g .ct-slice-donut-solid,.ct-series-g .ct-slice-pie{fill:#0544d3}.ct-series-h .ct-bar,.ct-series-h .ct-line,.ct-series-h .ct-point,.ct-series-h .ct-slice-donut{stroke:#6b0392}.ct-series-h .ct-area,.ct-series-h .ct-slice-donut-solid,.ct-series-h .ct-slice-pie{fill:#6b0392}.ct-series-i .ct-bar,.ct-series-i .ct-line,.ct-series-i .ct-point,.ct-series-i .ct-slice-donut{stroke:#f05b4f}.ct-series-i .ct-area,.ct-series-i .ct-slice-donut-solid,.ct-series-i .ct-slice-pie{fill:#f05b4f}.ct-series-j .ct-bar,.ct-series-j .ct-line,.ct-series-j .ct-point,.ct-series-j .ct-slice-donut{stroke:#dda458}.ct-series-j .ct-area,.ct-series-j .ct-slice-donut-solid,.ct-series-j .ct-slice-pie{fill:#dda458}.ct-series-k .ct-bar,.ct-series-k .ct-line,.ct-series-k .ct-point,.ct-series-k .ct-slice-donut{stroke:#eacf7d}.ct-series-k .ct-area,.ct-series-k .ct-slice-donut-solid,.ct-series-k .ct-slice-pie{fill:#eacf7d}.ct-series-l .ct-bar,.ct-series-l .ct-line,.ct-series-l .ct-point,.ct-series-l .ct-slice-donut{stroke:#86797d}.ct-series-l .ct-area,.ct-series-l .ct-slice-donut-solid,.ct-series-l .ct-slice-pie{fill:#86797d}.ct-series-m .ct-bar,.ct-series-m .ct-line,.ct-series-m .ct-point,.ct-series-m .ct-slice-donut{stroke:#b2c326}.ct-series-m .ct-area,.ct-series-m .ct-slice-donut-solid,.ct-series-m .ct-slice-pie{fill:#b2c326}.ct-series-n .ct-bar,.ct-series-n .ct-line,.ct-series-n .ct-point,.ct-series-n .ct-slice-donut{stroke:#6188e2}.ct-series-n .ct-area,.ct-series-n .ct-slice-donut-solid,.ct-series-n .ct-slice-pie{fill:#6188e2}.ct-series-o .ct-bar,.ct-series-o .ct-line,.ct-series-o .ct-point,.ct-series-o .ct-slice-donut{stroke:#a748ca}.ct-series-o .ct-area,.ct-series-o .ct-slice-donut-solid,.ct-series-o .ct-slice-pie{fill:#a748ca}.ct-square{display:block;position:relative;width:100%}.ct-square:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:100%}.ct-square:after{display:table}.ct-square>svg{display:block;position:absolute;top:0;left:0}.ct-minor-second{display:block;position:relative;width:100%}.ct-minor-second:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:93.75%}.ct-minor-second:after{display:table}.ct-minor-second>svg{display:block;position:absolute;top:0;left:0}.ct-major-second{display:block;position:relative;width:100%}.ct-major-second:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:88.8888888889%}.ct-major-second:after{display:table}.ct-major-second>svg{display:block;position:absolute;top:0;left:0}.ct-minor-third{display:block;position:relative;width:100%}.ct-minor-third:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:83.3333333333%}.ct-minor-third:after{display:table}.ct-minor-third>svg{display:block;position:absolute;top:0;left:0}.ct-major-third{display:block;position:relative;width:100%}.ct-major-third:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:80%}.ct-major-third:after{display:table}.ct-major-third>svg{display:block;position:absolute;top:0;left:0}.ct-perfect-fourth{display:block;position:relative;width:100%}.ct-perfect-fourth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:75%}.ct-perfect-fourth:after{display:table}.ct-perfect-fourth>svg{display:block;position:absolute;top:0;left:0}.ct-perfect-fifth{display:block;position:relative;width:100%}.ct-perfect-fifth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:66.6666666667%}.ct-perfect-fifth:after{display:table}.ct-perfect-fifth>svg{display:block;position:absolute;top:0;left:0}.ct-minor-sixth{display:block;position:relative;width:100%}.ct-minor-sixth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:62.5%}.ct-minor-sixth:after{display:table}.ct-minor-sixth>svg{display:block;position:absolute;top:0;left:0}.ct-golden-section{display:block;position:relative;width:100%}.ct-golden-section:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:61.804697157%}.ct-golden-section:after{display:table}.ct-golden-section>svg{display:block;position:absolute;top:0;left:0}.ct-major-sixth{display:block;position:relative;width:100%}.ct-major-sixth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:60%}.ct-major-sixth:after{display:table}.ct-major-sixth>svg{display:block;position:absolute;top:0;left:0}.ct-minor-seventh{display:block;position:relative;width:100%}.ct-minor-seventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:56.25%}.ct-minor-seventh:after{display:table}.ct-minor-seventh>svg{display:block;position:absolute;top:0;left:0}.ct-major-seventh{display:block;position:relative;width:100%}.ct-major-seventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:53.3333333333%}.ct-major-seventh:after{display:table}.ct-major-seventh>svg{display:block;position:absolute;top:0;left:0}.ct-octave{display:block;position:relative;width:100%}.ct-octave:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:50%}.ct-octave:after{display:table}.ct-octave>svg{display:block;position:absolute;top:0;left:0}.ct-major-tenth{display:block;position:relative;width:100%}.ct-major-tenth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:40%}.ct-major-tenth:after{display:table}.ct-major-tenth>svg{display:block;position:absolute;top:0;left:0}.ct-major-eleventh{display:block;position:relative;width:100%}.ct-major-eleventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:37.5%}.ct-major-eleventh:after{display:table}.ct-major-eleventh>svg{display:block;position:absolute;top:0;left:0}.ct-major-twelfth{display:block;position:relative;width:100%}.ct-major-twelfth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:33.3333333333%}.ct-major-twelfth:after{display:table}.ct-major-twelfth>svg{display:block;position:absolute;top:0;left:0}.ct-double-octave{display:block;position:relative;width:100%}.ct-double-octave:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:25%}.ct-double-octave:after{display:table}.ct-double-octave>svg{display:block;position:absolute;top:0;left:0}
 
resources/css/introjs.min.css DELETED
@@ -1 +0,0 @@
1
- .introjs-overlay{position:absolute;box-sizing:content-box;z-index:999999;background-color:#000;opacity:0;background:-moz-radial-gradient(center,ellipse farthest-corner,rgba(0,0,0,.4) 0,rgba(0,0,0,.9) 100%);background:-webkit-gradient(radial,center center,0,center center,100%,color-stop(0,rgba(0,0,0,.4)),color-stop(100%,rgba(0,0,0,.9)));background:-webkit-radial-gradient(center,ellipse farthest-corner,rgba(0,0,0,.4) 0,rgba(0,0,0,.9) 100%);background:-o-radial-gradient(center,ellipse farthest-corner,rgba(0,0,0,.4) 0,rgba(0,0,0,.9) 100%);background:-ms-radial-gradient(center,ellipse farthest-corner,rgba(0,0,0,.4) 0,rgba(0,0,0,.9) 100%);background:radial-gradient(center,ellipse farthest-corner,rgba(0,0,0,.4) 0,rgba(0,0,0,.9) 100%);-webkit-transition:all .3s ease-out;-moz-transition:all .3s ease-out;-ms-transition:all .3s ease-out;-o-transition:all .3s ease-out;transition:all .3s ease-out}.introjs-fixParent{z-index:auto!important;opacity:1!important;-webkit-transform:none!important;-moz-transform:none!important;-ms-transform:none!important;-o-transform:none!important;transform:none!important}.introjs-showElement,tr.introjs-showElement>td,tr.introjs-showElement>th{z-index:9999999!important}.introjs-disableInteraction{z-index:99999999!important;position:absolute;background-color:#fff;opacity:0}.introjs-relativePosition,tr.introjs-showElement>td,tr.introjs-showElement>th{position:relative}.introjs-helperLayer{box-sizing:content-box;position:absolute;z-index:9999998;background-color:#fff;background-color:rgba(255,255,255,.9);border:1px solid #777;border:1px solid rgba(0,0,0,.5);border-radius:4px;box-shadow:0 2px 15px rgba(0,0,0,.4);-webkit-transition:all .3s ease-out;-moz-transition:all .3s ease-out;-ms-transition:all .3s ease-out;-o-transition:all .3s ease-out;transition:all .3s ease-out}.introjs-tooltipReferenceLayer{box-sizing:content-box;position:absolute;visibility:hidden;z-index:100000000;background-color:transparent;-webkit-transition:all .3s ease-out;-moz-transition:all .3s ease-out;-ms-transition:all .3s ease-out;-o-transition:all .3s ease-out;transition:all .3s ease-out}.introjs-helperLayer *,.introjs-helperLayer :after,.introjs-helperLayer :before{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;-ms-box-sizing:content-box;-o-box-sizing:content-box;box-sizing:content-box}.introjs-helperNumberLayer{box-sizing:content-box;position:absolute;visibility:visible;top:-16px;left:-16px;z-index:9999999999!important;padding:2px;font-family:Arial,verdana,tahoma;font-size:13px;font-weight:700;color:#fff;text-align:center;text-shadow:1px 1px 1px rgba(0,0,0,.3);background:#ff3019;background:-webkit-linear-gradient(top,#ff3019 0,#cf0404 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#ff3019),color-stop(100%,#cf0404));background:-moz-linear-gradient(top,#ff3019 0,#cf0404 100%);background:-ms-linear-gradient(top,#ff3019 0,#cf0404 100%);background:-o-linear-gradient(top,#ff3019 0,#cf0404 100%);background:linear-gradient(to bottom,#ff3019 0,#cf0404 100%);width:20px;height:20px;line-height:20px;border:3px solid #fff;border-radius:50%;box-shadow:0 2px 5px rgba(0,0,0,.4)}.introjs-arrow{border:5px solid transparent;content:'';position:absolute}.introjs-arrow.top{top:-10px;border-bottom-color:#fff}.introjs-arrow.top-right{top:-10px;right:10px;border-bottom-color:#fff}.introjs-arrow.top-middle{top:-10px;left:50%;margin-left:-5px;border-bottom-color:#fff}.introjs-arrow.right{right:-10px;top:10px;border-left-color:#fff}.introjs-arrow.right-bottom{bottom:10px;right:-10px;border-left-color:#fff}.introjs-arrow.bottom{bottom:-10px;border-top-color:#fff}.introjs-arrow.bottom-right{bottom:-10px;right:10px;border-top-color:#fff}.introjs-arrow.bottom-middle{bottom:-10px;left:50%;margin-left:-5px;border-top-color:#fff}.introjs-arrow.left{left:-10px;top:10px;border-right-color:#fff}.introjs-arrow.left-bottom{left:-10px;bottom:10px;border-right-color:#fff}.introjs-tooltip{box-sizing:content-box;position:absolute;visibility:visible;padding:10px;background-color:#fff;min-width:200px;max-width:300px;border-radius:3px;box-shadow:0 1px 10px rgba(0,0,0,.4);-webkit-transition:opacity .1s ease-out;-moz-transition:opacity .1s ease-out;-ms-transition:opacity .1s ease-out;-o-transition:opacity .1s ease-out;transition:opacity .1s ease-out}.introjs-tooltipbuttons{text-align:right;white-space:nowrap}.introjs-button{box-sizing:content-box;position:relative;overflow:visible;display:inline-block;padding:.3em .8em;border:1px solid #d4d4d4;margin:0;text-decoration:none;text-shadow:1px 1px 0 #fff;font:11px/normal sans-serif;color:#333;white-space:nowrap;cursor:pointer;outline:0;background-color:#ececec;background-image:-webkit-gradient(linear,0 0,0 100%,from(#f4f4f4),to(#ececec));background-image:-moz-linear-gradient(#f4f4f4,#ececec);background-image:-o-linear-gradient(#f4f4f4,#ececec);background-image:linear-gradient(#f4f4f4,#ececec);-webkit-background-clip:padding;-moz-background-clip:padding;-o-background-clip:padding-box;-webkit-border-radius:.2em;-moz-border-radius:.2em;border-radius:.2em;zoom:1;margin-top:10px}.introjs-button:hover{border-color:#bcbcbc;text-decoration:none;box-shadow:0 1px 1px #e3e3e3}.introjs-button:active,.introjs-button:focus{background-image:-webkit-gradient(linear,0 0,0 100%,from(#ececec),to(#f4f4f4));background-image:-moz-linear-gradient(#ececec,#f4f4f4);background-image:-o-linear-gradient(#ececec,#f4f4f4);background-image:linear-gradient(#ececec,#f4f4f4)}.introjs-button::-moz-focus-inner{padding:0;border:0}.introjs-skipbutton{box-sizing:content-box;margin-right:5px;color:#7a7a7a}.introjs-prevbutton{-webkit-border-radius:.2em 0 0 .2em;-moz-border-radius:.2em 0 0 .2em;border-radius:.2em 0 0 .2em;border-right:none}.introjs-prevbutton.introjs-fullbutton{border:1px solid #d4d4d4;-webkit-border-radius:.2em;-moz-border-radius:.2em;border-radius:.2em}.introjs-nextbutton{-webkit-border-radius:0 .2em .2em 0;-moz-border-radius:0 .2em .2em 0;border-radius:0 .2em .2em 0}.introjs-nextbutton.introjs-fullbutton{-webkit-border-radius:.2em;-moz-border-radius:.2em;border-radius:.2em}.introjs-disabled,.introjs-disabled:focus,.introjs-disabled:hover{color:#9a9a9a;border-color:#d4d4d4;box-shadow:none;cursor:default;background-color:#f4f4f4;background-image:none;text-decoration:none}.introjs-hidden{display:none}.introjs-bullets{text-align:center}.introjs-bullets ul{box-sizing:content-box;clear:both;margin:15px auto 0;padding:0;display:inline-block}.introjs-bullets ul li{box-sizing:content-box;list-style:none;float:left;margin:0 2px}.introjs-bullets ul li a{box-sizing:content-box;display:block;width:6px;height:6px;background:#ccc;border-radius:10px;-moz-border-radius:10px;-webkit-border-radius:10px;text-decoration:none;cursor:pointer}.introjs-bullets ul li a:hover{background:#999}.introjs-bullets ul li a.active{background:#999}.introjs-progress{box-sizing:content-box;overflow:hidden;height:10px;margin:10px 0 5px 0;border-radius:4px;background-color:#ecf0f1}.introjs-progressbar{box-sizing:content-box;float:left;width:0%;height:100%;font-size:10px;line-height:10px;text-align:center;background-color:#08c}.introjsFloatingElement{position:absolute;height:0;width:0;left:50%;top:50%}.introjs-fixedTooltip{position:fixed}.introjs-hint{box-sizing:content-box;position:absolute;background:0 0;width:20px;height:15px;cursor:pointer}.introjs-hint:focus{border:0;outline:0}.introjs-hidehint{display:none}.introjs-fixedhint{position:fixed}.introjs-hint:hover>.introjs-hint-pulse{border:5px solid rgba(60,60,60,.57)}.introjs-hint-pulse{box-sizing:content-box;width:10px;height:10px;border:5px solid rgba(60,60,60,.27);-webkit-border-radius:30px;-moz-border-radius:30px;border-radius:30px;background-color:rgba(136,136,136,.24);z-index:10;position:absolute;-webkit-transition:all .2s ease-out;-moz-transition:all .2s ease-out;-ms-transition:all .2s ease-out;-o-transition:all .2s ease-out;transition:all .2s ease-out}.introjs-hint-no-anim .introjs-hint-dot{-webkit-animation:none;-moz-animation:none;animation:none}.introjs-hint-dot{box-sizing:content-box;border:10px solid rgba(146,146,146,.36);background:0 0;-webkit-border-radius:60px;-moz-border-radius:60px;border-radius:60px;height:50px;width:50px;-webkit-animation:introjspulse 3s ease-out;-moz-animation:introjspulse 3s ease-out;animation:introjspulse 3s ease-out;-webkit-animation-iteration-count:infinite;-moz-animation-iteration-count:infinite;animation-iteration-count:infinite;position:absolute;top:-25px;left:-25px;z-index:1;opacity:0}@-webkit-keyframes introjspulse{0%{-webkit-transform:scale(0);opacity:0}25%{-webkit-transform:scale(0);opacity:.1}50%{-webkit-transform:scale(.1);opacity:.3}75%{-webkit-transform:scale(.5);opacity:.5}100%{-webkit-transform:scale(1);opacity:0}}@-moz-keyframes introjspulse{0%{-moz-transform:scale(0);opacity:0}25%{-moz-transform:scale(0);opacity:.1}50%{-moz-transform:scale(.1);opacity:.3}75%{-moz-transform:scale(.5);opacity:.5}100%{-moz-transform:scale(1);opacity:0}}@keyframes introjspulse{0%{transform:scale(0);opacity:0}25%{transform:scale(0);opacity:.1}50%{transform:scale(.1);opacity:.3}75%{transform:scale(.5);opacity:.5}100%{transform:scale(1);opacity:0}}
 
resources/css/plugin.css CHANGED
@@ -374,11 +374,6 @@ label input[type=checkbox] {
374
  color: #008000 !important;
375
  line-height: 40px;
376
  }
377
- .bootstrap-select > button.dropdown-toggle,
378
- .bootstrap-select.show > button.dropdown-toggle {
379
- background-color: transparent;
380
- color: black !important;
381
- }
382
  .dropdown-item {
383
  font-size: 1.05rem;
384
  }
@@ -1447,12 +1442,6 @@ a.card_help {
1447
  .card .badge.badge-secondary a {
1448
  color: white;
1449
  }
1450
- .bootstrap-select.form-control {
1451
- border: 1px solid rgba(0, 0, 0, 0.4);
1452
- }
1453
- .bootstrap-select.form-control > button {
1454
- background-color: white;
1455
- }
1456
  .nav-pills a.nav-link.active {
1457
  background-color: #008000;
1458
  }
@@ -1493,11 +1482,11 @@ a.card_help {
1493
  #DashboardFeatureCards .card-title img {
1494
  vertical-align: text-bottom;
1495
  }
1496
- #Dashboard_ModSettingsSelect .bootstrap-select,
1497
  #Dashboard_ModSettingsSelect > select {
1498
  width: 100%;
1499
  }
1500
- #ModuleSettingsJump .bootstrap-select button {
1501
  padding: 0;
1502
  line-height: 15px;
1503
  background: transparent !important;
@@ -1505,16 +1494,16 @@ a.card_help {
1505
  outline: 0 none !important;
1506
  box-shadow: 0 0 transparent !important;
1507
  }
1508
- #ModuleSettingsJump .bootstrap-select button::after {
1509
  content: none !important;
1510
  }
1511
- #ModuleSettingsJump .bootstrap-select button .filter-option-inner-inner {
1512
  font-size: 13px;
1513
  line-height: 20px;
1514
  color: #008000 !important;
1515
  font-weight: 500;
1516
  }
1517
- #ModuleSettingsJump .bootstrap-select,
1518
  #ModuleSettingsJump select,
1519
  #ModuleSettingsJump select:focus,
1520
  #ModuleSettingsJump select:active {
@@ -1523,7 +1512,6 @@ a.card_help {
1523
  color: #008000;
1524
  vertical-align: inherit;
1525
  }
1526
-
1527
  #wpfooter {
1528
  display: none;
1529
  }
@@ -1538,7 +1526,6 @@ a.card_help {
1538
  word-break: keep-all;
1539
  white-space: nowrap;
1540
  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.2);
1541
-
1542
  position: absolute;
1543
  bottom: 0;
1544
  padding-left: 160px;
@@ -1548,20 +1535,24 @@ body.folded #FooterBannerGoPro {
1548
  padding-left: 40px;
1549
  }
1550
  @media (max-width: 1200px) {
1551
- #FooterBannerGoPro h6{
1552
  font-size: 14px;
1553
  letter-spacing: -1px;
1554
  }
1555
- #FooterBannerGoPro p{
 
1556
  font-size: 12px;
1557
  letter-spacing: -1px;
1558
  }
1559
  }
1560
  @media (max-width: 960px) {
1561
- #FooterBannerGoPro{
1562
  padding-left: 40px;
1563
  }
1564
  }
1565
  #odp-PageFoot {
1566
  height: 100px;
 
 
 
1567
  }
374
  color: #008000 !important;
375
  line-height: 40px;
376
  }
 
 
 
 
 
377
  .dropdown-item {
378
  font-size: 1.05rem;
379
  }
1442
  .card .badge.badge-secondary a {
1443
  color: white;
1444
  }
 
 
 
 
 
 
1445
  .nav-pills a.nav-link.active {
1446
  background-color: #008000;
1447
  }
1482
  #DashboardFeatureCards .card-title img {
1483
  vertical-align: text-bottom;
1484
  }
1485
+ #Dashboard_ModSettingsSelect .select2picker,
1486
  #Dashboard_ModSettingsSelect > select {
1487
  width: 100%;
1488
  }
1489
+ #ModuleSettingsJump .select2picker button {
1490
  padding: 0;
1491
  line-height: 15px;
1492
  background: transparent !important;
1494
  outline: 0 none !important;
1495
  box-shadow: 0 0 transparent !important;
1496
  }
1497
+ #ModuleSettingsJump .select2picker button::after {
1498
  content: none !important;
1499
  }
1500
+ #ModuleSettingsJump .select2picker button .filter-option-inner-inner {
1501
  font-size: 13px;
1502
  line-height: 20px;
1503
  color: #008000 !important;
1504
  font-weight: 500;
1505
  }
1506
+ #ModuleSettingsJump .select2picker,
1507
  #ModuleSettingsJump select,
1508
  #ModuleSettingsJump select:focus,
1509
  #ModuleSettingsJump select:active {
1512
  color: #008000;
1513
  vertical-align: inherit;
1514
  }
 
1515
  #wpfooter {
1516
  display: none;
1517
  }
1526
  word-break: keep-all;
1527
  white-space: nowrap;
1528
  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.2);
 
1529
  position: absolute;
1530
  bottom: 0;
1531
  padding-left: 160px;
1535
  padding-left: 40px;
1536
  }
1537
  @media (max-width: 1200px) {
1538
+ #FooterBannerGoPro h6 {
1539
  font-size: 14px;
1540
  letter-spacing: -1px;
1541
  }
1542
+
1543
+ #FooterBannerGoPro p {
1544
  font-size: 12px;
1545
  letter-spacing: -1px;
1546
  }
1547
  }
1548
  @media (max-width: 960px) {
1549
+ #FooterBannerGoPro {
1550
  padding-left: 40px;
1551
  }
1552
  }
1553
  #odp-PageFoot {
1554
  height: 100px;
1555
+ }
1556
+ .select2-selection {
1557
+ font-weight: normal;
1558
  }
resources/css/shield/charts.css ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 
2
+ .ct-line {
3
+ /* Control the thikness of your lines */
4
+ stroke-width: 2px !important;
5
+ }
6
+ .ct-point {
7
+ /* Size of your points */
8
+ stroke-width: 6px !important;
9
+ }
10
+
11
+ /*.ct-series-a .ct-line {*/
12
+ /* !* Set the colour of this series line *!*/
13
+ /* stroke: green;*/
14
+ /* !* Control the thikness of your lines *!*/
15
+ /* stroke-width: 2px;*/
16
+ /* !* Create a dashed line with a pattern *!*/
17
+ /* stroke-dasharray: 3px 1px;*/
18
+ /*}*/
19
+ /*!* This selector overrides the points style on line charts. Points on line charts are actually just very short strokes. This allows you to customize even the point size in CSS *!*/
20
+ /*.ct-series-a .ct-point {*/
21
+ /* !* Colour of your points *!*/
22
+ /* stroke: green;*/
23
+ /* !* Size of your points *!*/
24
+ /* stroke-width: 4px;*/
25
+ /* !* Make your points appear as squares *!*/
26
+ /* stroke-linecap: square;*/
27
+ /*}*/
resources/images/bootstrap/graph-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-graph-up" viewBox="0 0 16 16">
2
+ <path fill-rule="evenodd" d="M0 0h1v15h15v1H0V0zm10 3.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V4.9l-3.613 4.417a.5.5 0 0 1-.74.037L7.06 6.767l-3.656 5.027a.5.5 0 0 1-.808-.588l4-5.5a.5.5 0 0 1 .758-.06l2.609 2.61L13.445 4H10.5a.5.5 0 0 1-.5-.5z"/>
3
+ </svg>
resources/js/base64.min.js CHANGED
@@ -1 +0,0 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):function(){const r=e.Base64,o=t();o.noConflict=()=>(e.Base64=r,o),e.Meteor&&(Base64=o),e.Base64=o}()}("undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:this,(function(){"use strict";const e="3.6.0",t="function"==typeof atob,r="function"==typeof btoa,o="function"==typeof Buffer,n="function"==typeof TextDecoder?new TextDecoder:void 0,a="function"==typeof TextEncoder?new TextEncoder:void 0,f=[..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="],i=(e=>{let t={};return e.forEach(((e,r)=>t[e]=r)),t})(f),c=/^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/,u=String.fromCharCode.bind(String),s="function"==typeof Uint8Array.from?Uint8Array.from.bind(Uint8Array):(e,t=(e=>e))=>new Uint8Array(Array.prototype.slice.call(e,0).map(t)),d=e=>e.replace(/[+\/]/g,(e=>"+"==e?"-":"_")).replace(/=+$/m,""),l=e=>e.replace(/[^A-Za-z0-9\+\/]/g,""),h=e=>{let t,r,o,n,a="";const i=e.length%3;for(let i=0;i<e.length;){if((r=e.charCodeAt(i++))>255||(o=e.charCodeAt(i++))>255||(n=e.charCodeAt(i++))>255)throw new TypeError("invalid character found");t=r<<16|o<<8|n,a+=f[t>>18&63]+f[t>>12&63]+f[t>>6&63]+f[63&t]}return i?a.slice(0,i-3)+"===".substring(i):a},p=r?e=>btoa(e):o?e=>Buffer.from(e,"binary").toString("base64"):h,y=o?e=>Buffer.from(e).toString("base64"):e=>{let t=[];for(let r=0,o=e.length;r<o;r+=4096)t.push(u.apply(null,e.subarray(r,r+4096)));return p(t.join(""))},A=(e,t=!1)=>t?d(y(e)):y(e),b=e=>{if(e.length<2)return(t=e.charCodeAt(0))<128?e:t<2048?u(192|t>>>6)+u(128|63&t):u(224|t>>>12&15)+u(128|t>>>6&63)+u(128|63&t);var t=65536+1024*(e.charCodeAt(0)-55296)+(e.charCodeAt(1)-56320);return u(240|t>>>18&7)+u(128|t>>>12&63)+u(128|t>>>6&63)+u(128|63&t)},g=/[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g,B=e=>e.replace(g,b),x=o?e=>Buffer.from(e,"utf8").toString("base64"):a?e=>y(a.encode(e)):e=>p(B(e)),C=(e,t=!1)=>t?d(x(e)):x(e),m=e=>C(e,!0),U=/[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g,F=e=>{switch(e.length){case 4:var t=((7&e.charCodeAt(0))<<18|(63&e.charCodeAt(1))<<12|(63&e.charCodeAt(2))<<6|63&e.charCodeAt(3))-65536;return u(55296+(t>>>10))+u(56320+(1023&t));case 3:return u((15&e.charCodeAt(0))<<12|(63&e.charCodeAt(1))<<6|63&e.charCodeAt(2));default:return u((31&e.charCodeAt(0))<<6|63&e.charCodeAt(1))}},w=e=>e.replace(U,F),S=e=>{if(e=e.replace(/\s+/g,""),!c.test(e))throw new TypeError("malformed base64.");e+="==".slice(2-(3&e.length));let t,r,o,n="";for(let a=0;a<e.length;)t=i[e.charAt(a++)]<<18|i[e.charAt(a++)]<<12|(r=i[e.charAt(a++)])<<6|(o=i[e.charAt(a++)]),n+=64===r?u(t>>16&255):64===o?u(t>>16&255,t>>8&255):u(t>>16&255,t>>8&255,255&t);return n},E=t?e=>atob(l(e)):o?e=>Buffer.from(e,"base64").toString("binary"):S,v=o?e=>s(Buffer.from(e,"base64")):e=>s(E(e),(e=>e.charCodeAt(0))),D=e=>v(z(e)),R=o?e=>Buffer.from(e,"base64").toString("utf8"):n?e=>n.decode(v(e)):e=>w(E(e)),z=e=>l(e.replace(/[-_]/g,(e=>"-"==e?"+":"/"))),T=e=>R(z(e)),Z=e=>({value:e,enumerable:!1,writable:!0,configurable:!0}),j=function(){const e=(e,t)=>Object.defineProperty(String.prototype,e,Z(t));e("fromBase64",(function(){return T(this)})),e("toBase64",(function(e){return C(this,e)})),e("toBase64URI",(function(){return C(this,!0)})),e("toBase64URL",(function(){return C(this,!0)})),e("toUint8Array",(function(){return D(this)}))},I=function(){const e=(e,t)=>Object.defineProperty(Uint8Array.prototype,e,Z(t));e("toBase64",(function(e){return A(this,e)})),e("toBase64URI",(function(){return A(this,!0)})),e("toBase64URL",(function(){return A(this,!0)}))},O={version:e,VERSION:"3.6.0",atob:E,atobPolyfill:S,btoa:p,btoaPolyfill:h,fromBase64:T,toBase64:C,encode:C,encodeURI:m,encodeURL:m,utob:B,btou:w,decode:T,isValid:e=>{if("string"!=typeof e)return!1;const t=e.replace(/\s+/g,"").replace(/=+$/,"");return!/[^\s0-9a-zA-Z\+/]/.test(t)||!/[^\s0-9a-zA-Z\-_]/.test(t)},fromUint8Array:A,toUint8Array:D,extendString:j,extendUint8Array:I,extendBuiltins:()=>{j(),I()},Base64:{}};return Object.keys(O).forEach((e=>O.Base64[e]=O[e])),O}));
 
resources/js/bootstrap-select.min.js DELETED
@@ -1,9 +0,0 @@
1
- /*!
2
- * Bootstrap-select v1.13.18 (https://developer.snapappointments.com/bootstrap-select)
3
- *
4
- * Copyright 2012-2020 SnapAppointments, LLC
5
- * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
6
- */
7
-
8
- !function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){!function(P){"use strict";var d=["sanitize","whiteList","sanitizeFn"],r=["background","cite","href","itemtype","longdesc","poster","src","xlink:href"],e={"*":["class","dir","id","lang","role","tabindex","style",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},l=/^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi,a=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i;function v(e,t){var i=e.nodeName.toLowerCase();if(-1!==P.inArray(i,t))return-1===P.inArray(i,r)||Boolean(e.nodeValue.match(l)||e.nodeValue.match(a));for(var s=P(t).filter(function(e,t){return t instanceof RegExp}),n=0,o=s.length;n<o;n++)if(i.match(s[n]))return!0;return!1}function W(e,t,i){if(i&&"function"==typeof i)return i(e);for(var s=Object.keys(t),n=0,o=e.length;n<o;n++)for(var r=e[n].querySelectorAll("*"),l=0,a=r.length;l<a;l++){var c=r[l],d=c.nodeName.toLowerCase();if(-1!==s.indexOf(d))for(var h=[].slice.call(c.attributes),p=[].concat(t["*"]||[],t[d]||[]),u=0,f=h.length;u<f;u++){var m=h[u];v(m,p)||c.removeAttribute(m.nodeName)}else c.parentNode.removeChild(c)}}"classList"in document.createElement("_")||function(e){if("Element"in e){var t="classList",i="prototype",s=e.Element[i],n=Object,o=function(){var i=P(this);return{add:function(e){return e=Array.prototype.slice.call(arguments).join(" "),i.addClass(e)},remove:function(e){return e=Array.prototype.slice.call(arguments).join(" "),i.removeClass(e)},toggle:function(e,t){return i.toggleClass(e,t)},contains:function(e){return i.hasClass(e)}}};if(n.defineProperty){var r={get:o,enumerable:!0,configurable:!0};try{n.defineProperty(s,t,r)}catch(e){void 0!==e.number&&-2146823252!==e.number||(r.enumerable=!1,n.defineProperty(s,t,r))}}else n[i].__defineGetter__&&s.__defineGetter__(t,o)}}(window);var t,c,i=document.createElement("_");if(i.classList.add("c1","c2"),!i.classList.contains("c2")){var s=DOMTokenList.prototype.add,n=DOMTokenList.prototype.remove;DOMTokenList.prototype.add=function(){Array.prototype.forEach.call(arguments,s.bind(this))},DOMTokenList.prototype.remove=function(){Array.prototype.forEach.call(arguments,n.bind(this))}}if(i.classList.toggle("c3",!1),i.classList.contains("c3")){var o=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(e,t){return 1 in arguments&&!this.contains(e)==!t?t:o.call(this,e)}}function h(e){if(null==this)throw new TypeError;var t=String(this);if(e&&"[object RegExp]"==c.call(e))throw new TypeError;var i=t.length,s=String(e),n=s.length,o=1<arguments.length?arguments[1]:void 0,r=o?Number(o):0;r!=r&&(r=0);var l=Math.min(Math.max(r,0),i);if(i<n+l)return!1;for(var a=-1;++a<n;)if(t.charCodeAt(l+a)!=s.charCodeAt(a))return!1;return!0}function O(e,t){var i,s=e.selectedOptions,n=[];if(t){for(var o=0,r=s.length;o<r;o++)(i=s[o]).disabled||"OPTGROUP"===i.parentNode.tagName&&i.parentNode.disabled||n.push(i);return n}return s}function z(e,t){for(var i,s=[],n=t||e.selectedOptions,o=0,r=n.length;o<r;o++)(i=n[o]).disabled||"OPTGROUP"===i.parentNode.tagName&&i.parentNode.disabled||s.push(i.value);return e.multiple?s:s.length?s[0]:null}i=null,String.prototype.startsWith||(t=function(){try{var e={},t=Object.defineProperty,i=t(e,e,e)&&t}catch(e){}return i}(),c={}.toString,t?t(String.prototype,"startsWith",{value:h,configurable:!0,writable:!0}):String.prototype.startsWith=h),Object.keys||(Object.keys=function(e,t,i){for(t in i=[],e)i.hasOwnProperty.call(e,t)&&i.push(t);return i}),HTMLSelectElement&&!HTMLSelectElement.prototype.hasOwnProperty("selectedOptions")&&Object.defineProperty(HTMLSelectElement.prototype,"selectedOptions",{get:function(){return this.querySelectorAll(":checked")}});var p={useDefault:!1,_set:P.valHooks.select.set};P.valHooks.select.set=function(e,t){return t&&!p.useDefault&&P(e).data("selected",!0),p._set.apply(this,arguments)};var T=null,u=function(){try{return new Event("change"),!0}catch(e){return!1}}();function k(e,t,i,s){for(var n=["display","subtext","tokens"],o=!1,r=0;r<n.length;r++){var l=n[r],a=e[l];if(a&&(a=a.toString(),"display"===l&&(a=a.replace(/<[^>]+>/g,"")),s&&(a=w(a)),a=a.toUpperCase(),o="contains"===i?0<=a.indexOf(t):a.startsWith(t)))break}return o}function N(e){return parseInt(e,10)||0}P.fn.triggerNative=function(e){var t,i=this[0];i.dispatchEvent?(u?t=new Event(e,{bubbles:!0}):(t=document.createEvent("Event")).initEvent(e,!0,!1),i.dispatchEvent(t)):i.fireEvent?((t=document.createEventObject()).eventType=e,i.fireEvent("on"+e,t)):this.trigger(e)};var f={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C","\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g","\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i","\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O","\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S","\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w","\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe","\u0149":"'n","\u017f":"s"},m=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,g=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\u1ab0-\\u1aff\\u1dc0-\\u1dff]","g");function b(e){return f[e]}function w(e){return(e=e.toString())&&e.replace(m,b).replace(g,"")}var I,x,y,$,S=(I={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"},x="(?:"+Object.keys(I).join("|")+")",y=RegExp(x),$=RegExp(x,"g"),function(e){return e=null==e?"":""+e,y.test(e)?e.replace($,E):e});function E(e){return I[e]}var C={32:" ",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",65:"A",66:"B",67:"C",68:"D",69:"E",70:"F",71:"G",72:"H",73:"I",74:"J",75:"K",76:"L",77:"M",78:"N",79:"O",80:"P",81:"Q",82:"R",83:"S",84:"T",85:"U",86:"V",87:"W",88:"X",89:"Y",90:"Z",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"},A=27,L=13,D=32,H=9,B=38,R=40,M={success:!1,major:"3"};try{M.full=(P.fn.dropdown.Constructor.VERSION||"").split(" ")[0].split("."),M.major=M.full[0],M.success=!0}catch(e){}var U=0,j=".bs.select",V={DISABLED:"disabled",DIVIDER:"divider",SHOW:"open",DROPUP:"dropup",MENU:"dropdown-menu",MENURIGHT:"dropdown-menu-right",MENULEFT:"dropdown-menu-left",BUTTONCLASS:"btn-default",POPOVERHEADER:"popover-title",ICONBASE:"glyphicon",TICKICON:"glyphicon-ok"},F={MENU:"."+V.MENU},_={div:document.createElement("div"),span:document.createElement("span"),i:document.createElement("i"),subtext:document.createElement("small"),a:document.createElement("a"),li:document.createElement("li"),whitespace:document.createTextNode("\xa0"),fragment:document.createDocumentFragment()};_.noResults=_.li.cloneNode(!1),_.noResults.className="no-results",_.a.setAttribute("role","option"),_.a.className="dropdown-item",_.subtext.className="text-muted",_.text=_.span.cloneNode(!1),_.text.className="text",_.checkMark=_.span.cloneNode(!1);var G=new RegExp(B+"|"+R),q=new RegExp("^"+H+"$|"+A),K={li:function(e,t,i){var s=_.li.cloneNode(!1);return e&&(1===e.nodeType||11===e.nodeType?s.appendChild(e):s.innerHTML=e),void 0!==t&&""!==t&&(s.className=t),null!=i&&s.classList.add("optgroup-"+i),s},a:function(e,t,i){var s=_.a.cloneNode(!0);return e&&(11===e.nodeType?s.appendChild(e):s.insertAdjacentHTML("beforeend",e)),void 0!==t&&""!==t&&s.classList.add.apply(s.classList,t.split(/\s+/)),i&&s.setAttribute("style",i),s},text:function(e,t){var i,s,n=_.text.cloneNode(!1);if(e.content)n.innerHTML=e.content;else{if(n.textContent=e.text,e.icon){var o=_.whitespace.cloneNode(!1);(s=(!0===t?_.i:_.span).cloneNode(!1)).className=this.options.iconBase+" "+e.icon,_.fragment.appendChild(s),_.fragment.appendChild(o)}e.subtext&&((i=_.subtext.cloneNode(!1)).textContent=e.subtext,n.appendChild(i))}if(!0===t)for(;0<n.childNodes.length;)_.fragment.appendChild(n.childNodes[0]);else _.fragment.appendChild(n);return _.fragment},label:function(e){var t,i,s=_.text.cloneNode(!1);if(s.innerHTML=e.display,e.icon){var n=_.whitespace.cloneNode(!1);(i=_.span.cloneNode(!1)).className=this.options.iconBase+" "+e.icon,_.fragment.appendChild(i),_.fragment.appendChild(n)}return e.subtext&&((t=_.subtext.cloneNode(!1)).textContent=e.subtext,s.appendChild(t)),_.fragment.appendChild(s),_.fragment}};var Y=function(e,t){var i=this;p.useDefault||(P.valHooks.select.set=p._set,p.useDefault=!0),this.$element=P(e),this.$newElement=null,this.$button=null,this.$menu=null,this.options=t,this.selectpicker={main:{},search:{},current:{},view:{},isSearching:!1,keydown:{keyHistory:"",resetKeyHistory:{start:function(){return setTimeout(function(){i.selectpicker.keydown.keyHistory=""},800)}}}},this.sizeInfo={},null===this.options.title&&(this.options.title=this.$element.attr("title"));var s=this.options.windowPadding;"number"==typeof s&&(this.options.windowPadding=[s,s,s,s]),this.val=Y.prototype.val,this.render=Y.prototype.render,this.refresh=Y.prototype.refresh,this.setStyle=Y.prototype.setStyle,this.selectAll=Y.prototype.selectAll,this.deselectAll=Y.prototype.deselectAll,this.destroy=Y.prototype.destroy,this.remove=Y.prototype.remove,this.show=Y.prototype.show,this.hide=Y.prototype.hide,this.init()};function Z(e){var l,a=arguments,c=e;if([].shift.apply(a),!M.success){try{M.full=(P.fn.dropdown.Constructor.VERSION||"").split(" ")[0].split(".")}catch(e){Y.BootstrapVersion?M.full=Y.BootstrapVersion.split(" ")[0].split("."):(M.full=[M.major,"0","0"],console.warn("There was an issue retrieving Bootstrap's version. Ensure Bootstrap is being loaded before bootstrap-select and there is no namespace collision. If loading Bootstrap asynchronously, the version may need to be manually specified via $.fn.selectpicker.Constructor.BootstrapVersion.",e))}M.major=M.full[0],M.success=!0}if("4"===M.major){var t=[];Y.DEFAULTS.style===V.BUTTONCLASS&&t.push({name:"style",className:"BUTTONCLASS"}),Y.DEFAULTS.iconBase===V.ICONBASE&&t.push({name:"iconBase",className:"ICONBASE"}),Y.DEFAULTS.tickIcon===V.TICKICON&&t.push({name:"tickIcon",className:"TICKICON"}),V.DIVIDER="dropdown-divider",V.SHOW="show",V.BUTTONCLASS="btn-light",V.POPOVERHEADER="popover-header",V.ICONBASE="",V.TICKICON="bs-ok-default";for(var i=0;i<t.length;i++){e=t[i];Y.DEFAULTS[e.name]=V[e.className]}}var s=this.each(function(){var e=P(this);if(e.is("select")){var t=e.data("selectpicker"),i="object"==typeof c&&c;if(t){if(i)for(var s in i)Object.prototype.hasOwnProperty.call(i,s)&&(t.options[s]=i[s])}else{var n=e.data();for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&-1!==P.inArray(o,d)&&delete n[o];var r=P.extend({},Y.DEFAULTS,P.fn.selectpicker.defaults||{},n,i);r.template=P.extend({},Y.DEFAULTS.template,P.fn.selectpicker.defaults?P.fn.selectpicker.defaults.template:{},n.template,i.template),e.data("selectpicker",t=new Y(this,r))}"string"==typeof c&&(l=t[c]instanceof Function?t[c].apply(t,a):t.options[c])}});return void 0!==l?l:s}Y.VERSION="1.13.18",Y.DEFAULTS={noneSelectedText:"Nothing selected",noneResultsText:"No results matched {0}",countSelectedText:function(e,t){return 1==e?"{0} item selected":"{0} items selected"},maxOptionsText:function(e,t){return[1==e?"Limit reached ({n} item max)":"Limit reached ({n} items max)",1==t?"Group limit reached ({n} item max)":"Group limit reached ({n} items max)"]},selectAllText:"Select All",deselectAllText:"Deselect All",doneButton:!1,doneButtonText:"Close",multipleSeparator:", ",styleBase:"btn",style:V.BUTTONCLASS,size:"auto",title:null,selectedTextFormat:"values",width:!1,container:!1,hideDisabled:!1,showSubtext:!1,showIcon:!0,showContent:!0,dropupAuto:!0,header:!1,liveSearch:!1,liveSearchPlaceholder:null,liveSearchNormalize:!1,liveSearchStyle:"contains",actionsBox:!1,iconBase:V.ICONBASE,tickIcon:V.TICKICON,showTick:!1,template:{caret:'<span class="caret"></span>'},maxOptions:!1,mobile:!1,selectOnTab:!1,dropdownAlignRight:!1,windowPadding:0,virtualScroll:600,display:!1,sanitize:!0,sanitizeFn:null,whiteList:e},Y.prototype={constructor:Y,init:function(){var i=this,e=this.$element.attr("id"),t=this.$element[0],s=t.form;U++,this.selectId="bs-select-"+U,t.classList.add("bs-select-hidden"),this.multiple=this.$element.prop("multiple"),this.autofocus=this.$element.prop("autofocus"),t.classList.contains("show-tick")&&(this.options.showTick=!0),this.$newElement=this.createDropdown(),this.buildData(),this.$element.after(this.$newElement).prependTo(this.$newElement),s&&null===t.form&&(s.id||(s.id="form-"+this.selectId),t.setAttribute("form",s.id)),this.$button=this.$newElement.children("button"),this.$menu=this.$newElement.children(F.MENU),this.$menuInner=this.$menu.children(".inner"),this.$searchbox=this.$menu.find("input"),t.classList.remove("bs-select-hidden"),!0===this.options.dropdownAlignRight&&this.$menu[0].classList.add(V.MENURIGHT),void 0!==e&&this.$button.attr("data-id",e),this.checkDisabled(),this.clickListener(),this.options.liveSearch?(this.liveSearchListener(),this.focusedParent=this.$searchbox[0]):this.focusedParent=this.$menuInner[0],this.setStyle(),this.render(),this.setWidth(),this.options.container?this.selectPosition():this.$element.on("hide"+j,function(){if(i.isVirtual()){var e=i.$menuInner[0],t=e.firstChild.cloneNode(!1);e.replaceChild(t,e.firstChild),e.scrollTop=0}}),this.$menu.data("this",this),this.$newElement.data("this",this),this.options.mobile&&this.mobile(),this.$newElement.on({"hide.bs.dropdown":function(e){i.$element.trigger("hide"+j,e)},"hidden.bs.dropdown":function(e){i.$element.trigger("hidden"+j,e)},"show.bs.dropdown":function(e){i.$element.trigger("show"+j,e)},"shown.bs.dropdown":function(e){i.$element.trigger("shown"+j,e)}}),t.hasAttribute("required")&&this.$element.on("invalid"+j,function(){i.$button[0].classList.add("bs-invalid"),i.$element.on("shown"+j+".invalid",function(){i.$element.val(i.$element.val()).off("shown"+j+".invalid")}).on("rendered"+j,function(){this.validity.valid&&i.$button[0].classList.remove("bs-invalid"),i.$element.off("rendered"+j)}),i.$button.on("blur"+j,function(){i.$element.trigger("focus").trigger("blur"),i.$button.off("blur"+j)})}),setTimeout(function(){i.buildList(),i.$element.trigger("loaded"+j)})},createDropdown:function(){var e=this.multiple||this.options.showTick?" show-tick":"",t=this.multiple?' aria-multiselectable="true"':"",i="",s=this.autofocus?" autofocus":"";M.major<4&&this.$element.parent().hasClass("input-group")&&(i=" input-group-btn");var n,o="",r="",l="",a="";return this.options.header&&(o='<div class="'+V.POPOVERHEADER+'"><button type="button" class="close" aria-hidden="true">&times;</button>'+this.options.header+"</div>"),this.options.liveSearch&&(r='<div class="bs-searchbox"><input type="search" class="form-control" autocomplete="off"'+(null===this.options.liveSearchPlaceholder?"":' placeholder="'+S(this.options.liveSearchPlaceholder)+'"')+' role="combobox" aria-label="Search" aria-controls="'+this.selectId+'" aria-autocomplete="list"></div>'),this.multiple&&this.options.actionsBox&&(l='<div class="bs-actionsbox"><div class="btn-group btn-group-sm btn-block"><button type="button" class="actions-btn bs-select-all btn '+V.BUTTONCLASS+'">'+this.options.selectAllText+'</button><button type="button" class="actions-btn bs-deselect-all btn '+V.BUTTONCLASS+'">'+this.options.deselectAllText+"</button></div></div>"),this.multiple&&this.options.doneButton&&(a='<div class="bs-donebutton"><div class="btn-group btn-block"><button type="button" class="btn btn-sm '+V.BUTTONCLASS+'">'+this.options.doneButtonText+"</button></div></div>"),n='<div class="dropdown bootstrap-select'+e+i+'"><button type="button" tabindex="-1" class="'+this.options.styleBase+' dropdown-toggle" '+("static"===this.options.display?'data-display="static"':"")+'data-toggle="dropdown"'+s+' role="combobox" aria-owns="'+this.selectId+'" aria-haspopup="listbox" aria-expanded="false"><div class="filter-option"><div class="filter-option-inner"><div class="filter-option-inner-inner"></div></div> </div>'+("4"===M.major?"":'<span class="bs-caret">'+this.options.template.caret+"</span>")+'</button><div class="'+V.MENU+" "+("4"===M.major?"":V.SHOW)+'">'+o+r+l+'<div class="inner '+V.SHOW+'" role="listbox" id="'+this.selectId+'" tabindex="-1" '+t+'><ul class="'+V.MENU+" inner "+("4"===M.major?V.SHOW:"")+'" role="presentation"></ul></div>'+a+"</div></div>",P(n)},setPositionData:function(){this.selectpicker.view.canHighlight=[],this.selectpicker.view.size=0,this.selectpicker.view.firstHighlightIndex=!1;for(var e=0;e<this.selectpicker.current.data.length;e++){var t=this.selectpicker.current.data[e],i=!0;"divider"===t.type?(i=!1,t.height=this.sizeInfo.dividerHeight):"optgroup-label"===t.type?(i=!1,t.height=this.sizeInfo.dropdownHeaderHeight):t.height=this.sizeInfo.liHeight,t.disabled&&(i=!1),this.selectpicker.view.canHighlight.push(i),i&&(this.selectpicker.view.size++,t.posinset=this.selectpicker.view.size,!1===this.selectpicker.view.firstHighlightIndex&&(this.selectpicker.view.firstHighlightIndex=e)),t.position=(0===e?0:this.selectpicker.current.data[e-1].position)+t.height}},isVirtual:function(){return!1!==this.options.virtualScroll&&this.selectpicker.main.elements.length>=this.options.virtualScroll||!0===this.options.virtualScroll},createView:function(N,e,t){var A,L,D=this,i=0,H=[];if(this.selectpicker.isSearching=N,this.selectpicker.current=N?this.selectpicker.search:this.selectpicker.main,this.setPositionData(),e)if(t)i=this.$menuInner[0].scrollTop;else if(!D.multiple){var s=D.$element[0],n=(s.options[s.selectedIndex]||{}).liIndex;if("number"==typeof n&&!1!==D.options.size){var o=D.selectpicker.main.data[n],r=o&&o.position;r&&(i=r-(D.sizeInfo.menuInnerHeight+D.sizeInfo.liHeight)/2)}}function l(e,t){var i,s,n,o,r,l,a,c,d=D.selectpicker.current.elements.length,h=[],p=!0,u=D.isVirtual();D.selectpicker.view.scrollTop=e,i=Math.ceil(D.sizeInfo.menuInnerHeight/D.sizeInfo.liHeight*1.5),s=Math.round(d/i)||1;for(var f=0;f<s;f++){var m=(f+1)*i;if(f===s-1&&(m=d),h[f]=[f*i+(f?1:0),m],!d)break;void 0===r&&e-1<=D.selectpicker.current.data[m-1].position-D.sizeInfo.menuInnerHeight&&(r=f)}if(void 0===r&&(r=0),l=[D.selectpicker.view.position0,D.selectpicker.view.position1],n=Math.max(0,r-1),o=Math.min(s-1,r+1),D.selectpicker.view.position0=!1===u?0:Math.max(0,h[n][0])||0,D.selectpicker.view.position1=!1===u?d:Math.min(d,h[o][1])||0,a=l[0]!==D.selectpicker.view.position0||l[1]!==D.selectpicker.view.position1,void 0!==D.activeIndex&&(L=D.selectpicker.main.elements[D.prevActiveIndex],H=D.selectpicker.main.elements[D.activeIndex],A=D.selectpicker.main.elements[D.selectedIndex],t&&(D.activeIndex!==D.selectedIndex&&D.defocusItem(H),D.activeIndex=void 0),D.activeIndex&&D.activeIndex!==D.selectedIndex&&D.defocusItem(A)),void 0!==D.prevActiveIndex&&D.prevActiveIndex!==D.activeIndex&&D.prevActiveIndex!==D.selectedIndex&&D.defocusItem(L),(t||a)&&(c=D.selectpicker.view.visibleElements?D.selectpicker.view.visibleElements.slice():[],D.selectpicker.view.visibleElements=!1===u?D.selectpicker.current.elements:D.selectpicker.current.elements.slice(D.selectpicker.view.position0,D.selectpicker.view.position1),D.setOptionStatus(),(N||!1===u&&t)&&(p=!function(e,i){return e.length===i.length&&e.every(function(e,t){return e===i[t]})}(c,D.selectpicker.view.visibleElements)),(t||!0===u)&&p)){var v,g,b=D.$menuInner[0],w=document.createDocumentFragment(),I=b.firstChild.cloneNode(!1),x=D.selectpicker.view.visibleElements,k=[];b.replaceChild(I,b.firstChild);f=0;for(var y=x.length;f<y;f++){var $,S,E=x[f];D.options.sanitize&&($=E.lastChild)&&(S=D.selectpicker.current.data[f+D.selectpicker.view.position0])&&S.content&&!S.sanitized&&(k.push($),S.sanitized=!0),w.appendChild(E)}if(D.options.sanitize&&k.length&&W(k,D.options.whiteList,D.options.sanitizeFn),!0===u?(v=0===D.selectpicker.view.position0?0:D.selectpicker.current.data[D.selectpicker.view.position0-1].position,g=D.selectpicker.view.position1>d-1?0:D.selectpicker.current.data[d-1].position-D.selectpicker.current.data[D.selectpicker.view.position1-1].position,b.firstChild.style.marginTop=v+"px",b.firstChild.style.marginBottom=g+"px"):(b.firstChild.style.marginTop=0,b.firstChild.style.marginBottom=0),b.firstChild.appendChild(w),!0===u&&D.sizeInfo.hasScrollBar){var C=b.firstChild.offsetWidth;if(t&&C<D.sizeInfo.menuInnerInnerWidth&&D.sizeInfo.totalMenuWidth>D.sizeInfo.selectWidth)b.firstChild.style.minWidth=D.sizeInfo.menuInnerInnerWidth+"px";else if(C>D.sizeInfo.menuInnerInnerWidth){D.$menu[0].style.minWidth=0;var O=b.firstChild.offsetWidth;O>D.sizeInfo.menuInnerInnerWidth&&(D.sizeInfo.menuInnerInnerWidth=O,b.firstChild.style.minWidth=D.sizeInfo.menuInnerInnerWidth+"px"),D.$menu[0].style.minWidth=""}}}if(D.prevActiveIndex=D.activeIndex,D.options.liveSearch){if(N&&t){var z,T=0;D.selectpicker.view.canHighlight[T]||(T=1+D.selectpicker.view.canHighlight.slice(1).indexOf(!0)),z=D.selectpicker.view.visibleElements[T],D.defocusItem(D.selectpicker.view.currentActive),D.activeIndex=(D.selectpicker.current.data[T]||{}).index,D.focusItem(z)}}else D.$menuInner.trigger("focus")}l(i,!0),this.$menuInner.off("scroll.createView").on("scroll.createView",function(e,t){D.noScroll||l(this.scrollTop,t),D.noScroll=!1}),P(window).off("resize"+j+"."+this.selectId+".createView").on("resize"+j+"."+this.selectId+".createView",function(){D.$newElement.hasClass(V.SHOW)&&l(D.$menuInner[0].scrollTop)})},focusItem:function(e,t,i){if(e){t=t||this.selectpicker.main.data[this.activeIndex];var s=e.firstChild;s&&(s.setAttribute("aria-setsize",this.selectpicker.view.size),s.setAttribute("aria-posinset",t.posinset),!0!==i&&(this.focusedParent.setAttribute("aria-activedescendant",s.id),e.classList.add("active"),s.classList.add("active")))}},defocusItem:function(e){e&&(e.classList.remove("active"),e.firstChild&&e.firstChild.classList.remove("active"))},setPlaceholder:function(){var e=this,t=!1;if(this.options.title&&!this.multiple){this.selectpicker.view.titleOption||(this.selectpicker.view.titleOption=document.createElement("option")),t=!0;var i=this.$element[0],s=!1,n=!this.selectpicker.view.titleOption.parentNode,o=i.selectedIndex,r=i.options[o],l=window.performance&&window.performance.getEntriesByType("navigation"),a=l&&l.length?"back_forward"!==l[0].type:2!==window.performance.navigation.type;n&&(this.selectpicker.view.titleOption.className="bs-title-option",this.selectpicker.view.titleOption.value="",s=!r||0===o&&!1===r.defaultSelected&&void 0===this.$element.data("selected")),!n&&0===this.selectpicker.view.titleOption.index||i.insertBefore(this.selectpicker.view.titleOption,i.firstChild),s&&a?i.selectedIndex=0:"complete"!==document.readyState&&window.addEventListener("pageshow",function(){e.selectpicker.view.displayedValue!==i.value&&e.render()})}return t},buildData:function(){var p=':not([hidden]):not([data-hidden="true"])',u=[],f=0,m=this.setPlaceholder()?1:0;this.options.hideDisabled&&(p+=":not(:disabled)");var e=this.$element[0].querySelectorAll("select > *"+p);function v(e){var t=u[u.length-1];t&&"divider"===t.type&&(t.optID||e.optID)||((e=e||{}).type="divider",u.push(e))}function g(e,t){if((t=t||{}).divider="true"===e.getAttribute("data-divider"),t.divider)v({optID:t.optID});else{var i=u.length,s=e.style.cssText,n=s?S(s):"",o=(e.className||"")+(t.optgroupClass||"");t.optID&&(o="opt "+o),t.optionClass=o.trim(),t.inlineStyle=n,t.text=e.textContent,t.content=e.getAttribute("data-content"),t.tokens=e.getAttribute("data-tokens"),t.subtext=e.getAttribute("data-subtext"),t.icon=e.getAttribute("data-icon"),e.liIndex=i,t.display=t.content||t.text,t.type="option",t.index=i,t.option=e,t.selected=!!e.selected,t.disabled=t.disabled||!!e.disabled,u.push(t)}}function t(e,t){var i=t[e],s=!(e-1<m)&&t[e-1],n=t[e+1],o=i.querySelectorAll("option"+p);if(o.length){var r,l,a={display:S(i.label),subtext:i.getAttribute("data-subtext"),icon:i.getAttribute("data-icon"),type:"optgroup-label",optgroupClass:" "+(i.className||"")};f++,s&&v({optID:f}),a.optID=f,u.push(a);for(var c=0,d=o.length;c<d;c++){var h=o[c];0===c&&(l=(r=u.length-1)+d),g(h,{headerIndex:r,lastIndex:l,optID:a.optID,optgroupClass:a.optgroupClass,disabled:i.disabled})}n&&v({optID:f})}}for(var i=e.length,s=m;s<i;s++){var n=e[s];"OPTGROUP"!==n.tagName?g(n,{}):t(s,e)}this.selectpicker.main.data=this.selectpicker.current.data=u},buildList:function(){var s=this,e=this.selectpicker.main.data,n=[],o=0;function t(e){var t,i=0;switch(e.type){case"divider":t=K.li(!1,V.DIVIDER,e.optID?e.optID+"div":void 0);break;case"option":(t=K.li(K.a(K.text.call(s,e),e.optionClass,e.inlineStyle),"",e.optID)).firstChild&&(t.firstChild.id=s.selectId+"-"+e.index);break;case"optgroup-label":t=K.li(K.label.call(s,e),"dropdown-header"+e.optgroupClass,e.optID)}e.element=t,n.push(t),e.display&&(i+=e.display.length),e.subtext&&(i+=e.subtext.length),e.icon&&(i+=1),o<i&&(o=i,s.selectpicker.view.widestOption=n[n.length-1])}!s.options.showTick&&!s.multiple||_.checkMark.parentNode||(_.checkMark.className=this.options.iconBase+" "+s.options.tickIcon+" check-mark",_.a.appendChild(_.checkMark));for(var i=e.length,r=0;r<i;r++){t(e[r])}this.selectpicker.main.elements=this.selectpicker.current.elements=n},findLis:function(){return this.$menuInner.find(".inner > li")},render:function(){var e,t=this,i=this.$element[0],s=this.setPlaceholder()&&0===i.selectedIndex,n=O(i,this.options.hideDisabled),o=n.length,r=this.$button[0],l=r.querySelector(".filter-option-inner-inner"),a=document.createTextNode(this.options.multipleSeparator),c=_.fragment.cloneNode(!1),d=!1;if(r.classList.toggle("bs-placeholder",t.multiple?!o:!z(i,n)),t.multiple||1!==n.length||(t.selectpicker.view.displayedValue=z(i,n)),"static"===this.options.selectedTextFormat)c=K.text.call(this,{text:this.options.title},!0);else if(!1===(this.multiple&&-1!==this.options.selectedTextFormat.indexOf("count")&&1<o&&(1<(e=this.options.selectedTextFormat.split(">")).length&&o>e[1]||1===e.length&&2<=o))){if(!s){for(var h=0;h<o&&h<50;h++){var p=n[h],u=this.selectpicker.main.data[p.liIndex],f={};this.multiple&&0<h&&c.appendChild(a.cloneNode(!1)),p.title?f.text=p.title:u&&(u.content&&t.options.showContent?(f.content=u.content.toString(),d=!0):(t.options.showIcon&&(f.icon=u.icon),t.options.showSubtext&&!t.multiple&&u.subtext&&(f.subtext=" "+u.subtext),f.text=p.textContent.trim())),c.appendChild(K.text.call(this,f,!0))}49<o&&c.appendChild(document.createTextNode("..."))}}else{var m=':not([hidden]):not([data-hidden="true"]):not([data-divider="true"])';this.options.hideDisabled&&(m+=":not(:disabled)");var v=this.$element[0].querySelectorAll("select > option"+m+", optgroup"+m+" option"+m).length,g="function"==typeof this.options.countSelectedText?this.options.countSelectedText(o,v):this.options.countSelectedText;c=K.text.call(this,{text:g.replace("{0}",o.toString()).replace("{1}",v.toString())},!0)}if(null==this.options.title&&(this.options.title=this.$element.attr("title")),c.childNodes.length||(c=K.text.call(this,{text:void 0!==this.options.title?this.options.title:this.options.noneSelectedText},!0)),r.title=c.textContent.replace(/<[^>]*>?/g,"").trim(),this.options.sanitize&&d&&W([c],t.options.whiteList,t.options.sanitizeFn),l.innerHTML="",l.appendChild(c),M.major<4&&this.$newElement[0].classList.contains("bs3-has-addon")){var b=r.querySelector(".filter-expand"),w=l.cloneNode(!0);w.className="filter-expand",b?r.replaceChild(w,b):r.appendChild(w)}this.$element.trigger("rendered"+j)},setStyle:function(e,t){var i,s=this.$button[0],n=this.$newElement[0],o=this.options.style.trim();this.$element.attr("class")&&this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi,"")),M.major<4&&(n.classList.add("bs3"),n.parentNode.classList&&n.parentNode.classList.contains("input-group")&&(n.previousElementSibling||n.nextElementSibling)&&(n.previousElementSibling||n.nextElementSibling).classList.contains("input-group-addon")&&n.classList.add("bs3-has-addon")),i=e?e.trim():o,"add"==t?i&&s.classList.add.apply(s.classList,i.split(" ")):"remove"==t?i&&s.classList.remove.apply(s.classList,i.split(" ")):(o&&s.classList.remove.apply(s.classList,o.split(" ")),i&&s.classList.add.apply(s.classList,i.split(" ")))},liHeight:function(e){if(e||!1!==this.options.size&&!Object.keys(this.sizeInfo).length){var t,i=_.div.cloneNode(!1),s=_.div.cloneNode(!1),n=_.div.cloneNode(!1),o=document.createElement("ul"),r=_.li.cloneNode(!1),l=_.li.cloneNode(!1),a=_.a.cloneNode(!1),c=_.span.cloneNode(!1),d=this.options.header&&0<this.$menu.find("."+V.POPOVERHEADER).length?this.$menu.find("."+V.POPOVERHEADER)[0].cloneNode(!0):null,h=this.options.liveSearch?_.div.cloneNode(!1):null,p=this.options.actionsBox&&this.multiple&&0<this.$menu.find(".bs-actionsbox").length?this.$menu.find(".bs-actionsbox")[0].cloneNode(!0):null,u=this.options.doneButton&&this.multiple&&0<this.$menu.find(".bs-donebutton").length?this.$menu.find(".bs-donebutton")[0].cloneNode(!0):null,f=this.$element.find("option")[0];if(this.sizeInfo.selectWidth=this.$newElement[0].offsetWidth,c.className="text",a.className="dropdown-item "+(f?f.className:""),i.className=this.$menu[0].parentNode.className+" "+V.SHOW,i.style.width=0,"auto"===this.options.width&&(s.style.minWidth=0),s.className=V.MENU+" "+V.SHOW,n.className="inner "+V.SHOW,o.className=V.MENU+" inner "+("4"===M.major?V.SHOW:""),r.className=V.DIVIDER,l.className="dropdown-header",c.appendChild(document.createTextNode("\u200b")),this.selectpicker.current.data.length)for(var m=0;m<this.selectpicker.current.data.length;m++){var v=this.selectpicker.current.data[m];if("option"===v.type){t=v.element;break}}else t=_.li.cloneNode(!1),a.appendChild(c),t.appendChild(a);if(l.appendChild(c.cloneNode(!0)),this.selectpicker.view.widestOption&&o.appendChild(this.selectpicker.view.widestOption.cloneNode(!0)),o.appendChild(t),o.appendChild(r),o.appendChild(l),d&&s.appendChild(d),h){var g=document.createElement("input");h.className="bs-searchbox",g.className="form-control",h.appendChild(g),s.appendChild(h)}p&&s.appendChild(p),n.appendChild(o),s.appendChild(n),u&&s.appendChild(u),i.appendChild(s),document.body.appendChild(i);var b,w=t.offsetHeight,I=l?l.offsetHeight:0,x=d?d.offsetHeight:0,k=h?h.offsetHeight:0,y=p?p.offsetHeight:0,$=u?u.offsetHeight:0,S=P(r).outerHeight(!0),E=!!window.getComputedStyle&&window.getComputedStyle(s),C=s.offsetWidth,O=E?null:P(s),z={vert:N(E?E.paddingTop:O.css("paddingTop"))+N(E?E.paddingBottom:O.css("paddingBottom"))+N(E?E.borderTopWidth:O.css("borderTopWidth"))+N(E?E.borderBottomWidth:O.css("borderBottomWidth")),horiz:N(E?E.paddingLeft:O.css("paddingLeft"))+N(E?E.paddingRight:O.css("paddingRight"))+N(E?E.borderLeftWidth:O.css("borderLeftWidth"))+N(E?E.borderRightWidth:O.css("borderRightWidth"))},T={vert:z.vert+N(E?E.marginTop:O.css("marginTop"))+N(E?E.marginBottom:O.css("marginBottom"))+2,horiz:z.horiz+N(E?E.marginLeft:O.css("marginLeft"))+N(E?E.marginRight:O.css("marginRight"))+2};n.style.overflowY="scroll",b=s.offsetWidth-C,document.body.removeChild(i),this.sizeInfo.liHeight=w,this.sizeInfo.dropdownHeaderHeight=I,this.sizeInfo.headerHeight=x,this.sizeInfo.searchHeight=k,this.sizeInfo.actionsHeight=y,this.sizeInfo.doneButtonHeight=$,this.sizeInfo.dividerHeight=S,this.sizeInfo.menuPadding=z,this.sizeInfo.menuExtras=T,this.sizeInfo.menuWidth=C,this.sizeInfo.menuInnerInnerWidth=C-z.horiz,this.sizeInfo.totalMenuWidth=this.sizeInfo.menuWidth,this.sizeInfo.scrollBarWidth=b,this.sizeInfo.selectHeight=this.$newElement[0].offsetHeight,this.setPositionData()}},getSelectPosition:function(){var e,t=P(window),i=this.$newElement.offset(),s=P(this.options.container);this.options.container&&s.length&&!s.is("body")?((e=s.offset()).top+=parseInt(s.css("borderTopWidth")),e.left+=parseInt(s.css("borderLeftWidth"))):e={top:0,left:0};var n=this.options.windowPadding;this.sizeInfo.selectOffsetTop=i.top-e.top-t.scrollTop(),this.sizeInfo.selectOffsetBot=t.height()-this.sizeInfo.selectOffsetTop-this.sizeInfo.selectHeight-e.top-n[2],this.sizeInfo.selectOffsetLeft=i.left-e.left-t.scrollLeft(),this.sizeInfo.selectOffsetRight=t.width()-this.sizeInfo.selectOffsetLeft-this.sizeInfo.selectWidth-e.left-n[1],this.sizeInfo.selectOffsetTop-=n[0],this.sizeInfo.selectOffsetLeft-=n[3]},setMenuSize:function(e){this.getSelectPosition();var t,i,s,n,o,r,l,a,c=this.sizeInfo.selectWidth,d=this.sizeInfo.liHeight,h=this.sizeInfo.headerHeight,p=this.sizeInfo.searchHeight,u=this.sizeInfo.actionsHeight,f=this.sizeInfo.doneButtonHeight,m=this.sizeInfo.dividerHeight,v=this.sizeInfo.menuPadding,g=0;if(this.options.dropupAuto&&(l=d*this.selectpicker.current.elements.length+v.vert,a=this.sizeInfo.selectOffsetTop-this.sizeInfo.selectOffsetBot>this.sizeInfo.menuExtras.vert&&l+this.sizeInfo.menuExtras.vert+50>this.sizeInfo.selectOffsetBot,!0===this.selectpicker.isSearching&&(a=this.selectpicker.dropup),this.$newElement.toggleClass(V.DROPUP,a),this.selectpicker.dropup=a),"auto"===this.options.size)n=3<this.selectpicker.current.elements.length?3*this.sizeInfo.liHeight+this.sizeInfo.menuExtras.vert-2:0,i=this.sizeInfo.selectOffsetBot-this.sizeInfo.menuExtras.vert,s=n+h+p+u+f,r=Math.max(n-v.vert,0),this.$newElement.hasClass(V.DROPUP)&&(i=this.sizeInfo.selectOffsetTop-this.sizeInfo.menuExtras.vert),t=(o=i)-h-p-u-f-v.vert;else if(this.options.size&&"auto"!=this.options.size&&this.selectpicker.current.elements.length>this.options.size){for(var b=0;b<this.options.size;b++)"divider"===this.selectpicker.current.data[b].type&&g++;t=(i=d*this.options.size+g*m+v.vert)-v.vert,o=i+h+p+u+f,s=r=""}this.$menu.css({"max-height":o+"px",overflow:"hidden","min-height":s+"px"}),this.$menuInner.css({"max-height":t+"px","overflow-y":"auto","min-height":r+"px"}),this.sizeInfo.menuInnerHeight=Math.max(t,1),this.selectpicker.current.data.length&&this.selectpicker.current.data[this.selectpicker.current.data.length-1].position>this.sizeInfo.menuInnerHeight&&(this.sizeInfo.hasScrollBar=!0,this.sizeInfo.totalMenuWidth=this.sizeInfo.menuWidth+this.sizeInfo.scrollBarWidth),"auto"===this.options.dropdownAlignRight&&this.$menu.toggleClass(V.MENURIGHT,this.sizeInfo.selectOffsetLeft>this.sizeInfo.selectOffsetRight&&this.sizeInfo.selectOffsetRight<this.sizeInfo.totalMenuWidth-c),this.dropdown&&this.dropdown._popper&&this.dropdown._popper.update()},setSize:function(e){if(this.liHeight(e),this.options.header&&this.$menu.css("padding-top",0),!1!==this.options.size){var t=this,i=P(window);this.setMenuSize(),this.options.liveSearch&&this.$searchbox.off("input.setMenuSize propertychange.setMenuSize").on("input.setMenuSize propertychange.setMenuSize",function(){return t.setMenuSize()}),"auto"===this.options.size?i.off("resize"+j+"."+this.selectId+".setMenuSize scroll"+j+"."+this.selectId+".setMenuSize").on("resize"+j+"."+this.selectId+".setMenuSize scroll"+j+"."+this.selectId+".setMenuSize",function(){return t.setMenuSize()}):this.options.size&&"auto"!=this.options.size&&this.selectpicker.current.elements.length>this.options.size&&i.off("resize"+j+"."+this.selectId+".setMenuSize scroll"+j+"."+this.selectId+".setMenuSize")}this.createView(!1,!0,e)},setWidth:function(){var i=this;"auto"===this.options.width?requestAnimationFrame(function(){i.$menu.css("min-width","0"),i.$element.on("loaded"+j,function(){i.liHeight(),i.setMenuSize();var e=i.$newElement.clone().appendTo("body"),t=e.css("width","auto").children("button").outerWidth();e.remove(),i.sizeInfo.selectWidth=Math.max(i.sizeInfo.totalMenuWidth,t),i.$newElement.css("width",i.sizeInfo.selectWidth+"px")})}):"fit"===this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width","").addClass("fit-width")):this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width",this.options.width)):(this.$menu.css("min-width",""),this.$newElement.css("width","")),this.$newElement.hasClass("fit-width")&&"fit"!==this.options.width&&this.$newElement[0].classList.remove("fit-width")},selectPosition:function(){this.$bsContainer=P('<div class="bs-container" />');function e(e){var t={},i=r.options.display||!!P.fn.dropdown.Constructor.Default&&P.fn.dropdown.Constructor.Default.display;r.$bsContainer.addClass(e.attr("class").replace(/form-control|fit-width/gi,"")).toggleClass(V.DROPUP,e.hasClass(V.DROPUP)),s=e.offset(),l.is("body")?n={top:0,left:0}:((n=l.offset()).top+=parseInt(l.css("borderTopWidth"))-l.scrollTop(),n.left+=parseInt(l.css("borderLeftWidth"))-l.scrollLeft()),o=e.hasClass(V.DROPUP)?0:e[0].offsetHeight,(M.major<4||"static"===i)&&(t.top=s.top-n.top+o,t.left=s.left-n.left),t.width=e[0].offsetWidth,r.$bsContainer.css(t)}var s,n,o,r=this,l=P(this.options.container);this.$button.on("click.bs.dropdown.data-api",function(){r.isDisabled()||(e(r.$newElement),r.$bsContainer.appendTo(r.options.container).toggleClass(V.SHOW,!r.$button.hasClass(V.SHOW)).append(r.$menu))}),P(window).off("resize"+j+"."+this.selectId+" scroll"+j+"."+this.selectId).on("resize"+j+"."+this.selectId+" scroll"+j+"."+this.selectId,function(){r.$newElement.hasClass(V.SHOW)&&e(r.$newElement)}),this.$element.on("hide"+j,function(){r.$menu.data("height",r.$menu.height()),r.$bsContainer.detach()})},setOptionStatus:function(e){var t=this;if(t.noScroll=!1,t.selectpicker.view.visibleElements&&t.selectpicker.view.visibleElements.length)for(var i=0;i<t.selectpicker.view.visibleElements.length;i++){var s=t.selectpicker.current.data[i+t.selectpicker.view.position0],n=s.option;n&&(!0!==e&&t.setDisabled(s.index,s.disabled),t.setSelected(s.index,n.selected))}},setSelected:function(e,t){var i,s,n=this.selectpicker.main.elements[e],o=this.selectpicker.main.data[e],r=void 0!==this.activeIndex,l=this.activeIndex===e||t&&!this.multiple&&!r;o.selected=t,s=n.firstChild,t&&(this.selectedIndex=e),n.classList.toggle("selected",t),l?(this.focusItem(n,o),this.selectpicker.view.currentActive=n,this.activeIndex=e):this.defocusItem(n),s&&(s.classList.toggle("selected",t),t?s.setAttribute("aria-selected",!0):this.multiple?s.setAttribute("aria-selected",!1):s.removeAttribute("aria-selected")),l||r||!t||void 0===this.prevActiveIndex||(i=this.selectpicker.main.elements[this.prevActiveIndex],this.defocusItem(i))},setDisabled:function(e,t){var i,s=this.selectpicker.main.elements[e];this.selectpicker.main.data[e].disabled=t,i=s.firstChild,s.classList.toggle(V.DISABLED,t),i&&("4"===M.major&&i.classList.toggle(V.DISABLED,t),t?(i.setAttribute("aria-disabled",t),i.setAttribute("tabindex",-1)):(i.removeAttribute("aria-disabled"),i.setAttribute("tabindex",0)))},isDisabled:function(){return this.$element[0].disabled},checkDisabled:function(){this.isDisabled()?(this.$newElement[0].classList.add(V.DISABLED),this.$button.addClass(V.DISABLED).attr("aria-disabled",!0)):this.$button[0].classList.contains(V.DISABLED)&&(this.$newElement[0].classList.remove(V.DISABLED),this.$button.removeClass(V.DISABLED).attr("aria-disabled",!1))},clickListener:function(){var C=this,t=P(document);function e(){C.options.liveSearch?C.$searchbox.trigger("focus"):C.$menuInner.trigger("focus")}function i(){C.dropdown&&C.dropdown._popper&&C.dropdown._popper.state.isCreated?e():requestAnimationFrame(i)}t.data("spaceSelect",!1),this.$button.on("keyup",function(e){/(32)/.test(e.keyCode.toString(10))&&t.data("spaceSelect")&&(e.preventDefault(),t.data("spaceSelect",!1))}),this.$newElement.on("show.bs.dropdown",function(){3<M.major&&!C.dropdown&&(C.dropdown=C.$button.data("bs.dropdown"),C.dropdown._menu=C.$menu[0])}),this.$button.on("click.bs.dropdown.data-api",function(){C.$newElement.hasClass(V.SHOW)||C.setSize()}),this.$element.on("shown"+j,function(){C.$menuInner[0].scrollTop!==C.selectpicker.view.scrollTop&&(C.$menuInner[0].scrollTop=C.selectpicker.view.scrollTop),3<M.major?requestAnimationFrame(i):e()}),this.$menuInner.on("mouseenter","li a",function(e){var t=this.parentElement,i=C.isVirtual()?C.selectpicker.view.position0:0,s=Array.prototype.indexOf.call(t.parentElement.children,t),n=C.selectpicker.current.data[s+i];C.focusItem(t,n,!0)}),this.$menuInner.on("click","li a",function(e,t){var i=P(this),s=C.$element[0],n=C.isVirtual()?C.selectpicker.view.position0:0,o=C.selectpicker.current.data[i.parent().index()+n],r=o.index,l=z(s),a=s.selectedIndex,c=s.options[a],d=!0;if(C.multiple&&1!==C.options.maxOptions&&e.stopPropagation(),e.preventDefault(),!C.isDisabled()&&!i.parent().hasClass(V.DISABLED)){var h=o.option,p=P(h),u=h.selected,f=p.parent("optgroup"),m=f.find("option"),v=C.options.maxOptions,g=f.data("maxOptions")||!1;if(r===C.activeIndex&&(t=!0),t||(C.prevActiveIndex=C.activeIndex,C.activeIndex=void 0),C.multiple){if(h.selected=!u,C.setSelected(r,!u),C.focusedParent.focus(),!1!==v||!1!==g){var b=v<O(s).length,w=g<f.find("option:selected").length;if(v&&b||g&&w)if(v&&1==v)s.selectedIndex=-1,h.selected=!0,C.setOptionStatus(!0);else if(g&&1==g){for(var I=0;I<m.length;I++){var x=m[I];x.selected=!1,C.setSelected(x.liIndex,!1)}h.selected=!0,C.setSelected(r,!0)}else{var k="string"==typeof C.options.maxOptionsText?[C.options.maxOptionsText,C.options.maxOptionsText]:C.options.maxOptionsText,y="function"==typeof k?k(v,g):k,$=y[0].replace("{n}",v),S=y[1].replace("{n}",g),E=P('<div class="notify"></div>');y[2]&&($=$.replace("{var}",y[2][1<v?0:1]),S=S.replace("{var}",y[2][1<g?0:1])),h.selected=!1,C.$menu.append(E),v&&b&&(E.append(P("<div>"+$+"</div>")),d=!1,C.$element.trigger("maxReached"+j)),g&&w&&(E.append(P("<div>"+S+"</div>")),d=!1,C.$element.trigger("maxReachedGrp"+j)),setTimeout(function(){C.setSelected(r,!1)},10),E[0].classList.add("fadeOut"),setTimeout(function(){E.remove()},1050)}}}else c&&(c.selected=!1),h.selected=!0,C.setSelected(r,!0);!C.multiple||C.multiple&&1===C.options.maxOptions?C.$button.trigger("focus"):C.options.liveSearch&&C.$searchbox.trigger("focus"),d&&(!C.multiple&&a===s.selectedIndex||(T=[h.index,p.prop("selected"),l],C.$element.triggerNative("change")))}}),this.$menu.on("click","li."+V.DISABLED+" a, ."+V.POPOVERHEADER+", ."+V.POPOVERHEADER+" :not(.close)",function(e){e.currentTarget==this&&(e.preventDefault(),e.stopPropagation(),C.options.liveSearch&&!P(e.target).hasClass("close")?C.$searchbox.trigger("focus"):C.$button.trigger("focus"))}),this.$menuInner.on("click",".divider, .dropdown-header",function(e){e.preventDefault(),e.stopPropagation(),C.options.liveSearch?C.$searchbox.trigger("focus"):C.$button.trigger("focus")}),this.$menu.on("click","."+V.POPOVERHEADER+" .close",function(){C.$button.trigger("click")}),this.$searchbox.on("click",function(e){e.stopPropagation()}),this.$menu.on("click",".actions-btn",function(e){C.options.liveSearch?C.$searchbox.trigger("focus"):C.$button.trigger("focus"),e.preventDefault(),e.stopPropagation(),P(this).hasClass("bs-select-all")?C.selectAll():C.deselectAll()}),this.$button.on("focus"+j,function(e){var t=C.$element[0].getAttribute("tabindex");void 0!==t&&e.originalEvent&&e.originalEvent.isTrusted&&(this.setAttribute("tabindex",t),C.$element[0].setAttribute("tabindex",-1),C.selectpicker.view.tabindex=t)}).on("blur"+j,function(e){void 0!==C.selectpicker.view.tabindex&&e.originalEvent&&e.originalEvent.isTrusted&&(C.$element[0].setAttribute("tabindex",C.selectpicker.view.tabindex),this.setAttribute("tabindex",-1),C.selectpicker.view.tabindex=void 0)}),this.$element.on("change"+j,function(){C.render(),C.$element.trigger("changed"+j,T),T=null}).on("focus"+j,function(){C.options.mobile||C.$button[0].focus()})},liveSearchListener:function(){var u=this;this.$button.on("click.bs.dropdown.data-api",function(){u.$searchbox.val()&&(u.$searchbox.val(""),u.selectpicker.search.previousValue=void 0)}),this.$searchbox.on("click.bs.dropdown.data-api focus.bs.dropdown.data-api touchend.bs.dropdown.data-api",function(e){e.stopPropagation()}),this.$searchbox.on("input propertychange",function(){var e=u.$searchbox[0].value;if(u.selectpicker.search.elements=[],u.selectpicker.search.data=[],e){var t=[],i=e.toUpperCase(),s={},n=[],o=u._searchStyle(),r=u.options.liveSearchNormalize;r&&(i=w(i));for(var l=0;l<u.selectpicker.main.data.length;l++){var a=u.selectpicker.main.data[l];s[l]||(s[l]=k(a,i,o,r)),s[l]&&void 0!==a.headerIndex&&-1===n.indexOf(a.headerIndex)&&(0<a.headerIndex&&(s[a.headerIndex-1]=!0,n.push(a.headerIndex-1)),s[a.headerIndex]=!0,n.push(a.headerIndex),s[a.lastIndex+1]=!0),s[l]&&"optgroup-label"!==a.type&&n.push(l)}l=0;for(var c=n.length;l<c;l++){var d=n[l],h=n[l-1],p=(a=u.selectpicker.main.data[d],u.selectpicker.main.data[h]);("divider"!==a.type||"divider"===a.type&&p&&"divider"!==p.type&&c-1!==l)&&(u.selectpicker.search.data.push(a),t.push(u.selectpicker.main.elements[d]))}u.activeIndex=void 0,u.noScroll=!0,u.$menuInner.scrollTop(0),u.selectpicker.search.elements=t,u.createView(!0),function(e,t){e.length||(_.noResults.innerHTML=this.options.noneResultsText.replace("{0}",'"'+S(t)+'"'),this.$menuInner[0].firstChild.appendChild(_.noResults))}.call(u,t,e)}else u.selectpicker.search.previousValue&&(u.$menuInner.scrollTop(0),u.createView(!1));u.selectpicker.search.previousValue=e})},_searchStyle:function(){return this.options.liveSearchStyle||"contains"},val:function(e){var t=this.$element[0];if(void 0===e)return this.$element.val();var i=z(t);if(T=[null,null,i],this.$element.val(e).trigger("changed"+j,T),this.$newElement.hasClass(V.SHOW))if(this.multiple)this.setOptionStatus(!0);else{var s=(t.options[t.selectedIndex]||{}).liIndex;"number"==typeof s&&(this.setSelected(this.selectedIndex,!1),this.setSelected(s,!0))}return this.render(),T=null,this.$element},changeAll:function(e){if(this.multiple){void 0===e&&(e=!0);var t=this.$element[0],i=0,s=0,n=z(t);t.classList.add("bs-select-hidden");for(var o=0,r=this.selectpicker.current.data,l=r.length;o<l;o++){var a=r[o],c=a.option;c&&!a.disabled&&"divider"!==a.type&&(a.selected&&i++,!0===(c.selected=e)&&s++)}t.classList.remove("bs-select-hidden"),i!==s&&(this.setOptionStatus(),T=[null,null,n],this.$element.triggerNative("change"))}},selectAll:function(){return this.changeAll(!0)},deselectAll:function(){return this.changeAll(!1)},toggle:function(e){(e=e||window.event)&&e.stopPropagation(),this.$button.trigger("click.bs.dropdown.data-api")},keydown:function(e){var t,i,s,n,o,r=P(this),l=r.hasClass("dropdown-toggle"),a=(l?r.closest(".dropdown"):r.closest(F.MENU)).data("this"),c=a.findLis(),d=!1,h=e.which===H&&!l&&!a.options.selectOnTab,p=G.test(e.which)||h,u=a.$menuInner[0].scrollTop,f=!0===a.isVirtual()?a.selectpicker.view.position0:0;if(!(112<=e.which&&e.which<=123))if(!(i=a.$newElement.hasClass(V.SHOW))&&(p||48<=e.which&&e.which<=57||96<=e.which&&e.which<=105||65<=e.which&&e.which<=90)&&(a.$button.trigger("click.bs.dropdown.data-api"),a.options.liveSearch))a.$searchbox.trigger("focus");else{if(e.which===A&&i&&(e.preventDefault(),a.$button.trigger("click.bs.dropdown.data-api").trigger("focus")),p){if(!c.length)return;-1!==(t=(s=a.selectpicker.main.elements[a.activeIndex])?Array.prototype.indexOf.call(s.parentElement.children,s):-1)&&a.defocusItem(s),e.which===B?(-1!==t&&t--,t+f<0&&(t+=c.length),a.selectpicker.view.canHighlight[t+f]||-1===(t=a.selectpicker.view.canHighlight.slice(0,t+f).lastIndexOf(!0)-f)&&(t=c.length-1)):e.which!==R&&!h||(++t+f>=a.selectpicker.view.canHighlight.length&&(t=a.selectpicker.view.firstHighlightIndex),a.selectpicker.view.canHighlight[t+f]||(t=t+1+a.selectpicker.view.canHighlight.slice(t+f+1).indexOf(!0))),e.preventDefault();var m=f+t;e.which===B?0===f&&t===c.length-1?(a.$menuInner[0].scrollTop=a.$menuInner[0].scrollHeight,m=a.selectpicker.current.elements.length-1):d=(o=(n=a.selectpicker.current.data[m]).position-n.height)<u:e.which!==R&&!h||(t===a.selectpicker.view.firstHighlightIndex?(a.$menuInner[0].scrollTop=0,m=a.selectpicker.view.firstHighlightIndex):d=u<(o=(n=a.selectpicker.current.data[m]).position-a.sizeInfo.menuInnerHeight)),s=a.selectpicker.current.elements[m],a.activeIndex=a.selectpicker.current.data[m].index,a.focusItem(s),a.selectpicker.view.currentActive=s,d&&(a.$menuInner[0].scrollTop=o),a.options.liveSearch?a.$searchbox.trigger("focus"):r.trigger("focus")}else if(!r.is("input")&&!q.test(e.which)||e.which===D&&a.selectpicker.keydown.keyHistory){var v,g,b=[];e.preventDefault(),a.selectpicker.keydown.keyHistory+=C[e.which],a.selectpicker.keydown.resetKeyHistory.cancel&&clearTimeout(a.selectpicker.keydown.resetKeyHistory.cancel),a.selectpicker.keydown.resetKeyHistory.cancel=a.selectpicker.keydown.resetKeyHistory.start(),g=a.selectpicker.keydown.keyHistory,/^(.)\1+$/.test(g)&&(g=g.charAt(0));for(var w=0;w<a.selectpicker.current.data.length;w++){var I=a.selectpicker.current.data[w];k(I,g,"startsWith",!0)&&a.selectpicker.view.canHighlight[w]&&b.push(I.index)}if(b.length){var x=0;c.removeClass("active").find("a").removeClass("active"),1===g.length&&(-1===(x=b.indexOf(a.activeIndex))||x===b.length-1?x=0:x++),v=b[x],d=0<u-(n=a.selectpicker.main.data[v]).position?(o=n.position-n.height,!0):(o=n.position-a.sizeInfo.menuInnerHeight,n.position>u+a.sizeInfo.menuInnerHeight),s=a.selectpicker.main.elements[v],a.activeIndex=b[x],a.focusItem(s),s&&s.firstChild.focus(),d&&(a.$menuInner[0].scrollTop=o),r.trigger("focus")}}i&&(e.which===D&&!a.selectpicker.keydown.keyHistory||e.which===L||e.which===H&&a.options.selectOnTab)&&(e.which!==D&&e.preventDefault(),a.options.liveSearch&&e.which===D||(a.$menuInner.find(".active a").trigger("click",!0),r.trigger("focus"),a.options.liveSearch||(e.preventDefault(),P(document).data("spaceSelect",!0))))}},mobile:function(){this.options.mobile=!0,this.$element[0].classList.add("mobile-device")},refresh:function(){var e=P.extend({},this.options,this.$element.data());this.options=e,this.checkDisabled(),this.buildData(),this.setStyle(),this.render(),this.buildList(),this.setWidth(),this.setSize(!0),this.$element.trigger("refreshed"+j)},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()},destroy:function(){this.$newElement.before(this.$element).remove(),this.$bsContainer?this.$bsContainer.remove():this.$menu.remove(),this.selectpicker.view.titleOption&&this.selectpicker.view.titleOption.parentNode&&this.selectpicker.view.titleOption.parentNode.removeChild(this.selectpicker.view.titleOption),this.$element.off(j).removeData("selectpicker").removeClass("bs-select-hidden selectpicker"),P(window).off(j+"."+this.selectId)}};var J=P.fn.selectpicker;function Q(){if(P.fn.dropdown)return(P.fn.dropdown.Constructor._dataApiKeydownHandler||P.fn.dropdown.Constructor.prototype.keydown).apply(this,arguments)}P.fn.selectpicker=Z,P.fn.selectpicker.Constructor=Y,P.fn.selectpicker.noConflict=function(){return P.fn.selectpicker=J,this},P(document).off("keydown.bs.dropdown.data-api").on("keydown.bs.dropdown.data-api",':not(.bootstrap-select) > [data-toggle="dropdown"]',Q).on("keydown.bs.dropdown.data-api",":not(.bootstrap-select) > .dropdown-menu",Q).on("keydown"+j,'.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input',Y.prototype.keydown).on("focusin.modal",'.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input',function(e){e.stopPropagation()}),P(window).on("load"+j+".data-api",function(){P(".selectpicker").each(function(){var e=P(this);Z.call(e,e.data())})})}(e)});
9
- //# sourceMappingURL=bootstrap-select.min.js.map
 
 
 
 
 
 
 
 
 
resources/js/chartist.min.js DELETED
@@ -1,10 +0,0 @@
1
- /* Chartist.js 0.11.4
2
- * Copyright © 2019 Gion Kunz
3
- * Free to use under either the WTFPL license or the MIT license.
4
- * https://raw.githubusercontent.com/gionkunz/chartist-js/master/LICENSE-WTFPL
5
- * https://raw.githubusercontent.com/gionkunz/chartist-js/master/LICENSE-MIT
6
- */
7
-
8
- !function(a,b){"function"==typeof define&&define.amd?define("Chartist",[],function(){return a.Chartist=b()}):"object"==typeof module&&module.exports?module.exports=b():a.Chartist=b()}(this,function(){var a={version:"0.11.4"};return function(a,b){"use strict";var c=a.window,d=a.document;b.namespaces={svg:"http://www.w3.org/2000/svg",xmlns:"http://www.w3.org/2000/xmlns/",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",ct:"http://gionkunz.github.com/chartist-js/ct"},b.noop=function(a){return a},b.alphaNumerate=function(a){return String.fromCharCode(97+a%26)},b.extend=function(a){var c,d,e;for(a=a||{},c=1;c<arguments.length;c++){d=arguments[c];for(var f in d)e=d[f],"object"!=typeof e||null===e||e instanceof Array?a[f]=e:a[f]=b.extend(a[f],e)}return a},b.replaceAll=function(a,b,c){return a.replace(new RegExp(b,"g"),c)},b.ensureUnit=function(a,b){return"number"==typeof a&&(a+=b),a},b.quantity=function(a){if("string"==typeof a){var b=/^(\d+)\s*(.*)$/g.exec(a);return{value:+b[1],unit:b[2]||void 0}}return{value:a}},b.querySelector=function(a){return a instanceof Node?a:d.querySelector(a)},b.times=function(a){return Array.apply(null,new Array(a))},b.sum=function(a,b){return a+(b?b:0)},b.mapMultiply=function(a){return function(b){return b*a}},b.mapAdd=function(a){return function(b){return b+a}},b.serialMap=function(a,c){var d=[],e=Math.max.apply(null,a.map(function(a){return a.length}));return b.times(e).forEach(function(b,e){var f=a.map(function(a){return a[e]});d[e]=c.apply(null,f)}),d},b.roundWithPrecision=function(a,c){var d=Math.pow(10,c||b.precision);return Math.round(a*d)/d},b.precision=8,b.escapingMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#039;"},b.serialize=function(a){return null===a||void 0===a?a:("number"==typeof a?a=""+a:"object"==typeof a&&(a=JSON.stringify({data:a})),Object.keys(b.escapingMap).reduce(function(a,c){return b.replaceAll(a,c,b.escapingMap[c])},a))},b.deserialize=function(a){if("string"!=typeof a)return a;a=Object.keys(b.escapingMap).reduce(function(a,c){return b.replaceAll(a,b.escapingMap[c],c)},a);try{a=JSON.parse(a),a=void 0!==a.data?a.data:a}catch(c){}return a},b.createSvg=function(a,c,d,e){var f;return c=c||"100%",d=d||"100%",Array.prototype.slice.call(a.querySelectorAll("svg")).filter(function(a){return a.getAttributeNS(b.namespaces.xmlns,"ct")}).forEach(function(b){a.removeChild(b)}),f=new b.Svg("svg").attr({width:c,height:d}).addClass(e),f._node.style.width=c,f._node.style.height=d,a.appendChild(f._node),f},b.normalizeData=function(a,c,d){var e,f={raw:a,normalized:{}};return f.normalized.series=b.getDataArray({series:a.series||[]},c,d),e=f.normalized.series.every(function(a){return a instanceof Array})?Math.max.apply(null,f.normalized.series.map(function(a){return a.length})):f.normalized.series.length,f.normalized.labels=(a.labels||[]).slice(),Array.prototype.push.apply(f.normalized.labels,b.times(Math.max(0,e-f.normalized.labels.length)).map(function(){return""})),c&&b.reverseData(f.normalized),f},b.safeHasProperty=function(a,b){return null!==a&&"object"==typeof a&&a.hasOwnProperty(b)},b.isDataHoleValue=function(a){return null===a||void 0===a||"number"==typeof a&&isNaN(a)},b.reverseData=function(a){a.labels.reverse(),a.series.reverse();for(var b=0;b<a.series.length;b++)"object"==typeof a.series[b]&&void 0!==a.series[b].data?a.series[b].data.reverse():a.series[b]instanceof Array&&a.series[b].reverse()},b.getDataArray=function(a,c,d){function e(a){if(b.safeHasProperty(a,"value"))return e(a.value);if(b.safeHasProperty(a,"data"))return e(a.data);if(a instanceof Array)return a.map(e);if(!b.isDataHoleValue(a)){if(d){var c={};return"string"==typeof d?c[d]=b.getNumberOrUndefined(a):c.y=b.getNumberOrUndefined(a),c.x=a.hasOwnProperty("x")?b.getNumberOrUndefined(a.x):c.x,c.y=a.hasOwnProperty("y")?b.getNumberOrUndefined(a.y):c.y,c}return b.getNumberOrUndefined(a)}}return a.series.map(e)},b.normalizePadding=function(a,b){return b=b||0,"number"==typeof a?{top:a,right:a,bottom:a,left:a}:{top:"number"==typeof a.top?a.top:b,right:"number"==typeof a.right?a.right:b,bottom:"number"==typeof a.bottom?a.bottom:b,left:"number"==typeof a.left?a.left:b}},b.getMetaData=function(a,b){var c=a.data?a.data[b]:a[b];return c?c.meta:void 0},b.orderOfMagnitude=function(a){return Math.floor(Math.log(Math.abs(a))/Math.LN10)},b.projectLength=function(a,b,c){return b/c.range*a},b.getAvailableHeight=function(a,c){return Math.max((b.quantity(c.height).value||a.height())-(c.chartPadding.top+c.chartPadding.bottom)-c.axisX.offset,0)},b.getHighLow=function(a,c,d){function e(a){if(void 0!==a)if(a instanceof Array)for(var b=0;b<a.length;b++)e(a[b]);else{var c=d?+a[d]:+a;g&&c>f.high&&(f.high=c),h&&c<f.low&&(f.low=c)}}c=b.extend({},c,d?c["axis"+d.toUpperCase()]:{});var f={high:void 0===c.high?-Number.MAX_VALUE:+c.high,low:void 0===c.low?Number.MAX_VALUE:+c.low},g=void 0===c.high,h=void 0===c.low;return(g||h)&&e(a),(c.referenceValue||0===c.referenceValue)&&(f.high=Math.max(c.referenceValue,f.high),f.low=Math.min(c.referenceValue,f.low)),f.high<=f.low&&(0===f.low?f.high=1:f.low<0?f.high=0:f.high>0?f.low=0:(f.high=1,f.low=0)),f},b.isNumeric=function(a){return null!==a&&isFinite(a)},b.isFalseyButZero=function(a){return!a&&0!==a},b.getNumberOrUndefined=function(a){return b.isNumeric(a)?+a:void 0},b.isMultiValue=function(a){return"object"==typeof a&&("x"in a||"y"in a)},b.getMultiValue=function(a,c){return b.isMultiValue(a)?b.getNumberOrUndefined(a[c||"y"]):b.getNumberOrUndefined(a)},b.rho=function(a){function b(a,c){return a%c===0?c:b(c,a%c)}function c(a){return a*a+1}if(1===a)return a;var d,e=2,f=2;if(a%2===0)return 2;do e=c(e)%a,f=c(c(f))%a,d=b(Math.abs(e-f),a);while(1===d);return d},b.getBounds=function(a,c,d,e){function f(a,b){return a===(a+=b)&&(a*=1+(b>0?o:-o)),a}var g,h,i,j=0,k={high:c.high,low:c.low};k.valueRange=k.high-k.low,k.oom=b.orderOfMagnitude(k.valueRange),k.step=Math.pow(10,k.oom),k.min=Math.floor(k.low/k.step)*k.step,k.max=Math.ceil(k.high/k.step)*k.step,k.range=k.max-k.min,k.numberOfSteps=Math.round(k.range/k.step);var l=b.projectLength(a,k.step,k),m=l<d,n=e?b.rho(k.range):0;if(e&&b.projectLength(a,1,k)>=d)k.step=1;else if(e&&n<k.step&&b.projectLength(a,n,k)>=d)k.step=n;else for(;;){if(m&&b.projectLength(a,k.step,k)<=d)k.step*=2;else{if(m||!(b.projectLength(a,k.step/2,k)>=d))break;if(k.step/=2,e&&k.step%1!==0){k.step*=2;break}}if(j++>1e3)throw new Error("Exceeded maximum number of iterations while optimizing scale step!")}var o=2.221e-16;for(k.step=Math.max(k.step,o),h=k.min,i=k.max;h+k.step<=k.low;)h=f(h,k.step);for(;i-k.step>=k.high;)i=f(i,-k.step);k.min=h,k.max=i,k.range=k.max-k.min;var p=[];for(g=k.min;g<=k.max;g=f(g,k.step)){var q=b.roundWithPrecision(g);q!==p[p.length-1]&&p.push(q)}return k.values=p,k},b.polarToCartesian=function(a,b,c,d){var e=(d-90)*Math.PI/180;return{x:a+c*Math.cos(e),y:b+c*Math.sin(e)}},b.createChartRect=function(a,c,d){var e=!(!c.axisX&&!c.axisY),f=e?c.axisY.offset:0,g=e?c.axisX.offset:0,h=a.width()||b.quantity(c.width).value||0,i=a.height()||b.quantity(c.height).value||0,j=b.normalizePadding(c.chartPadding,d);h=Math.max(h,f+j.left+j.right),i=Math.max(i,g+j.top+j.bottom);var k={padding:j,width:function(){return this.x2-this.x1},height:function(){return this.y1-this.y2}};return e?("start"===c.axisX.position?(k.y2=j.top+g,k.y1=Math.max(i-j.bottom,k.y2+1)):(k.y2=j.top,k.y1=Math.max(i-j.bottom-g,k.y2+1)),"start"===c.axisY.position?(k.x1=j.left+f,k.x2=Math.max(h-j.right,k.x1+1)):(k.x1=j.left,k.x2=Math.max(h-j.right-f,k.x1+1))):(k.x1=j.left,k.x2=Math.max(h-j.right,k.x1+1),k.y2=j.top,k.y1=Math.max(i-j.bottom,k.y2+1)),k},b.createGrid=function(a,c,d,e,f,g,h,i){var j={};j[d.units.pos+"1"]=a,j[d.units.pos+"2"]=a,j[d.counterUnits.pos+"1"]=e,j[d.counterUnits.pos+"2"]=e+f;var k=g.elem("line",j,h.join(" "));i.emit("draw",b.extend({type:"grid",axis:d,index:c,group:g,element:k},j))},b.createGridBackground=function(a,b,c,d){var e=a.elem("rect",{x:b.x1,y:b.y2,width:b.width(),height:b.height()},c,!0);d.emit("draw",{type:"gridBackground",group:a,element:e})},b.createLabel=function(a,c,e,f,g,h,i,j,k,l,m){var n,o={};if(o[g.units.pos]=a+i[g.units.pos],o[g.counterUnits.pos]=i[g.counterUnits.pos],o[g.units.len]=c,o[g.counterUnits.len]=Math.max(0,h-10),l){var p=d.createElement("span");p.className=k.join(" "),p.setAttribute("xmlns",b.namespaces.xhtml),p.innerText=f[e],p.style[g.units.len]=Math.round(o[g.units.len])+"px",p.style[g.counterUnits.len]=Math.round(o[g.counterUnits.len])+"px",n=j.foreignObject(p,b.extend({style:"overflow: visible;"},o))}else n=j.elem("text",o,k.join(" ")).text(f[e]);m.emit("draw",b.extend({type:"label",axis:g,index:e,group:j,element:n,text:f[e]},o))},b.getSeriesOption=function(a,b,c){if(a.name&&b.series&&b.series[a.name]){var d=b.series[a.name];return d.hasOwnProperty(c)?d[c]:b[c]}return b[c]},b.optionsProvider=function(a,d,e){function f(a){var f=h;if(h=b.extend({},j),d)for(i=0;i<d.length;i++){var g=c.matchMedia(d[i][0]);g.matches&&(h=b.extend(h,d[i][1]))}e&&a&&e.emit("optionsChanged",{previousOptions:f,currentOptions:h})}function g(){k.forEach(function(a){a.removeListener(f)})}var h,i,j=b.extend({},a),k=[];if(!c.matchMedia)throw"window.matchMedia not found! Make sure you're using a polyfill.";if(d)for(i=0;i<d.length;i++){var l=c.matchMedia(d[i][0]);l.addListener(f),k.push(l)}return f(),{removeMediaQueryListeners:g,getCurrentOptions:function(){return b.extend({},h)}}},b.splitIntoSegments=function(a,c,d){var e={increasingX:!1,fillHoles:!1};d=b.extend({},e,d);for(var f=[],g=!0,h=0;h<a.length;h+=2)void 0===b.getMultiValue(c[h/2].value)?d.fillHoles||(g=!0):(d.increasingX&&h>=2&&a[h]<=a[h-2]&&(g=!0),g&&(f.push({pathCoordinates:[],valueData:[]}),g=!1),f[f.length-1].pathCoordinates.push(a[h],a[h+1]),f[f.length-1].valueData.push(c[h/2]));return f}}(this||global,a),function(a,b){"use strict";b.Interpolation={},b.Interpolation.none=function(a){var c={fillHoles:!1};return a=b.extend({},c,a),function(c,d){for(var e=new b.Svg.Path,f=!0,g=0;g<c.length;g+=2){var h=c[g],i=c[g+1],j=d[g/2];void 0!==b.getMultiValue(j.value)?(f?e.move(h,i,!1,j):e.line(h,i,!1,j),f=!1):a.fillHoles||(f=!0)}return e}},b.Interpolation.simple=function(a){var c={divisor:2,fillHoles:!1};a=b.extend({},c,a);var d=1/Math.max(1,a.divisor);return function(c,e){for(var f,g,h,i=new b.Svg.Path,j=0;j<c.length;j+=2){var k=c[j],l=c[j+1],m=(k-f)*d,n=e[j/2];void 0!==n.value?(void 0===h?i.move(k,l,!1,n):i.curve(f+m,g,k-m,l,k,l,!1,n),f=k,g=l,h=n):a.fillHoles||(f=k=h=void 0)}return i}},b.Interpolation.cardinal=function(a){var c={tension:1,fillHoles:!1};a=b.extend({},c,a);var d=Math.min(1,Math.max(0,a.tension)),e=1-d;return function f(c,g){var h=b.splitIntoSegments(c,g,{fillHoles:a.fillHoles});if(h.length){if(h.length>1){var i=[];return h.forEach(function(a){i.push(f(a.pathCoordinates,a.valueData))}),b.Svg.Path.join(i)}if(c=h[0].pathCoordinates,g=h[0].valueData,c.length<=4)return b.Interpolation.none()(c,g);for(var j,k=(new b.Svg.Path).move(c[0],c[1],!1,g[0]),l=0,m=c.length;m-2*!j>l;l+=2){var n=[{x:+c[l-2],y:+c[l-1]},{x:+c[l],y:+c[l+1]},{x:+c[l+2],y:+c[l+3]},{x:+c[l+4],y:+c[l+5]}];j?l?m-4===l?n[3]={x:+c[0],y:+c[1]}:m-2===l&&(n[2]={x:+c[0],y:+c[1]},n[3]={x:+c[2],y:+c[3]}):n[0]={x:+c[m-2],y:+c[m-1]}:m-4===l?n[3]=n[2]:l||(n[0]={x:+c[l],y:+c[l+1]}),k.curve(d*(-n[0].x+6*n[1].x+n[2].x)/6+e*n[2].x,d*(-n[0].y+6*n[1].y+n[2].y)/6+e*n[2].y,d*(n[1].x+6*n[2].x-n[3].x)/6+e*n[2].x,d*(n[1].y+6*n[2].y-n[3].y)/6+e*n[2].y,n[2].x,n[2].y,!1,g[(l+2)/2])}return k}return b.Interpolation.none()([])}},b.Interpolation.monotoneCubic=function(a){var c={fillHoles:!1};return a=b.extend({},c,a),function d(c,e){var f=b.splitIntoSegments(c,e,{fillHoles:a.fillHoles,increasingX:!0});if(f.length){if(f.length>1){var g=[];return f.forEach(function(a){g.push(d(a.pathCoordinates,a.valueData))}),b.Svg.Path.join(g)}if(c=f[0].pathCoordinates,e=f[0].valueData,c.length<=4)return b.Interpolation.none()(c,e);var h,i,j=[],k=[],l=c.length/2,m=[],n=[],o=[],p=[];for(h=0;h<l;h++)j[h]=c[2*h],k[h]=c[2*h+1];for(h=0;h<l-1;h++)o[h]=k[h+1]-k[h],p[h]=j[h+1]-j[h],n[h]=o[h]/p[h];for(m[0]=n[0],m[l-1]=n[l-2],h=1;h<l-1;h++)0===n[h]||0===n[h-1]||n[h-1]>0!=n[h]>0?m[h]=0:(m[h]=3*(p[h-1]+p[h])/((2*p[h]+p[h-1])/n[h-1]+(p[h]+2*p[h-1])/n[h]),isFinite(m[h])||(m[h]=0));for(i=(new b.Svg.Path).move(j[0],k[0],!1,e[0]),h=0;h<l-1;h++)i.curve(j[h]+p[h]/3,k[h]+m[h]*p[h]/3,j[h+1]-p[h]/3,k[h+1]-m[h+1]*p[h]/3,j[h+1],k[h+1],!1,e[h+1]);return i}return b.Interpolation.none()([])}},b.Interpolation.step=function(a){var c={postpone:!0,fillHoles:!1};return a=b.extend({},c,a),function(c,d){for(var e,f,g,h=new b.Svg.Path,i=0;i<c.length;i+=2){var j=c[i],k=c[i+1],l=d[i/2];void 0!==l.value?(void 0===g?h.move(j,k,!1,l):(a.postpone?h.line(j,f,!1,g):h.line(e,k,!1,l),h.line(j,k,!1,l)),e=j,f=k,g=l):a.fillHoles||(e=f=g=void 0)}return h}}}(this||global,a),function(a,b){"use strict";b.EventEmitter=function(){function a(a,b){d[a]=d[a]||[],d[a].push(b)}function b(a,b){d[a]&&(b?(d[a].splice(d[a].indexOf(b),1),0===d[a].length&&delete d[a]):delete d[a])}function c(a,b){d[a]&&d[a].forEach(function(a){a(b)}),d["*"]&&d["*"].forEach(function(c){c(a,b)})}var d=[];return{addEventHandler:a,removeEventHandler:b,emit:c}}}(this||global,a),function(a,b){"use strict";function c(a){var b=[];if(a.length)for(var c=0;c<a.length;c++)b.push(a[c]);return b}function d(a,c){var d=c||this.prototype||b.Class,e=Object.create(d);b.Class.cloneDefinitions(e,a);var f=function(){var a,c=e.constructor||function(){};return a=this===b?Object.create(e):this,c.apply(a,Array.prototype.slice.call(arguments,0)),a};return f.prototype=e,f["super"]=d,f.extend=this.extend,f}function e(){var a=c(arguments),b=a[0];return a.splice(1,a.length-1).forEach(function(a){Object.getOwnPropertyNames(a).forEach(function(c){delete b[c],Object.defineProperty(b,c,Object.getOwnPropertyDescriptor(a,c))})}),b}b.Class={extend:d,cloneDefinitions:e}}(this||global,a),function(a,b){"use strict";function c(a,c,d){return a&&(this.data=a||{},this.data.labels=this.data.labels||[],this.data.series=this.data.series||[],this.eventEmitter.emit("data",{type:"update",data:this.data})),c&&(this.options=b.extend({},d?this.options:this.defaultOptions,c),this.initializeTimeoutId||(this.optionsProvider.removeMediaQueryListeners(),this.optionsProvider=b.optionsProvider(this.options,this.responsiveOptions,this.eventEmitter))),this.initializeTimeoutId||this.createChart(this.optionsProvider.getCurrentOptions()),this}function d(){return this.initializeTimeoutId?i.clearTimeout(this.initializeTimeoutId):(i.removeEventListener("resize",this.resizeListener),this.optionsProvider.removeMediaQueryListeners()),this}function e(a,b){return this.eventEmitter.addEventHandler(a,b),this}function f(a,b){return this.eventEmitter.removeEventHandler(a,b),this}function g(){i.addEventListener("resize",this.resizeListener),this.optionsProvider=b.optionsProvider(this.options,this.responsiveOptions,this.eventEmitter),this.eventEmitter.addEventHandler("optionsChanged",function(){this.update()}.bind(this)),this.options.plugins&&this.options.plugins.forEach(function(a){a instanceof Array?a[0](this,a[1]):a(this)}.bind(this)),this.eventEmitter.emit("data",{type:"initial",data:this.data}),this.createChart(this.optionsProvider.getCurrentOptions()),this.initializeTimeoutId=void 0}function h(a,c,d,e,f){this.container=b.querySelector(a),this.data=c||{},this.data.labels=this.data.labels||[],this.data.series=this.data.series||[],this.defaultOptions=d,this.options=e,this.responsiveOptions=f,this.eventEmitter=b.EventEmitter(),this.supportsForeignObject=b.Svg.isSupported("Extensibility"),this.supportsAnimations=b.Svg.isSupported("AnimationEventsAttribute"),this.resizeListener=function(){this.update()}.bind(this),this.container&&(this.container.__chartist__&&this.container.__chartist__.detach(),this.container.__chartist__=this),this.initializeTimeoutId=setTimeout(g.bind(this),0)}var i=a.window;b.Base=b.Class.extend({constructor:h,optionsProvider:void 0,container:void 0,svg:void 0,eventEmitter:void 0,createChart:function(){throw new Error("Base chart type can't be instantiated!")},update:c,detach:d,on:e,off:f,version:b.version,supportsForeignObject:!1})}(this||global,a),function(a,b){"use strict";function c(a,c,d,e,f){a instanceof Element?this._node=a:(this._node=y.createElementNS(b.namespaces.svg,a),"svg"===a&&this.attr({"xmlns:ct":b.namespaces.ct})),c&&this.attr(c),d&&this.addClass(d),e&&(f&&e._node.firstChild?e._node.insertBefore(this._node,e._node.firstChild):e._node.appendChild(this._node))}function d(a,c){return"string"==typeof a?c?this._node.getAttributeNS(c,a):this._node.getAttribute(a):(Object.keys(a).forEach(function(c){if(void 0!==a[c])if(c.indexOf(":")!==-1){var d=c.split(":");this._node.setAttributeNS(b.namespaces[d[0]],c,a[c])}else this._node.setAttribute(c,a[c])}.bind(this)),this)}function e(a,c,d,e){return new b.Svg(a,c,d,this,e)}function f(){return this._node.parentNode instanceof SVGElement?new b.Svg(this._node.parentNode):null}function g(){for(var a=this._node;"svg"!==a.nodeName;)a=a.parentNode;return new b.Svg(a)}function h(a){var c=this._node.querySelector(a);return c?new b.Svg(c):null}function i(a){var c=this._node.querySelectorAll(a);return c.length?new b.Svg.List(c):null}function j(){return this._node}function k(a,c,d,e){if("string"==typeof a){var f=y.createElement("div");f.innerHTML=a,a=f.firstChild}a.setAttribute("xmlns",b.namespaces.xmlns);var g=this.elem("foreignObject",c,d,e);return g._node.appendChild(a),g}function l(a){return this._node.appendChild(y.createTextNode(a)),this}function m(){for(;this._node.firstChild;)this._node.removeChild(this._node.firstChild);return this}function n(){return this._node.parentNode.removeChild(this._node),this.parent()}function o(a){return this._node.parentNode.replaceChild(a._node,this._node),a}function p(a,b){return b&&this._node.firstChild?this._node.insertBefore(a._node,this._node.firstChild):this._node.appendChild(a._node),this}function q(){return this._node.getAttribute("class")?this._node.getAttribute("class").trim().split(/\s+/):[]}function r(a){return this._node.setAttribute("class",this.classes(this._node).concat(a.trim().split(/\s+/)).filter(function(a,b,c){return c.indexOf(a)===b}).join(" ")),this}function s(a){var b=a.trim().split(/\s+/);return this._node.setAttribute("class",this.classes(this._node).filter(function(a){return b.indexOf(a)===-1}).join(" ")),this}function t(){return this._node.setAttribute("class",""),this}function u(){return this._node.getBoundingClientRect().height}function v(){return this._node.getBoundingClientRect().width}function w(a,c,d){return void 0===c&&(c=!0),Object.keys(a).forEach(function(e){function f(a,c){var f,g,h,i={};a.easing&&(h=a.easing instanceof Array?a.easing:b.Svg.Easing[a.easing],delete a.easing),a.begin=b.ensureUnit(a.begin,"ms"),a.dur=b.ensureUnit(a.dur,"ms"),h&&(a.calcMode="spline",a.keySplines=h.join(" "),a.keyTimes="0;1"),c&&(a.fill="freeze",i[e]=a.from,this.attr(i),g=b.quantity(a.begin||0).value,a.begin="indefinite"),f=this.elem("animate",b.extend({attributeName:e},a)),c&&setTimeout(function(){try{f._node.beginElement()}catch(b){i[e]=a.to,this.attr(i),f.remove()}}.bind(this),g),d&&f._node.addEventListener("beginEvent",function(){d.emit("animationBegin",{element:this,animate:f._node,params:a})}.bind(this)),f._node.addEventListener("endEvent",function(){d&&d.emit("animationEnd",{element:this,animate:f._node,params:a}),c&&(i[e]=a.to,this.attr(i),f.remove())}.bind(this))}a[e]instanceof Array?a[e].forEach(function(a){f.bind(this)(a,!1)}.bind(this)):f.bind(this)(a[e],c)}.bind(this)),this}function x(a){var c=this;this.svgElements=[];for(var d=0;d<a.length;d++)this.svgElements.push(new b.Svg(a[d]));Object.keys(b.Svg.prototype).filter(function(a){return["constructor","parent","querySelector","querySelectorAll","replace","append","classes","height","width"].indexOf(a)===-1}).forEach(function(a){c[a]=function(){var d=Array.prototype.slice.call(arguments,0);return c.svgElements.forEach(function(c){b.Svg.prototype[a].apply(c,d)}),c}})}var y=a.document;b.Svg=b.Class.extend({constructor:c,attr:d,elem:e,parent:f,root:g,querySelector:h,querySelectorAll:i,getNode:j,foreignObject:k,text:l,empty:m,remove:n,replace:o,append:p,classes:q,addClass:r,removeClass:s,removeAllClasses:t,height:u,width:v,animate:w}),b.Svg.isSupported=function(a){return y.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#"+a,"1.1")};var z={easeInSine:[.47,0,.745,.715],easeOutSine:[.39,.575,.565,1],easeInOutSine:[.445,.05,.55,.95],easeInQuad:[.55,.085,.68,.53],easeOutQuad:[.25,.46,.45,.94],easeInOutQuad:[.455,.03,.515,.955],easeInCubic:[.55,.055,.675,.19],easeOutCubic:[.215,.61,.355,1],easeInOutCubic:[.645,.045,.355,1],easeInQuart:[.895,.03,.685,.22],easeOutQuart:[.165,.84,.44,1],easeInOutQuart:[.77,0,.175,1],easeInQuint:[.755,.05,.855,.06],easeOutQuint:[.23,1,.32,1],easeInOutQuint:[.86,0,.07,1],easeInExpo:[.95,.05,.795,.035],easeOutExpo:[.19,1,.22,1],easeInOutExpo:[1,0,0,1],easeInCirc:[.6,.04,.98,.335],easeOutCirc:[.075,.82,.165,1],easeInOutCirc:[.785,.135,.15,.86],easeInBack:[.6,-.28,.735,.045],easeOutBack:[.175,.885,.32,1.275],easeInOutBack:[.68,-.55,.265,1.55]};b.Svg.Easing=z,b.Svg.List=b.Class.extend({constructor:x})}(this||global,a),function(a,b){"use strict";function c(a,c,d,e,f,g){var h=b.extend({command:f?a.toLowerCase():a.toUpperCase()},c,g?{data:g}:{});d.splice(e,0,h)}function d(a,b){a.forEach(function(c,d){t[c.command.toLowerCase()].forEach(function(e,f){b(c,e,d,f,a)})})}function e(a,c){this.pathElements=[],this.pos=0,this.close=a,this.options=b.extend({},u,c)}function f(a){return void 0!==a?(this.pos=Math.max(0,Math.min(this.pathElements.length,a)),this):this.pos}function g(a){return this.pathElements.splice(this.pos,a),this}function h(a,b,d,e){return c("M",{x:+a,y:+b},this.pathElements,this.pos++,d,e),this}function i(a,b,d,e){return c("L",{x:+a,y:+b},this.pathElements,this.pos++,d,e),this}function j(a,b,d,e,f,g,h,i){return c("C",{x1:+a,y1:+b,x2:+d,y2:+e,x:+f,y:+g},this.pathElements,this.pos++,h,i),this}function k(a,b,d,e,f,g,h,i,j){return c("A",{rx:+a,ry:+b,xAr:+d,lAf:+e,sf:+f,x:+g,y:+h},this.pathElements,this.pos++,i,j),this}function l(a){var c=a.replace(/([A-Za-z])([0-9])/g,"$1 $2").replace(/([0-9])([A-Za-z])/g,"$1 $2").split(/[\s,]+/).reduce(function(a,b){return b.match(/[A-Za-z]/)&&a.push([]),a[a.length-1].push(b),a},[]);"Z"===c[c.length-1][0].toUpperCase()&&c.pop();var d=c.map(function(a){var c=a.shift(),d=t[c.toLowerCase()];return b.extend({command:c},d.reduce(function(b,c,d){return b[c]=+a[d],b},{}))}),e=[this.pos,0];return Array.prototype.push.apply(e,d),Array.prototype.splice.apply(this.pathElements,e),this.pos+=d.length,this}function m(){var a=Math.pow(10,this.options.accuracy);return this.pathElements.reduce(function(b,c){var d=t[c.command.toLowerCase()].map(function(b){return this.options.accuracy?Math.round(c[b]*a)/a:c[b]}.bind(this));return b+c.command+d.join(",")}.bind(this),"")+(this.close?"Z":"")}function n(a,b){return d(this.pathElements,function(c,d){c[d]*="x"===d[0]?a:b}),this}function o(a,b){return d(this.pathElements,function(c,d){c[d]+="x"===d[0]?a:b}),this}function p(a){return d(this.pathElements,function(b,c,d,e,f){var g=a(b,c,d,e,f);(g||0===g)&&(b[c]=g)}),this}function q(a){var c=new b.Svg.Path(a||this.close);return c.pos=this.pos,c.pathElements=this.pathElements.slice().map(function(a){return b.extend({},a)}),c.options=b.extend({},this.options),c}function r(a){var c=[new b.Svg.Path];return this.pathElements.forEach(function(d){d.command===a.toUpperCase()&&0!==c[c.length-1].pathElements.length&&c.push(new b.Svg.Path),c[c.length-1].pathElements.push(d)}),c}function s(a,c,d){for(var e=new b.Svg.Path(c,d),f=0;f<a.length;f++)for(var g=a[f],h=0;h<g.pathElements.length;h++)e.pathElements.push(g.pathElements[h]);return e}var t={m:["x","y"],l:["x","y"],c:["x1","y1","x2","y2","x","y"],a:["rx","ry","xAr","lAf","sf","x","y"]},u={accuracy:3};b.Svg.Path=b.Class.extend({constructor:e,position:f,remove:g,move:h,line:i,curve:j,arc:k,scale:n,translate:o,transform:p,parse:l,stringify:m,clone:q,splitByCommand:r}),b.Svg.Path.elementDescriptions=t,b.Svg.Path.join=s}(this||global,a),function(a,b){"use strict";function c(a,b,c,d){this.units=a,this.counterUnits=a===e.x?e.y:e.x,this.chartRect=b,this.axisLength=b[a.rectEnd]-b[a.rectStart],this.gridOffset=b[a.rectOffset],this.ticks=c,this.options=d}function d(a,c,d,e,f){var g=e["axis"+this.units.pos.toUpperCase()],h=this.ticks.map(this.projectValue.bind(this)),i=this.ticks.map(g.labelInterpolationFnc);h.forEach(function(j,k){var l,m={x:0,y:0};l=h[k+1]?h[k+1]-j:Math.max(this.axisLength-j,30),b.isFalseyButZero(i[k])&&""!==i[k]||("x"===this.units.pos?(j=this.chartRect.x1+j,m.x=e.axisX.labelOffset.x,"start"===e.axisX.position?m.y=this.chartRect.padding.top+e.axisX.labelOffset.y+(d?5:20):m.y=this.chartRect.y1+e.axisX.labelOffset.y+(d?5:20)):(j=this.chartRect.y1-j,m.y=e.axisY.labelOffset.y-(d?l:0),"start"===e.axisY.position?m.x=d?this.chartRect.padding.left+e.axisY.labelOffset.x:this.chartRect.x1-10:m.x=this.chartRect.x2+e.axisY.labelOffset.x+10),g.showGrid&&b.createGrid(j,k,this,this.gridOffset,this.chartRect[this.counterUnits.len](),a,[e.classNames.grid,e.classNames[this.units.dir]],f),g.showLabel&&b.createLabel(j,l,k,i,this,g.offset,m,c,[e.classNames.label,e.classNames[this.units.dir],"start"===g.position?e.classNames[g.position]:e.classNames.end],d,f))}.bind(this))}var e=(a.window,a.document,{x:{pos:"x",len:"width",dir:"horizontal",rectStart:"x1",rectEnd:"x2",rectOffset:"y2"},y:{pos:"y",len:"height",dir:"vertical",rectStart:"y2",rectEnd:"y1",rectOffset:"x1"}});b.Axis=b.Class.extend({constructor:c,createGridAndLabels:d,projectValue:function(a,b,c){throw new Error("Base axis can't be instantiated!")}}),b.Axis.units=e}(this||global,a),function(a,b){"use strict";function c(a,c,d,e){var f=e.highLow||b.getHighLow(c,e,a.pos);this.bounds=b.getBounds(d[a.rectEnd]-d[a.rectStart],f,e.scaleMinSpace||20,e.onlyInteger),this.range={min:this.bounds.min,max:this.bounds.max},b.AutoScaleAxis["super"].constructor.call(this,a,d,this.bounds.values,e)}function d(a){return this.axisLength*(+b.getMultiValue(a,this.units.pos)-this.bounds.min)/this.bounds.range}a.window,a.document;b.AutoScaleAxis=b.Axis.extend({constructor:c,projectValue:d})}(this||global,a),function(a,b){"use strict";function c(a,c,d,e){var f=e.highLow||b.getHighLow(c,e,a.pos);this.divisor=e.divisor||1,this.ticks=e.ticks||b.times(this.divisor).map(function(a,b){return f.low+(f.high-f.low)/this.divisor*b}.bind(this)),this.ticks.sort(function(a,b){return a-b}),this.range={min:f.low,max:f.high},b.FixedScaleAxis["super"].constructor.call(this,a,d,this.ticks,e),this.stepLength=this.axisLength/this.divisor}function d(a){return this.axisLength*(+b.getMultiValue(a,this.units.pos)-this.range.min)/(this.range.max-this.range.min)}a.window,a.document;b.FixedScaleAxis=b.Axis.extend({constructor:c,projectValue:d})}(this||global,a),function(a,b){"use strict";function c(a,c,d,e){b.StepAxis["super"].constructor.call(this,a,d,e.ticks,e);var f=Math.max(1,e.ticks.length-(e.stretch?1:0));this.stepLength=this.axisLength/f}function d(a,b){return this.stepLength*b}a.window,a.document;b.StepAxis=b.Axis.extend({constructor:c,projectValue:d})}(this||global,a),function(a,b){"use strict";function c(a){var c=b.normalizeData(this.data,a.reverseData,!0);this.svg=b.createSvg(this.container,a.width,a.height,a.classNames.chart);var d,f,g=this.svg.elem("g").addClass(a.classNames.gridGroup),h=this.svg.elem("g"),i=this.svg.elem("g").addClass(a.classNames.labelGroup),j=b.createChartRect(this.svg,a,e.padding);d=void 0===a.axisX.type?new b.StepAxis(b.Axis.units.x,c.normalized.series,j,b.extend({},a.axisX,{ticks:c.normalized.labels,stretch:a.fullWidth})):a.axisX.type.call(b,b.Axis.units.x,c.normalized.series,j,a.axisX),f=void 0===a.axisY.type?new b.AutoScaleAxis(b.Axis.units.y,c.normalized.series,j,b.extend({},a.axisY,{high:b.isNumeric(a.high)?a.high:a.axisY.high,low:b.isNumeric(a.low)?a.low:a.axisY.low})):a.axisY.type.call(b,b.Axis.units.y,c.normalized.series,j,a.axisY),d.createGridAndLabels(g,i,this.supportsForeignObject,a,this.eventEmitter),f.createGridAndLabels(g,i,this.supportsForeignObject,a,this.eventEmitter),a.showGridBackground&&b.createGridBackground(g,j,a.classNames.gridBackground,this.eventEmitter),c.raw.series.forEach(function(e,g){var i=h.elem("g");i.attr({"ct:series-name":e.name,"ct:meta":b.serialize(e.meta)}),i.addClass([a.classNames.series,e.className||a.classNames.series+"-"+b.alphaNumerate(g)].join(" "));var k=[],l=[];c.normalized.series[g].forEach(function(a,h){var i={x:j.x1+d.projectValue(a,h,c.normalized.series[g]),y:j.y1-f.projectValue(a,h,c.normalized.series[g])};k.push(i.x,i.y),l.push({value:a,valueIndex:h,meta:b.getMetaData(e,h)})}.bind(this));var m={lineSmooth:b.getSeriesOption(e,a,"lineSmooth"),showPoint:b.getSeriesOption(e,a,"showPoint"),showLine:b.getSeriesOption(e,a,"showLine"),showArea:b.getSeriesOption(e,a,"showArea"),areaBase:b.getSeriesOption(e,a,"areaBase")},n="function"==typeof m.lineSmooth?m.lineSmooth:m.lineSmooth?b.Interpolation.monotoneCubic():b.Interpolation.none(),o=n(k,l);if(m.showPoint&&o.pathElements.forEach(function(c){var h=i.elem("line",{x1:c.x,y1:c.y,x2:c.x+.01,y2:c.y},a.classNames.point).attr({"ct:value":[c.data.value.x,c.data.value.y].filter(b.isNumeric).join(","),"ct:meta":b.serialize(c.data.meta)});this.eventEmitter.emit("draw",{type:"point",value:c.data.value,index:c.data.valueIndex,meta:c.data.meta,series:e,seriesIndex:g,axisX:d,axisY:f,group:i,element:h,x:c.x,y:c.y})}.bind(this)),m.showLine){var p=i.elem("path",{d:o.stringify()},a.classNames.line,!0);this.eventEmitter.emit("draw",{type:"line",values:c.normalized.series[g],path:o.clone(),chartRect:j,index:g,series:e,seriesIndex:g,seriesMeta:e.meta,axisX:d,axisY:f,group:i,element:p})}if(m.showArea&&f.range){var q=Math.max(Math.min(m.areaBase,f.range.max),f.range.min),r=j.y1-f.projectValue(q);o.splitByCommand("M").filter(function(a){return a.pathElements.length>1}).map(function(a){var b=a.pathElements[0],c=a.pathElements[a.pathElements.length-1];return a.clone(!0).position(0).remove(1).move(b.x,r).line(b.x,b.y).position(a.pathElements.length+1).line(c.x,r)}).forEach(function(b){var h=i.elem("path",{d:b.stringify()},a.classNames.area,!0);this.eventEmitter.emit("draw",{type:"area",values:c.normalized.series[g],path:b.clone(),series:e,seriesIndex:g,axisX:d,axisY:f,chartRect:j,index:g,group:i,element:h})}.bind(this))}}.bind(this)),this.eventEmitter.emit("created",{bounds:f.bounds,chartRect:j,axisX:d,axisY:f,svg:this.svg,options:a})}function d(a,c,d,f){b.Line["super"].constructor.call(this,a,c,e,b.extend({},e,d),f)}var e=(a.window,a.document,{axisX:{offset:30,position:"end",labelOffset:{x:0,y:0},showLabel:!0,showGrid:!0,labelInterpolationFnc:b.noop,type:void 0},axisY:{offset:40,position:"start",labelOffset:{x:0,y:0},showLabel:!0,showGrid:!0,labelInterpolationFnc:b.noop,type:void 0,scaleMinSpace:20,onlyInteger:!1},width:void 0,height:void 0,showLine:!0,showPoint:!0,showArea:!1,areaBase:0,lineSmooth:!0,showGridBackground:!1,low:void 0,high:void 0,chartPadding:{top:15,right:15,bottom:5,left:10},fullWidth:!1,reverseData:!1,classNames:{chart:"ct-chart-line",label:"ct-label",labelGroup:"ct-labels",series:"ct-series",line:"ct-line",point:"ct-point",area:"ct-area",grid:"ct-grid",gridGroup:"ct-grids",gridBackground:"ct-grid-background",vertical:"ct-vertical",horizontal:"ct-horizontal",start:"ct-start",end:"ct-end"}});b.Line=b.Base.extend({constructor:d,createChart:c})}(this||global,a),function(a,b){"use strict";function c(a){var c,d;a.distributeSeries?(c=b.normalizeData(this.data,a.reverseData,a.horizontalBars?"x":"y"),c.normalized.series=c.normalized.series.map(function(a){return[a]})):c=b.normalizeData(this.data,a.reverseData,a.horizontalBars?"x":"y"),this.svg=b.createSvg(this.container,a.width,a.height,a.classNames.chart+(a.horizontalBars?" "+a.classNames.horizontalBars:""));var f=this.svg.elem("g").addClass(a.classNames.gridGroup),g=this.svg.elem("g"),h=this.svg.elem("g").addClass(a.classNames.labelGroup);
9
- if(a.stackBars&&0!==c.normalized.series.length){var i=b.serialMap(c.normalized.series,function(){return Array.prototype.slice.call(arguments).map(function(a){return a}).reduce(function(a,b){return{x:a.x+(b&&b.x)||0,y:a.y+(b&&b.y)||0}},{x:0,y:0})});d=b.getHighLow([i],a,a.horizontalBars?"x":"y")}else d=b.getHighLow(c.normalized.series,a,a.horizontalBars?"x":"y");d.high=+a.high||(0===a.high?0:d.high),d.low=+a.low||(0===a.low?0:d.low);var j,k,l,m,n,o=b.createChartRect(this.svg,a,e.padding);k=a.distributeSeries&&a.stackBars?c.normalized.labels.slice(0,1):c.normalized.labels,a.horizontalBars?(j=m=void 0===a.axisX.type?new b.AutoScaleAxis(b.Axis.units.x,c.normalized.series,o,b.extend({},a.axisX,{highLow:d,referenceValue:0})):a.axisX.type.call(b,b.Axis.units.x,c.normalized.series,o,b.extend({},a.axisX,{highLow:d,referenceValue:0})),l=n=void 0===a.axisY.type?new b.StepAxis(b.Axis.units.y,c.normalized.series,o,{ticks:k}):a.axisY.type.call(b,b.Axis.units.y,c.normalized.series,o,a.axisY)):(l=m=void 0===a.axisX.type?new b.StepAxis(b.Axis.units.x,c.normalized.series,o,{ticks:k}):a.axisX.type.call(b,b.Axis.units.x,c.normalized.series,o,a.axisX),j=n=void 0===a.axisY.type?new b.AutoScaleAxis(b.Axis.units.y,c.normalized.series,o,b.extend({},a.axisY,{highLow:d,referenceValue:0})):a.axisY.type.call(b,b.Axis.units.y,c.normalized.series,o,b.extend({},a.axisY,{highLow:d,referenceValue:0})));var p=a.horizontalBars?o.x1+j.projectValue(0):o.y1-j.projectValue(0),q=[];l.createGridAndLabels(f,h,this.supportsForeignObject,a,this.eventEmitter),j.createGridAndLabels(f,h,this.supportsForeignObject,a,this.eventEmitter),a.showGridBackground&&b.createGridBackground(f,o,a.classNames.gridBackground,this.eventEmitter),c.raw.series.forEach(function(d,e){var f,h,i=e-(c.raw.series.length-1)/2;f=a.distributeSeries&&!a.stackBars?l.axisLength/c.normalized.series.length/2:a.distributeSeries&&a.stackBars?l.axisLength/2:l.axisLength/c.normalized.series[e].length/2,h=g.elem("g"),h.attr({"ct:series-name":d.name,"ct:meta":b.serialize(d.meta)}),h.addClass([a.classNames.series,d.className||a.classNames.series+"-"+b.alphaNumerate(e)].join(" ")),c.normalized.series[e].forEach(function(g,k){var r,s,t,u;if(u=a.distributeSeries&&!a.stackBars?e:a.distributeSeries&&a.stackBars?0:k,r=a.horizontalBars?{x:o.x1+j.projectValue(g&&g.x?g.x:0,k,c.normalized.series[e]),y:o.y1-l.projectValue(g&&g.y?g.y:0,u,c.normalized.series[e])}:{x:o.x1+l.projectValue(g&&g.x?g.x:0,u,c.normalized.series[e]),y:o.y1-j.projectValue(g&&g.y?g.y:0,k,c.normalized.series[e])},l instanceof b.StepAxis&&(l.options.stretch||(r[l.units.pos]+=f*(a.horizontalBars?-1:1)),r[l.units.pos]+=a.stackBars||a.distributeSeries?0:i*a.seriesBarDistance*(a.horizontalBars?-1:1)),t=q[k]||p,q[k]=t-(p-r[l.counterUnits.pos]),void 0!==g){var v={};v[l.units.pos+"1"]=r[l.units.pos],v[l.units.pos+"2"]=r[l.units.pos],!a.stackBars||"accumulate"!==a.stackMode&&a.stackMode?(v[l.counterUnits.pos+"1"]=p,v[l.counterUnits.pos+"2"]=r[l.counterUnits.pos]):(v[l.counterUnits.pos+"1"]=t,v[l.counterUnits.pos+"2"]=q[k]),v.x1=Math.min(Math.max(v.x1,o.x1),o.x2),v.x2=Math.min(Math.max(v.x2,o.x1),o.x2),v.y1=Math.min(Math.max(v.y1,o.y2),o.y1),v.y2=Math.min(Math.max(v.y2,o.y2),o.y1);var w=b.getMetaData(d,k);s=h.elem("line",v,a.classNames.bar).attr({"ct:value":[g.x,g.y].filter(b.isNumeric).join(","),"ct:meta":b.serialize(w)}),this.eventEmitter.emit("draw",b.extend({type:"bar",value:g,index:k,meta:w,series:d,seriesIndex:e,axisX:m,axisY:n,chartRect:o,group:h,element:s},v))}}.bind(this))}.bind(this)),this.eventEmitter.emit("created",{bounds:j.bounds,chartRect:o,axisX:m,axisY:n,svg:this.svg,options:a})}function d(a,c,d,f){b.Bar["super"].constructor.call(this,a,c,e,b.extend({},e,d),f)}var e=(a.window,a.document,{axisX:{offset:30,position:"end",labelOffset:{x:0,y:0},showLabel:!0,showGrid:!0,labelInterpolationFnc:b.noop,scaleMinSpace:30,onlyInteger:!1},axisY:{offset:40,position:"start",labelOffset:{x:0,y:0},showLabel:!0,showGrid:!0,labelInterpolationFnc:b.noop,scaleMinSpace:20,onlyInteger:!1},width:void 0,height:void 0,high:void 0,low:void 0,referenceValue:0,chartPadding:{top:15,right:15,bottom:5,left:10},seriesBarDistance:15,stackBars:!1,stackMode:"accumulate",horizontalBars:!1,distributeSeries:!1,reverseData:!1,showGridBackground:!1,classNames:{chart:"ct-chart-bar",horizontalBars:"ct-horizontal-bars",label:"ct-label",labelGroup:"ct-labels",series:"ct-series",bar:"ct-bar",grid:"ct-grid",gridGroup:"ct-grids",gridBackground:"ct-grid-background",vertical:"ct-vertical",horizontal:"ct-horizontal",start:"ct-start",end:"ct-end"}});b.Bar=b.Base.extend({constructor:d,createChart:c})}(this||global,a),function(a,b){"use strict";function c(a,b,c){var d=b.x>a.x;return d&&"explode"===c||!d&&"implode"===c?"start":d&&"implode"===c||!d&&"explode"===c?"end":"middle"}function d(a){var d,e,g,h,i,j=b.normalizeData(this.data),k=[],l=a.startAngle;this.svg=b.createSvg(this.container,a.width,a.height,a.donut?a.classNames.chartDonut:a.classNames.chartPie),e=b.createChartRect(this.svg,a,f.padding),g=Math.min(e.width()/2,e.height()/2),i=a.total||j.normalized.series.reduce(function(a,b){return a+b},0);var m=b.quantity(a.donutWidth);"%"===m.unit&&(m.value*=g/100),g-=a.donut&&!a.donutSolid?m.value/2:0,h="outside"===a.labelPosition||a.donut&&!a.donutSolid?g:"center"===a.labelPosition?0:a.donutSolid?g-m.value/2:g/2,h+=a.labelOffset;var n={x:e.x1+e.width()/2,y:e.y2+e.height()/2},o=1===j.raw.series.filter(function(a){return a.hasOwnProperty("value")?0!==a.value:0!==a}).length;j.raw.series.forEach(function(a,b){k[b]=this.svg.elem("g",null,null)}.bind(this)),a.showLabel&&(d=this.svg.elem("g",null,null)),j.raw.series.forEach(function(e,f){if(0!==j.normalized.series[f]||!a.ignoreEmptyValues){k[f].attr({"ct:series-name":e.name}),k[f].addClass([a.classNames.series,e.className||a.classNames.series+"-"+b.alphaNumerate(f)].join(" "));var p=i>0?l+j.normalized.series[f]/i*360:0,q=Math.max(0,l-(0===f||o?0:.2));p-q>=359.99&&(p=q+359.99);var r,s,t,u=b.polarToCartesian(n.x,n.y,g,q),v=b.polarToCartesian(n.x,n.y,g,p),w=new b.Svg.Path(!a.donut||a.donutSolid).move(v.x,v.y).arc(g,g,0,p-l>180,0,u.x,u.y);a.donut?a.donutSolid&&(t=g-m.value,r=b.polarToCartesian(n.x,n.y,t,l-(0===f||o?0:.2)),s=b.polarToCartesian(n.x,n.y,t,p),w.line(r.x,r.y),w.arc(t,t,0,p-l>180,1,s.x,s.y)):w.line(n.x,n.y);var x=a.classNames.slicePie;a.donut&&(x=a.classNames.sliceDonut,a.donutSolid&&(x=a.classNames.sliceDonutSolid));var y=k[f].elem("path",{d:w.stringify()},x);if(y.attr({"ct:value":j.normalized.series[f],"ct:meta":b.serialize(e.meta)}),a.donut&&!a.donutSolid&&(y._node.style.strokeWidth=m.value+"px"),this.eventEmitter.emit("draw",{type:"slice",value:j.normalized.series[f],totalDataSum:i,index:f,meta:e.meta,series:e,group:k[f],element:y,path:w.clone(),center:n,radius:g,startAngle:l,endAngle:p}),a.showLabel){var z;z=1===j.raw.series.length?{x:n.x,y:n.y}:b.polarToCartesian(n.x,n.y,h,l+(p-l)/2);var A;A=j.normalized.labels&&!b.isFalseyButZero(j.normalized.labels[f])?j.normalized.labels[f]:j.normalized.series[f];var B=a.labelInterpolationFnc(A,f);if(B||0===B){var C=d.elem("text",{dx:z.x,dy:z.y,"text-anchor":c(n,z,a.labelDirection)},a.classNames.label).text(""+B);this.eventEmitter.emit("draw",{type:"label",index:f,group:d,element:C,text:""+B,x:z.x,y:z.y})}}l=p}}.bind(this)),this.eventEmitter.emit("created",{chartRect:e,svg:this.svg,options:a})}function e(a,c,d,e){b.Pie["super"].constructor.call(this,a,c,f,b.extend({},f,d),e)}var f=(a.window,a.document,{width:void 0,height:void 0,chartPadding:5,classNames:{chartPie:"ct-chart-pie",chartDonut:"ct-chart-donut",series:"ct-series",slicePie:"ct-slice-pie",sliceDonut:"ct-slice-donut",sliceDonutSolid:"ct-slice-donut-solid",label:"ct-label"},startAngle:0,total:void 0,donut:!1,donutSolid:!1,donutWidth:60,showLabel:!0,labelOffset:0,labelPosition:"inside",labelInterpolationFnc:b.noop,labelDirection:"neutral",reverseData:!1,ignoreEmptyValues:!1});b.Pie=b.Base.extend({constructor:e,createChart:d,determineAnchorPosition:c})}(this||global,a),a});
10
- //# sourceMappingURL=chartist.min.js.map
 
 
 
 
 
 
 
 
 
 
resources/js/charts.js DELETED
@@ -1,131 +0,0 @@
1
- jQuery.fn.icwpWpsfChartWithFilters = function ( aOptions ) {
2
-
3
- let resetFilters = function ( evt ) {
4
- jQuery( 'input[type=text]', $oForm ).each( function () {
5
- jQuery( this ).val( '' );
6
- } );
7
- jQuery( 'select', $oForm ).each( function () {
8
- jQuery( this ).prop( 'selectedIndex', 0 );
9
- } );
10
- jQuery( 'input[type=checkbox]', $oForm ).each( function () {
11
- jQuery( this ).prop( 'checked', false );
12
- } );
13
- aOpts[ 'chart' ].renderChartFromForm( $oForm );
14
- };
15
-
16
- let submitFilters = function ( evt ) {
17
- evt.preventDefault();
18
- aOpts[ 'chart' ].renderChartFromForm( $oForm );
19
- return false;
20
- };
21
-
22
- let initialise = function () {
23
- jQuery( document ).ready( function () {
24
- $oForm = jQuery( aOpts[ 'selector_filter_form' ] );
25
- $oForm.on( 'change', submitFilters );
26
- $oForm.on( 'click', 'a#ClearForm', resetFilters );
27
- } );
28
- };
29
-
30
- let $oThis = this;
31
- let aOpts = jQuery.extend( {}, aOptions );
32
- let $oForm;
33
- initialise();
34
-
35
- return this;
36
- };
37
-
38
- jQuery.fn.icwpWpsfAjaxChart = function ( aOptions ) {
39
-
40
- this.reloadChart = function () {
41
- reqRenderChart();
42
- };
43
-
44
- let createChartContainer = function () {
45
- $oChartContainer = jQuery( '<div />' ).appendTo( $oThis );
46
- $oChartContainer.addClass( 'icwpAjaxContainerChart' )
47
- .addClass( 'ct-chart' );
48
- };
49
-
50
- let refreshChart = function ( event ) {
51
- event.preventDefault();
52
- let aChartRequestParams = {};
53
- reqRenderChart( aChartRequestParams );
54
- };
55
-
56
- this.renderChartFromForm = function ( $oForm ) {
57
- reqRenderChart( { 'form_params': $oForm.serialize() } );
58
- };
59
-
60
- let reqRenderChart = function ( aRequestParams ) {
61
- if ( bReqRunning ) {
62
- return false;
63
- }
64
- bReqRunning = true;
65
-
66
- $oChartContainer.html( 'Loading...' );
67
-
68
- jQuery.post( ajaxurl, jQuery.extend( aOpts[ 'ajax_render' ], aOpts[ 'req_params' ], aRequestParams ),
69
- function ( oResponse ) {
70
-
71
- $oChartContainer.html('');
72
- new Chartist.Line(
73
- $oThis.selector+' .icwpAjaxContainerChart',
74
- oResponse.data.chart.data,
75
- {
76
- height: '100px',
77
- fullWidth: true,
78
- showArea: false,
79
- chartPadding: {
80
- top: 10,
81
- right: 10,
82
- bottom: 10,
83
- left: 10
84
- },
85
- axisX: {
86
- offset: 5,
87
- showLabel: false,
88
- showGrid: false,
89
- },
90
- axisY: {
91
- offset: 25,
92
- onlyInteger: true,
93
- showLabel: true,
94
- labelInterpolationFnc: function ( value ) {
95
- return value;
96
- }
97
- },
98
- plugins: [
99
- Chartist.plugins.legend( {
100
- legendNames: oResponse.data.chart.legend_names
101
- } )
102
- ]
103
- }
104
- );
105
- }
106
- ).always(
107
- function () {
108
- bReqRunning = false;
109
- }
110
- );
111
- };
112
-
113
- let setHandlers = function () {
114
- };
115
-
116
- let initialise = function () {
117
- jQuery( document ).ready( function () {
118
- createChartContainer();
119
- reqRenderChart();
120
- setHandlers();
121
- } );
122
- };
123
-
124
- let $oThis = this;
125
- let $oChartContainer;
126
- let bReqRunning = false;
127
- let aOpts = jQuery.extend( {}, aOptions );
128
- initialise();
129
-
130
- return this;
131
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
resources/js/introjs.min.js DELETED
@@ -1 +0,0 @@
1
- !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t(),module.exports.introJs=function(){return console.warn('Deprecated: please use require("intro.js") directly, instead of the introJs method of the function'),t().apply(this,arguments)};else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).introJs=t()}}(function(){function n(t){this._targetElement=t,this._introItems=[],this._options={nextLabel:"Next &rarr;",prevLabel:"&larr; Back",skipLabel:"Skip",doneLabel:"Done",hidePrev:!1,hideNext:!1,tooltipPosition:"bottom",tooltipClass:"",highlightClass:"",exitOnEsc:!0,exitOnOverlayClick:!0,showStepNumbers:!0,keyboardNavigation:!0,showButtons:!0,showBullets:!0,showProgress:!1,scrollToElement:!0,scrollTo:"element",scrollPadding:30,overlayOpacity:.8,positionPrecedence:["bottom","top","right","left"],disableInteraction:!1,helperElementPadding:10,hintPosition:"top-middle",hintButtonLabel:"Got it",hintAnimation:!0,buttonClass:"introjs-button"}}function e(t,i){var e=t.querySelectorAll("*[data-intro]"),n=[];if(this._options.steps)B(this._options.steps,function(t){var e=h(t);if(e.step=n.length+1,"string"==typeof e.element&&(e.element=document.querySelector(e.element)),void 0===e.element||null===e.element){var i=document.querySelector(".introjsFloatingElement");null===i&&((i=document.createElement("div")).className="introjsFloatingElement",document.body.appendChild(i)),e.element=i,e.position="floating"}e.scrollTo=e.scrollTo||this._options.scrollTo,void 0===e.disableInteraction&&(e.disableInteraction=this._options.disableInteraction),null!==e.element&&n.push(e)}.bind(this));else{var o;if(e.length<1)return!1;B(e,function(t){if((!i||t.getAttribute("data-intro-group")===i)&&"none"!==t.style.display){var e=parseInt(t.getAttribute("data-step"),10);o=void 0!==t.getAttribute("data-disable-interaction")?!!t.getAttribute("data-disable-interaction"):this._options.disableInteraction,0<e&&(n[e-1]={element:t,intro:t.getAttribute("data-intro"),step:parseInt(t.getAttribute("data-step"),10),tooltipClass:t.getAttribute("data-tooltipclass"),highlightClass:t.getAttribute("data-highlightclass"),position:t.getAttribute("data-position")||this._options.tooltipPosition,scrollTo:t.getAttribute("data-scrollto")||this._options.scrollTo,disableInteraction:o})}}.bind(this));var s=0;B(e,function(t){if((!i||t.getAttribute("data-intro-group")===i)&&null===t.getAttribute("data-step")){for(;void 0!==n[s];)s++;o=void 0!==t.getAttribute("data-disable-interaction")?!!t.getAttribute("data-disable-interaction"):this._options.disableInteraction,n[s]={element:t,intro:t.getAttribute("data-intro"),step:s+1,tooltipClass:t.getAttribute("data-tooltipclass"),highlightClass:t.getAttribute("data-highlightclass"),position:t.getAttribute("data-position")||this._options.tooltipPosition,scrollTo:t.getAttribute("data-scrollto")||this._options.scrollTo,disableInteraction:o}}}.bind(this))}for(var l=[],r=0;r<n.length;r++)n[r]&&l.push(n[r]);return(n=l).sort(function(t,e){return t.step-e.step}),this._introItems=n,function(t){var e=document.createElement("div"),i="",n=this;if(e.className="introjs-overlay",t.tagName&&"body"!==t.tagName.toLowerCase()){var o=k(t);o&&(i+="width: "+o.width+"px; height:"+o.height+"px; top:"+o.top+"px;left: "+o.left+"px;",e.style.cssText=i)}else i+="top: 0;bottom: 0; left: 0;right: 0;position: fixed;",e.style.cssText=i;return t.appendChild(e),e.onclick=function(){!0===n._options.exitOnOverlayClick&&A.call(n,t)},window.setTimeout(function(){i+="opacity: "+n._options.overlayOpacity.toString()+";",e.style.cssText=i},10),!0}.call(this,t)&&(E.call(this),this._options.keyboardNavigation&&u.on(window,"keydown",c,this,!0),u.on(window,"resize",a,this,!0)),!1}function a(){this.refresh.call(this)}function c(t){var e=null===t.code?t.which:t.code;if(null===e&&(e=null===t.charCode?t.keyCode:t.charCode),"Escape"!==e&&27!==e||!0!==this._options.exitOnEsc){if("ArrowLeft"===e||37===e)N.call(this);else if("ArrowRight"===e||39===e)E.call(this);else if("Enter"===e||13===e){var i=t.target||t.srcElement;i&&i.className.match("introjs-prevbutton")?N.call(this):i&&i.className.match("introjs-skipbutton")?(this._introItems.length-1===this._currentStep&&"function"==typeof this._introCompleteCallback&&this._introCompleteCallback.call(this),A.call(this,this._targetElement)):i&&i.getAttribute("data-stepnumber")?i.click():E.call(this),t.preventDefault?t.preventDefault():t.returnValue=!1}}else A.call(this,this._targetElement)}function h(t){if(null===t||"object"!=typeof t||void 0!==t.nodeType)return t;var e={};for(var i in t)void 0!==window.jQuery&&t[i]instanceof window.jQuery?e[i]=t[i]:e[i]=h(t[i]);return e}function E(){this._direction="forward",void 0!==this._currentStepNumber&&B(this._introItems,function(t,e){t.step===this._currentStepNumber&&(this._currentStep=e-1,this._currentStepNumber=void 0)}.bind(this)),void 0===this._currentStep?this._currentStep=0:++this._currentStep;var t=this._introItems[this._currentStep],e=!0;return void 0!==this._introBeforeChangeCallback&&(e=this._introBeforeChangeCallback.call(this,t.element)),!1===e?(--this._currentStep,!1):this._introItems.length<=this._currentStep?("function"==typeof this._introCompleteCallback&&this._introCompleteCallback.call(this),void A.call(this,this._targetElement)):void i.call(this,t)}function N(){if(this._direction="backward",0===this._currentStep)return!1;--this._currentStep;var t=this._introItems[this._currentStep],e=!0;if(void 0!==this._introBeforeChangeCallback&&(e=this._introBeforeChangeCallback.call(this,t.element)),!1===e)return++this._currentStep,!1;i.call(this,t)}function A(t,e){var i=!0;if(void 0!==this._introBeforeExitCallback&&(i=this._introBeforeExitCallback.call(this)),e||!1!==i){var n=t.querySelectorAll(".introjs-overlay");n&&n.length&&B(n,function(t){t.style.opacity=0,window.setTimeout(function(){this.parentNode&&this.parentNode.removeChild(this)}.bind(t),500)}.bind(this));var o=t.querySelector(".introjs-helperLayer");o&&o.parentNode.removeChild(o);var s=t.querySelector(".introjs-tooltipReferenceLayer");s&&s.parentNode.removeChild(s);var l=t.querySelector(".introjs-disableInteraction");l&&l.parentNode.removeChild(l);var r=document.querySelector(".introjsFloatingElement");r&&r.parentNode.removeChild(r),q(),B(document.querySelectorAll(".introjs-fixParent"),function(t){O(t,/introjs-fixParent/g)}),u.off(window,"keydown",c,this,!0),u.off(window,"resize",a,this,!0),void 0!==this._introExitCallback&&this._introExitCallback.call(this),this._currentStep=void 0}}function L(t,e,i,n,o){var s,l,r,a,c,h="";if(o=o||!1,e.style.top=null,e.style.right=null,e.style.bottom=null,e.style.left=null,e.style.marginLeft=null,e.style.marginTop=null,i.style.display="inherit",null!=n&&(n.style.top=null,n.style.left=null),this._introItems[this._currentStep])switch(h="string"==typeof(s=this._introItems[this._currentStep]).tooltipClass?s.tooltipClass:this._options.tooltipClass,e.className=("introjs-tooltip "+h).replace(/^\s+|\s+$/g,""),e.setAttribute("role","dialog"),"floating"!==(c=this._introItems[this._currentStep].position)&&(c=function(t,e,i){var n=this._options.positionPrecedence.slice(),o=b(),s=k(e).height+10,l=k(e).width+20,r=t.getBoundingClientRect(),a="floating";r.bottom+s+s>o.height&&m(n,"bottom");r.top-s<0&&m(n,"top");r.right+l>o.width&&m(n,"right");r.left-l<0&&m(n,"left");var c=(h=i||"",u=h.indexOf("-"),-1!==u?h.substr(u):"");var h,u;i&&(i=i.split("-")[0]);n.length&&(a="auto"!==i&&-1<n.indexOf(i)?i:n[0]);-1!==["top","bottom"].indexOf(a)&&(a+=function(t,e,i,n){var o=e/2,s=Math.min(i.width,window.screen.width),l=["-left-aligned","-middle-aligned","-right-aligned"],r="";s-t<e&&m(l,"-left-aligned");(t<o||s-t<o)&&m(l,"-middle-aligned");t<e&&m(l,"-right-aligned");r=l.length?-1!==l.indexOf(n)?n:l[0]:"-middle-aligned";return r}(r.left,l,o,c));return a}.call(this,t,e,c)),r=k(t),l=k(e),a=b(),H(e,"introjs-"+c),c){case"top-right-aligned":i.className="introjs-arrow bottom-right";var u=0;f(r,u,l,e),e.style.bottom=r.height+20+"px";break;case"top-middle-aligned":i.className="introjs-arrow bottom-middle";var d=r.width/2-l.width/2;o&&(d+=5),f(r,d,l,e)&&(e.style.right=null,p(r,d,l,a,e)),e.style.bottom=r.height+20+"px";break;case"top-left-aligned":case"top":i.className="introjs-arrow bottom",p(r,o?0:15,l,a,e),e.style.bottom=r.height+20+"px";break;case"right":e.style.left=r.width+20+"px",r.top+l.height>a.height?(i.className="introjs-arrow left-bottom",e.style.top="-"+(l.height-r.height-20)+"px"):i.className="introjs-arrow left";break;case"left":o||!0!==this._options.showStepNumbers||(e.style.top="15px"),r.top+l.height>a.height?(e.style.top="-"+(l.height-r.height-20)+"px",i.className="introjs-arrow right-bottom"):i.className="introjs-arrow right",e.style.right=r.width+20+"px";break;case"floating":i.style.display="none",e.style.left="50%",e.style.top="50%",e.style.marginLeft="-"+l.width/2+"px",e.style.marginTop="-"+l.height/2+"px",null!=n&&(n.style.left="-"+(l.width/2+18)+"px",n.style.top="-"+(l.height/2+18)+"px");break;case"bottom-right-aligned":i.className="introjs-arrow top-right",f(r,u=0,l,e),e.style.top=r.height+20+"px";break;case"bottom-middle-aligned":i.className="introjs-arrow top-middle",d=r.width/2-l.width/2,o&&(d+=5),f(r,d,l,e)&&(e.style.right=null,p(r,d,l,a,e)),e.style.top=r.height+20+"px";break;default:i.className="introjs-arrow top",p(r,0,l,a,e),e.style.top=r.height+20+"px"}}function p(t,e,i,n,o){return t.left+e+i.width>n.width?(o.style.left=n.width-i.width-t.left+"px",!1):(o.style.left=e+"px",!0)}function f(t,e,i,n){return t.left+t.width-e-i.width<0?(n.style.left=-t.left+"px",!1):(n.style.right=e+"px",!0)}function m(t,e){-1<t.indexOf(e)&&t.splice(t.indexOf(e),1)}function T(t){if(t){if(!this._introItems[this._currentStep])return;var e=this._introItems[this._currentStep],i=k(e.element),n=this._options.helperElementPadding;d(e.element)?H(t,"introjs-fixedTooltip"):O(t,"introjs-fixedTooltip"),"floating"===e.position&&(n=0),t.style.cssText="width: "+(i.width+n)+"px; height:"+(i.height+n)+"px; top:"+(i.top-n/2)+"px;left: "+(i.left-n/2)+"px;"}}function I(t){t.setAttribute("role","button"),t.tabIndex=0}function i(o){void 0!==this._introChangeCallback&&this._introChangeCallback.call(this,o.element);var t,e,i,n,s=this,l=document.querySelector(".introjs-helperLayer"),r=document.querySelector(".introjs-tooltipReferenceLayer"),a="introjs-helperLayer";if("string"==typeof o.highlightClass&&(a+=" "+o.highlightClass),"string"==typeof this._options.highlightClass&&(a+=" "+this._options.highlightClass),null!==l){var c=r.querySelector(".introjs-helperNumberLayer"),h=r.querySelector(".introjs-tooltiptext"),u=r.querySelector(".introjs-arrow"),d=r.querySelector(".introjs-tooltip");if(i=r.querySelector(".introjs-skipbutton"),e=r.querySelector(".introjs-prevbutton"),t=r.querySelector(".introjs-nextbutton"),l.className=a,d.style.opacity=0,d.style.display="none",null!==c){var p=this._introItems[0<=o.step-2?o.step-2:0];(null!==p&&"forward"===this._direction&&"floating"===p.position||"backward"===this._direction&&"floating"===o.position)&&(c.style.opacity=0)}(n=R(o.element))!==document.body&&V(n,o.element),T.call(s,l),T.call(s,r),B(document.querySelectorAll(".introjs-fixParent"),function(t){O(t,/introjs-fixParent/g)}),q(),s._lastShowElementTimer&&window.clearTimeout(s._lastShowElementTimer),s._lastShowElementTimer=window.setTimeout(function(){null!==c&&(c.innerHTML=o.step),h.innerHTML=o.intro,d.style.display="block",L.call(s,o.element,d,u,c),s._options.showBullets&&(r.querySelector(".introjs-bullets li > a.active").className="",r.querySelector('.introjs-bullets li > a[data-stepnumber="'+o.step+'"]').className="active"),r.querySelector(".introjs-progress .introjs-progressbar").style.cssText="width:"+z.call(s)+"%;",r.querySelector(".introjs-progress .introjs-progressbar").setAttribute("aria-valuenow",z.call(s)),d.style.opacity=1,c&&(c.style.opacity=1),null!=i&&/introjs-donebutton/gi.test(i.className)?i.focus():null!=t&&t.focus(),P.call(s,o.scrollTo,o,h)},350)}else{var f=document.createElement("div"),m=document.createElement("div"),b=document.createElement("div"),g=document.createElement("div"),y=document.createElement("div"),v=document.createElement("div"),_=document.createElement("div"),w=document.createElement("div");f.className=a,m.className="introjs-tooltipReferenceLayer",(n=R(o.element))!==document.body&&V(n,o.element),T.call(s,f),T.call(s,m),this._targetElement.appendChild(f),this._targetElement.appendChild(m),b.className="introjs-arrow",y.className="introjs-tooltiptext",y.innerHTML=o.intro,!(v.className="introjs-bullets")===this._options.showBullets&&(v.style.display="none");var C=document.createElement("ul");C.setAttribute("role","tablist");var j=function(){s.goToStep(this.getAttribute("data-stepnumber"))};B(this._introItems,function(t,e){var i=document.createElement("li"),n=document.createElement("a");i.setAttribute("role","presentation"),n.setAttribute("role","tab"),n.onclick=j,e===o.step-1&&(n.className="active"),I(n),n.innerHTML="&nbsp;",n.setAttribute("data-stepnumber",t.step),i.appendChild(n),C.appendChild(i)}),v.appendChild(C),!(_.className="introjs-progress")===this._options.showProgress&&(_.style.display="none");var k=document.createElement("div");k.className="introjs-progressbar",k.setAttribute("role","progress"),k.setAttribute("aria-valuemin",0),k.setAttribute("aria-valuemax",100),k.setAttribute("aria-valuenow",z.call(this)),k.style.cssText="width:"+z.call(this)+"%;",_.appendChild(k),!(w.className="introjs-tooltipbuttons")===this._options.showButtons&&(w.style.display="none"),g.className="introjs-tooltip",g.appendChild(y),g.appendChild(v),g.appendChild(_);var x=document.createElement("span");!0===this._options.showStepNumbers&&(x.className="introjs-helperNumberLayer",x.innerHTML=o.step,m.appendChild(x)),g.appendChild(b),m.appendChild(g),(t=document.createElement("a")).onclick=function(){s._introItems.length-1!==s._currentStep&&E.call(s)},I(t),t.innerHTML=this._options.nextLabel,(e=document.createElement("a")).onclick=function(){0!==s._currentStep&&N.call(s)},I(e),e.innerHTML=this._options.prevLabel,(i=document.createElement("a")).className=this._options.buttonClass+" introjs-skipbutton ",I(i),i.innerHTML=this._options.skipLabel,i.onclick=function(){s._introItems.length-1===s._currentStep&&"function"==typeof s._introCompleteCallback&&s._introCompleteCallback.call(s),s._introItems.length-1!==s._currentStep&&"function"==typeof s._introExitCallback&&s._introExitCallback.call(s),"function"==typeof s._introSkipCallback&&s._introSkipCallback.call(s),A.call(s,s._targetElement)},w.appendChild(i),1<this._introItems.length&&(w.appendChild(e),w.appendChild(t)),g.appendChild(w),L.call(s,o.element,g,b,x),P.call(this,o.scrollTo,o,g)}var S=s._targetElement.querySelector(".introjs-disableInteraction");S&&S.parentNode.removeChild(S),o.disableInteraction&&function(){var t=document.querySelector(".introjs-disableInteraction");null===t&&((t=document.createElement("div")).className="introjs-disableInteraction",this._targetElement.appendChild(t)),T.call(this,t)}.call(s),0===this._currentStep&&1<this._introItems.length?(null!=i&&(i.className=this._options.buttonClass+" introjs-skipbutton"),null!=t&&(t.className=this._options.buttonClass+" introjs-nextbutton"),!0===this._options.hidePrev?(null!=e&&(e.className=this._options.buttonClass+" introjs-prevbutton introjs-hidden"),null!=t&&H(t,"introjs-fullbutton")):null!=e&&(e.className=this._options.buttonClass+" introjs-prevbutton introjs-disabled"),null!=i&&(i.innerHTML=this._options.skipLabel)):this._introItems.length-1===this._currentStep||1===this._introItems.length?(null!=i&&(i.innerHTML=this._options.doneLabel,H(i,"introjs-donebutton")),null!=e&&(e.className=this._options.buttonClass+" introjs-prevbutton"),!0===this._options.hideNext?(null!=t&&(t.className=this._options.buttonClass+" introjs-nextbutton introjs-hidden"),null!=e&&H(e,"introjs-fullbutton")):null!=t&&(t.className=this._options.buttonClass+" introjs-nextbutton introjs-disabled")):(null!=i&&(i.className=this._options.buttonClass+" introjs-skipbutton"),null!=e&&(e.className=this._options.buttonClass+" introjs-prevbutton"),null!=t&&(t.className=this._options.buttonClass+" introjs-nextbutton"),null!=i&&(i.innerHTML=this._options.skipLabel)),e.setAttribute("role","button"),t.setAttribute("role","button"),i.setAttribute("role","button"),null!=t&&t.focus(),function(t){var e;if(t.element instanceof SVGElement)for(e=t.element.parentNode;null!==t.element.parentNode&&e.tagName&&"body"!==e.tagName.toLowerCase();)"svg"===e.tagName.toLowerCase()&&H(e,"introjs-showElement introjs-relativePosition"),e=e.parentNode;H(t.element,"introjs-showElement");var i=M(t.element,"position");"absolute"!==i&&"relative"!==i&&"fixed"!==i&&H(t.element,"introjs-relativePosition");e=t.element.parentNode;for(;null!==e&&e.tagName&&"body"!==e.tagName.toLowerCase();){var n=M(e,"z-index"),o=parseFloat(M(e,"opacity")),s=M(e,"transform")||M(e,"-webkit-transform")||M(e,"-moz-transform")||M(e,"-ms-transform")||M(e,"-o-transform");(/[0-9]+/.test(n)||o<1||"none"!==s&&void 0!==s)&&H(e,"introjs-fixParent"),e=e.parentNode}}(o),void 0!==this._introAfterChangeCallback&&this._introAfterChangeCallback.call(this,o.element)}function P(t,e,i){var n,o,s;if("off"!==t&&(this._options.scrollToElement&&(n="tooltip"===t?i.getBoundingClientRect():e.element.getBoundingClientRect(),o=e.element,!(0<=(s=o.getBoundingClientRect()).top&&0<=s.left&&s.bottom+80<=window.innerHeight&&s.right<=window.innerWidth)))){var l=b().height;n.bottom-(n.bottom-n.top)<0||e.element.clientHeight>l?window.scrollBy(0,n.top-(l/2-n.height/2)-this._options.scrollPadding):window.scrollBy(0,n.top-(l/2-n.height/2)+this._options.scrollPadding)}}function q(){B(document.querySelectorAll(".introjs-showElement"),function(t){O(t,/introjs-[a-zA-Z]+/g)})}function B(t,e,i){if(t)for(var n=0,o=t.length;n<o;n++)e(t[n],n);"function"==typeof i&&i()}var o,s=(o={},function(t,e){return o[e=e||"introjs-stamp"]=o[e]||0,void 0===t[e]&&(t[e]=o[e]++),t[e]}),u=new function(){var r="introjs_event";this._id=function(t,e,i,n){return e+s(i)+(n?"_"+s(n):"")},this.on=function(e,t,i,n,o){var s=this._id.apply(this,arguments),l=function(t){return i.call(n||e,t||window.event)};"addEventListener"in e?e.addEventListener(t,l,o):"attachEvent"in e&&e.attachEvent("on"+t,l),e[r]=e[r]||{},e[r][s]=l},this.off=function(t,e,i,n,o){var s=this._id.apply(this,arguments),l=t[r]&&t[r][s];l&&("removeEventListener"in t?t.removeEventListener(e,l,o):"detachEvent"in t&&t.detachEvent("on"+e,l),t[r][s]=null)}};function H(e,t){if(e instanceof SVGElement){var i=e.getAttribute("class")||"";e.setAttribute("class",i+" "+t)}else{if(void 0!==e.classList)B(t.split(" "),function(t){e.classList.add(t)});else e.className.match(t)||(e.className+=" "+t)}}function O(t,e){if(t instanceof SVGElement){var i=t.getAttribute("class")||"";t.setAttribute("class",i.replace(e,"").replace(/^\s+|\s+$/g,""))}else t.className=t.className.replace(e,"").replace(/^\s+|\s+$/g,"")}function M(t,e){var i="";return t.currentStyle?i=t.currentStyle[e]:document.defaultView&&document.defaultView.getComputedStyle&&(i=document.defaultView.getComputedStyle(t,null).getPropertyValue(e)),i&&i.toLowerCase?i.toLowerCase():i}function d(t){var e=t.parentNode;return!(!e||"HTML"===e.nodeName)&&("fixed"===M(t,"position")||d(e))}function b(){if(void 0!==window.innerWidth)return{width:window.innerWidth,height:window.innerHeight};var t=document.documentElement;return{width:t.clientWidth,height:t.clientHeight}}function g(){var t=document.querySelector(".introjs-hintReference");if(t){var e=t.getAttribute("data-step");return t.parentNode.removeChild(t),e}}function l(t){if(this._introItems=[],this._options.hints)B(this._options.hints,function(t){var e=h(t);"string"==typeof e.element&&(e.element=document.querySelector(e.element)),e.hintPosition=e.hintPosition||this._options.hintPosition,e.hintAnimation=e.hintAnimation||this._options.hintAnimation,null!==e.element&&this._introItems.push(e)}.bind(this));else{var e=t.querySelectorAll("*[data-hint]");if(!e||!e.length)return!1;B(e,function(t){var e=t.getAttribute("data-hintanimation");e=e?"true"===e:this._options.hintAnimation,this._introItems.push({element:t,hint:t.getAttribute("data-hint"),hintPosition:t.getAttribute("data-hintposition")||this._options.hintPosition,hintAnimation:e,tooltipClass:t.getAttribute("data-tooltipclass"),position:t.getAttribute("data-position")||this._options.tooltipPosition})}.bind(this))}(function(){var l=this,r=document.querySelector(".introjs-hints");null===r&&((r=document.createElement("div")).className="introjs-hints");B(this._introItems,function(t,e){if(!document.querySelector('.introjs-hint[data-step="'+e+'"]')){var i,n=document.createElement("a");I(n),n.onclick=(i=e,function(t){var e=t||window.event;e.stopPropagation&&e.stopPropagation(),null!==e.cancelBubble&&(e.cancelBubble=!0),j.call(l,i)}),n.className="introjs-hint",t.hintAnimation||H(n,"introjs-hint-no-anim"),d(t.element)&&H(n,"introjs-fixedhint");var o=document.createElement("div");o.className="introjs-hint-dot";var s=document.createElement("div");s.className="introjs-hint-pulse",n.appendChild(o),n.appendChild(s),n.setAttribute("data-step",e),t.targetElement=t.element,t.element=n,C.call(this,t.hintPosition,n,t.targetElement),r.appendChild(n)}}.bind(this)),document.body.appendChild(r),void 0!==this._hintsAddedCallback&&this._hintsAddedCallback.call(this)}).call(this),u.on(document,"click",g,this,!1),u.on(window,"resize",r,this,!0)}function r(){B(this._introItems,function(t){void 0!==t.targetElement&&C.call(this,t.hintPosition,t.element,t.targetElement)}.bind(this))}function y(t){var e=document.querySelector(".introjs-hints");return e?e.querySelectorAll(t):[]}function v(t){var e=y('.introjs-hint[data-step="'+t+'"]')[0];g.call(this),e&&H(e,"introjs-hidehint"),void 0!==this._hintCloseCallback&&this._hintCloseCallback.call(this,t)}function _(t){var e=y('.introjs-hint[data-step="'+t+'"]')[0];e&&O(e,/introjs-hidehint/g)}function w(t){var e=y('.introjs-hint[data-step="'+t+'"]')[0];e&&e.parentNode.removeChild(e)}function C(t,e,i){var n=k.call(this,i);switch(t){default:case"top-left":e.style.left=n.left+"px",e.style.top=n.top+"px";break;case"top-right":e.style.left=n.left+n.width-20+"px",e.style.top=n.top+"px";break;case"bottom-left":e.style.left=n.left+"px",e.style.top=n.top+n.height-20+"px";break;case"bottom-right":e.style.left=n.left+n.width-20+"px",e.style.top=n.top+n.height-20+"px";break;case"middle-left":e.style.left=n.left+"px",e.style.top=n.top+(n.height-20)/2+"px";break;case"middle-right":e.style.left=n.left+n.width-20+"px",e.style.top=n.top+(n.height-20)/2+"px";break;case"middle-middle":e.style.left=n.left+(n.width-20)/2+"px",e.style.top=n.top+(n.height-20)/2+"px";break;case"bottom-middle":e.style.left=n.left+(n.width-20)/2+"px",e.style.top=n.top+n.height-20+"px";break;case"top-middle":e.style.left=n.left+(n.width-20)/2+"px",e.style.top=n.top+"px"}}function j(t){var e=document.querySelector('.introjs-hint[data-step="'+t+'"]'),i=this._introItems[t];void 0!==this._hintClickCallback&&this._hintClickCallback.call(this,e,i,t);var n=g.call(this);if(parseInt(n,10)!==t){var o=document.createElement("div"),s=document.createElement("div"),l=document.createElement("div"),r=document.createElement("div");o.className="introjs-tooltip",o.onclick=function(t){t.stopPropagation?t.stopPropagation():t.cancelBubble=!0},s.className="introjs-tooltiptext";var a=document.createElement("p");a.innerHTML=i.hint;var c=document.createElement("a");c.className=this._options.buttonClass,c.setAttribute("role","button"),c.innerHTML=this._options.hintButtonLabel,c.onclick=v.bind(this,t),s.appendChild(a),s.appendChild(c),l.className="introjs-arrow",o.appendChild(l),o.appendChild(s),this._currentStep=e.getAttribute("data-step"),r.className="introjs-tooltipReferenceLayer introjs-hintReference",r.setAttribute("data-step",e.getAttribute("data-step")),T.call(this,r),r.appendChild(o),document.body.appendChild(r),L.call(this,e,o,l,null,!0)}}function k(t){var e=document.body,i=document.documentElement,n=window.pageYOffset||i.scrollTop||e.scrollTop,o=window.pageXOffset||i.scrollLeft||e.scrollLeft,s=t.getBoundingClientRect();return{top:s.top+n,width:s.width,height:s.height,left:s.left+o}}function R(t){var e=window.getComputedStyle(t),i="absolute"===e.position,n=/(auto|scroll)/;if("fixed"===e.position)return document.body;for(var o=t;o=o.parentElement;)if(e=window.getComputedStyle(o),(!i||"static"!==e.position)&&n.test(e.overflow+e.overflowY+e.overflowX))return o;return document.body}function V(t,e){t.scrollTop=e.offsetTop-t.offsetTop}function z(){return parseInt(this._currentStep+1,10)/this._introItems.length*100}var x=function(t){var e;if("object"==typeof t)e=new n(t);else if("string"==typeof t){var i=document.querySelector(t);if(!i)throw new Error("There is no element with given selector.");e=new n(i)}else e=new n(document.body);return x.instances[s(e,"introjs-instance")]=e};return x.version="2.9.3",x.instances={},x.fn=n.prototype={clone:function(){return new n(this)},setOption:function(t,e){return this._options[t]=e,this},setOptions:function(t){return this._options=function(t,e){var i,n={};for(i in t)n[i]=t[i];for(i in e)n[i]=e[i];return n}(this._options,t),this},start:function(t){return e.call(this,this._targetElement,t),this},goToStep:function(t){return function(t){this._currentStep=t-2,void 0!==this._introItems&&E.call(this)}.call(this,t),this},addStep:function(t){return this._options.steps||(this._options.steps=[]),this._options.steps.push(t),this},addSteps:function(t){if(t.length){for(var e=0;e<t.length;e++)this.addStep(t[e]);return this}},goToStepNumber:function(t){return function(t){this._currentStepNumber=t,void 0!==this._introItems&&E.call(this)}.call(this,t),this},nextStep:function(){return E.call(this),this},previousStep:function(){return N.call(this),this},exit:function(t){return A.call(this,this._targetElement,t),this},refresh:function(){return function(){if(T.call(this,document.querySelector(".introjs-helperLayer")),T.call(this,document.querySelector(".introjs-tooltipReferenceLayer")),T.call(this,document.querySelector(".introjs-disableInteraction")),void 0!==this._currentStep&&null!==this._currentStep){var t=document.querySelector(".introjs-helperNumberLayer"),e=document.querySelector(".introjs-arrow"),i=document.querySelector(".introjs-tooltip");L.call(this,this._introItems[this._currentStep].element,i,e,t)}return r.call(this),this}.call(this),this},onbeforechange:function(t){if("function"!=typeof t)throw new Error("Provided callback for onbeforechange was not a function");return this._introBeforeChangeCallback=t,this},onchange:function(t){if("function"!=typeof t)throw new Error("Provided callback for onchange was not a function.");return this._introChangeCallback=t,this},onafterchange:function(t){if("function"!=typeof t)throw new Error("Provided callback for onafterchange was not a function");return this._introAfterChangeCallback=t,this},oncomplete:function(t){if("function"!=typeof t)throw new Error("Provided callback for oncomplete was not a function.");return this._introCompleteCallback=t,this},onhintsadded:function(t){if("function"!=typeof t)throw new Error("Provided callback for onhintsadded was not a function.");return this._hintsAddedCallback=t,this},onhintclick:function(t){if("function"!=typeof t)throw new Error("Provided callback for onhintclick was not a function.");return this._hintClickCallback=t,this},onhintclose:function(t){if("function"!=typeof t)throw new Error("Provided callback for onhintclose was not a function.");return this._hintCloseCallback=t,this},onexit:function(t){if("function"!=typeof t)throw new Error("Provided callback for onexit was not a function.");return this._introExitCallback=t,this},onskip:function(t){if("function"!=typeof t)throw new Error("Provided callback for onskip was not a function.");return this._introSkipCallback=t,this},onbeforeexit:function(t){if("function"!=typeof t)throw new Error("Provided callback for onbeforeexit was not a function.");return this._introBeforeExitCallback=t,this},addHints:function(){return l.call(this,this._targetElement),this},hideHint:function(t){return v.call(this,t),this},hideHints:function(){return function(){B(y(".introjs-hint"),function(t){v.call(this,t.getAttribute("data-step"))}.bind(this))}.call(this),this},showHint:function(t){return _.call(this,t),this},showHints:function(){return function(){var t=y(".introjs-hint");t&&t.length?B(t,function(t){_.call(this,t.getAttribute("data-step"))}.bind(this)):l.call(this,this._targetElement)}.call(this),this},removeHints:function(){return function(){B(y(".introjs-hint"),function(t){w.call(this,t.getAttribute("data-step"))}.bind(this))}.call(this),this},removeHint:function(t){return w.call(this,t),this},showHintDialog:function(t){return j.call(this,t),this}},x});
 
resources/js/plugin.js CHANGED
@@ -70,15 +70,6 @@ let iCWP_WPSF_OptsPageRender = new function () {
70
  };
71
  }();
72
 
73
- if ( typeof icwp_wpsf_vars_tourmanager !== 'undefined' ) {
74
- var iCWP_WPSF_MarkTourFinished = new function () {
75
- this.finishedTour = function ( sTourKey ) {
76
- icwp_wpsf_vars_tourmanager.ajax[ 'tour_key' ] = sTourKey;
77
- jQuery.post( ajaxurl, icwp_wpsf_vars_tourmanager.ajax ).always();
78
- };
79
- }();
80
- }
81
-
82
  var iCWP_WPSF_Toaster = new function () {
83
 
84
  this.showMessage = function ( msg, success ) {
@@ -158,7 +149,6 @@ var iCWP_WPSF_OptionsFormSubmit = new function () {
158
  },
159
  }
160
  ).fail( function () {
161
- alert( 'fail()' );
162
  if ( useCompression ) {
163
  handleResponse( raw );
164
  }
@@ -414,4 +404,11 @@ if ( typeof icwp_wpsf_vars_plugin !== 'undefined' ) {
414
  return false;
415
  } );
416
  } );
417
- }
 
 
 
 
 
 
 
70
  };
71
  }();
72
 
 
 
 
 
 
 
 
 
 
73
  var iCWP_WPSF_Toaster = new function () {
74
 
75
  this.showMessage = function ( msg, success ) {
149
  },
150
  }
151
  ).fail( function () {
 
152
  if ( useCompression ) {
153
  handleResponse( raw );
154
  }
404
  return false;
405
  } );
406
  } );
407
+ }
408
+
409
+ jQuery( document ).ready( function () {
410
+ jQuery( document ).icwpWpsfTours();
411
+ jQuery( '.select2picker' ).select2({
412
+ width: 'resolve'
413
+ });
414
+ } );
resources/js/shield-comments.js DELETED
@@ -1,137 +0,0 @@
1
- /** @var shield_comments object */
2
- if ( typeof shield_comments !== 'undefined' ) {
3
- var iCWP_WPSF_ShieldCommentGuard = new function () {
4
-
5
- var submitButton;
6
- var origButtonValue;
7
- var nTimerCounter;
8
- var sCountdownTimer;
9
-
10
- this.initialise = function () {
11
- jQuery( document ).ready( function () {
12
- insertPlaceHolder_Gasp( this );
13
- } );
14
- };
15
-
16
- var reEnableButton = function () {
17
- let nRemaining = shield_comments.vars.cooldown - nTimerCounter;
18
- submitButton.value = shield_comments.strings.js_comment_wait.replace( "%s", nRemaining );
19
- if ( nTimerCounter >= shield_comments.vars.cooldown ) {
20
- submitButton.value = origButtonValue;
21
- submitButton.disabled = false;
22
- clearInterval( sCountdownTimer );
23
- }
24
- nTimerCounter++;
25
- };
26
-
27
- var assignElements = function ( shiep ) {
28
- var maybecheckbox = document.getElementById( '_shieldcb_nombre' );
29
- if ( typeof (maybecheckbox) === "undefined" || maybecheckbox === null ) {
30
- var cbnombre = document.createElement( "input" );
31
- cbnombre.type = "hidden";
32
- cbnombre.id = "_shieldcb_nombre";
33
- cbnombre.name = "cb_nombre";
34
- cbnombre.value = shield_comments.vars.cbname;
35
- shiep.appendChild( cbnombre );
36
-
37
- document.body.style.cursor = 'wait';
38
- submitButton.disabled = true;
39
-
40
- var aAjaxVars = shield_comments.ajax.comment_token;
41
- jQuery.post( aAjaxVars.ajaxurl, aAjaxVars,
42
- function ( oResponse ) {
43
- if ( typeof (oResponse) !== "undefined" && oResponse !== null ) {
44
- if ( oResponse.success ) {
45
- var inputBotts = document.createElement( "input" );
46
- inputBotts.type = "hidden";
47
- inputBotts.name = "botts";
48
- inputBotts.value = aAjaxVars.ts;
49
- var inputToken = document.createElement( "input" );
50
- inputToken.type = "hidden";
51
- inputToken.name = "comment_token";
52
- inputToken.value = oResponse.data.token;
53
-
54
- shiep.appendChild( inputBotts );
55
- shiep.appendChild( inputToken );
56
- }
57
- }
58
- }
59
- ).fail(
60
- function () {
61
- alert( 'There was a problem with the request. Please try reloading the page.' );
62
- }
63
- ).always( function () {
64
- submitButton.disabled = false;
65
- document.body.style.cursor = 'default';
66
- }
67
- );
68
- }
69
- };
70
-
71
- var reDisableButton = function () {
72
- submitButton.value = shield_comments.strings.comment_reload;
73
- submitButton.disabled = true;
74
- };
75
-
76
- var insertPlaceHolder_Gasp = function ( form ) {
77
- var shiep = document.getElementById( shield_comments.vars.uniq );
78
- if ( typeof (shiep) === "undefined" || shiep === null ) {
79
- return;
80
- }
81
-
82
- var shieThe_cb = document.createElement( "input" );
83
- shieThe_cb.type = "checkbox";
84
- shieThe_cb.value = "Y";
85
- shieThe_cb.name = shield_comments.vars.cbname;
86
- shieThe_cb.id = '_' + shieThe_cb.name;
87
- shieThe_cb.onchange = function () {
88
- assignElements( shiep );
89
- };
90
-
91
- var shieThe_lab = document.createElement( "label" );
92
- var shieThe_labspan = document.createElement( "span" );
93
- shieThe_labspan.innerHTML = '&nbsp;' + shield_comments.strings.label;
94
-
95
- shieThe_lab.appendChild( shieThe_cb );
96
- shieThe_lab.appendChild( shieThe_labspan );
97
-
98
- var shishoney = document.createElement( "input" );
99
- shishoney.type = "hidden";
100
- shishoney.name = "sugar_sweet_email";
101
-
102
- shiep.appendChild( shishoney );
103
- shiep.appendChild( shieThe_lab );
104
-
105
- var comForm = shieThe_cb.form;
106
- var subbuttonList = comForm.querySelectorAll( 'input[type="submit"]' );
107
-
108
- if ( typeof (subbuttonList) !== "undefined" ) {
109
-
110
- submitButton = subbuttonList[ 0 ];
111
-
112
- if ( typeof (submitButton) !== "undefined" ) {
113
-
114
- if ( shield_comments.vars.cooldown > 0 ) {
115
- submitButton.disabled = true;
116
- origButtonValue = submitButton.value;
117
- nTimerCounter = 0;
118
- reEnableButton();
119
- sCountdownTimer = setInterval( reEnableButton, 1000 );
120
- }
121
- if ( shield_comments.vars.expires > 0 ) {
122
- setTimeout( reDisableButton, (1000 * shield_comments.vars.expires - 1000) );
123
- }
124
- }
125
- }
126
-
127
- shieThe_cb.form.onsubmit = function () {
128
- if ( shieThe_cb.checked !== true ) {
129
- alert( shield_comments.strings.alert );
130
- return false;
131
- }
132
- return true;
133
- };
134
- };
135
- }();
136
- iCWP_WPSF_ShieldCommentGuard.initialise();
137
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
resources/js/shield/antibot.js ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ if ( typeof Shield_Antibot === typeof undefined && typeof shield_vars_antibotjs !== typeof undefined ) {
2
+
3
+ var Shield_Antibot = new function () {
4
+
5
+ var request_count = 0;
6
+
7
+ var domReady = function ( fn ) {
8
+ if ( document.readyState !== 'loading' ) {
9
+ fn();
10
+ }
11
+ else if ( document.addEventListener ) {
12
+ document.addEventListener( 'DOMContentLoaded', fn );
13
+ }
14
+ else {
15
+ document.attachEvent( 'onreadystatechange', function () {
16
+ if ( document.readyState !== 'loading' )
17
+ fn();
18
+ } );
19
+ }
20
+ }
21
+
22
+ this.initialise = function () {
23
+ domReady( function () {
24
+ fire();
25
+ } );
26
+ };
27
+
28
+ var fire = function () {
29
+ var sendRequest = false;
30
+ var current = getCookie( 'icwp-wpsf-notbot' );
31
+ if ( current === undefined ) {
32
+ sendRequest = true;
33
+ }
34
+ else {
35
+ var remaining = current.split( "z" )[ 0 ] - Math.floor( Date.now() / 1000 );
36
+ if ( remaining < 60 ) {
37
+ sendRequest = true;
38
+ }
39
+ }
40
+
41
+ if ( sendRequest && request_count < 11 ) {
42
+ sendReq();
43
+ }
44
+ window.setTimeout( fire, 60000 );
45
+ };
46
+
47
+ var sendReq = function ( name ) {
48
+ var xhttp = new XMLHttpRequest();
49
+ xhttp.open( "POST", shield_vars_antibotjs.hrefs.ajax, true );
50
+ xhttp.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded;' );
51
+ xhttp.send( shield_vars_antibotjs.ajax.not_bot );
52
+ request_count++;
53
+ };
54
+
55
+ var getCookie = function ( name ) {
56
+ var value = "; " + document.cookie;
57
+ var parts = value.split( "; " + name + "=" );
58
+ if ( parts.length === 2 ) return parts.pop().split( ";" ).shift();
59
+ };
60
+ }();
61
+ Shield_Antibot.initialise();
62
+ }
resources/js/shield/charts.js ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery.fn.icwpWpsfChartWithFilters = function ( aOptions ) {
2
+
3
+ let resetFilters = function ( evt ) {
4
+ jQuery( 'select', chartForm ).each( function () {
5
+ jQuery( this ).prop( 'selectedIndex', 0 );
6
+ } );
7
+ opts[ 'chart' ].renderChartFromForm( chartForm );
8
+ };
9
+
10
+ let submitFilters = function ( evt ) {
11
+ evt.preventDefault();
12
+ opts[ 'chart' ].renderChartFromForm( chartForm );
13
+ return false;
14
+ };
15
+
16
+ let initialise = function () {
17
+ jQuery( document ).ready( function () {
18
+ chartForm = jQuery( opts[ 'selector_filter_form' ] );
19
+ chartForm.on( 'click', 'input[type=submit]', submitFilters );
20
+ chartForm.on( 'click', 'a#ClearForm', resetFilters );
21
+ } );
22
+ };
23
+
24
+ let $oThis = this;
25
+ let opts = jQuery.extend( {}, aOptions );
26
+ let chartForm;
27
+ initialise();
28
+
29
+ return this;
30
+ };
31
+
32
+ jQuery.fn.icwpWpsfAjaxChart = function ( options ) {
33
+
34
+ this.reloadChart = function () {
35
+ reqRenderChart();
36
+ };
37
+
38
+ let createChartContainer = function () {
39
+ chartTitleContainer = jQuery( '#CustomChartTitle' );
40
+ chartContainer = jQuery( '<div />' ).appendTo( $oThis );
41
+ chartContainer.addClass( 'icwpAjaxContainerChart' )
42
+ .addClass( 'ct-chart' );
43
+ };
44
+
45
+ let refreshChart = function ( event ) {
46
+ event.preventDefault();
47
+ let aChartRequestParams = {};
48
+ reqRenderChart( aChartRequestParams );
49
+ };
50
+
51
+ this.renderChartFromForm = function ( $oForm ) {
52
+ reqRenderChart( { 'form_params': $oForm.serialize() } );
53
+ };
54
+
55
+ let reqRenderChart = function ( reqParams ) {
56
+ if ( bReqRunning ) {
57
+ return false;
58
+ }
59
+ bReqRunning = true;
60
+
61
+ chartContainer.html( 'Loading...' );
62
+ chartTitleContainer.html( '' );
63
+ jQuery.post( ajaxurl, jQuery.extend( opts[ 'ajax_render' ], opts[ 'req_params' ], reqParams ),
64
+ function ( response ) {
65
+
66
+ if ( !response.success ) {
67
+ alert( response.data.message );
68
+ }
69
+ else {
70
+ if ( opts[ 'show_title' ] && typeof response.data.chart.title !== typeof undefined ) {
71
+ chartTitleContainer.html( response.data.chart.title );
72
+ }
73
+
74
+ chartContainer.html( '' );
75
+ new Chartist.Line(
76
+ $oThis[ 0 ].querySelectorAll( '.icwpAjaxContainerChart' )[ 0 ],
77
+ response.data.chart.data,
78
+ jQuery.extend( {
79
+ fullWidth: true,
80
+ showArea: false,
81
+ chartPadding: {
82
+ top: 10,
83
+ right: 10,
84
+ bottom: 10,
85
+ left: 10
86
+ },
87
+ axisX: {
88
+ showLabel: true,
89
+ showGrid: true,
90
+ },
91
+ axisY: {
92
+ onlyInteger: true,
93
+ showLabel: true,
94
+ labelInterpolationFnc: function ( value ) {
95
+ return value;
96
+ }
97
+ },
98
+ plugins: [
99
+ Chartist.plugins.legend( {
100
+ legendNames: response.data.chart.legend
101
+ } )
102
+ ]
103
+ }, opts[ 'chart_options' ] )
104
+ );
105
+ }
106
+ }
107
+ ).always(
108
+ function () {
109
+ bReqRunning = false;
110
+ }
111
+ );
112
+ };
113
+
114
+ let setHandlers = function () {
115
+ };
116
+
117
+ let initialise = function () {
118
+ jQuery( document ).ready( function () {
119
+ createChartContainer();
120
+ if ( opts[ 'init_render' ] ) {
121
+ reqRenderChart();
122
+ }
123
+ setHandlers();
124
+ } );
125
+ };
126
+
127
+ let $oThis = this;
128
+ let chartContainer;
129
+ let chartTitleContainer;
130
+ let bReqRunning = false;
131
+ let opts = jQuery.extend( {
132
+ init_render: true,
133
+ show_title: false
134
+ }, options );
135
+ initialise();
136
+
137
+ return this;
138
+ };
resources/js/shield/comments.js ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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/ipanalyse.js CHANGED
@@ -1,7 +1,7 @@
1
  jQuery.fn.icwpWpsfIpAnalyse = function ( options ) {
2
 
3
  var runAnalysis = function () {
4
- let newUrl = window.location.href.replace( /&analyse_ip=(\d{1,3}\.){3}\d{1,3}/i, "" );
5
  if ( $oIpSelect.val().length > 0 ) {
6
  newUrl += "&analyse_ip=" + $oIpSelect.val();
7
  }
@@ -18,7 +18,7 @@ jQuery.fn.icwpWpsfIpAnalyse = function ( options ) {
18
  window.history.replaceState(
19
  {},
20
  document.title,
21
- window.location.href.replace( /&analyse_ip=(\d{1,3}\.){3}\d{1,3}/i, "" )
22
  );
23
  };
24
 
@@ -35,15 +35,18 @@ jQuery.fn.icwpWpsfIpAnalyse = function ( options ) {
35
  jQuery( '#IpSelectContent' ).addClass( "d-none" );
36
  jQuery( '#IpReviewContent' ).removeClass( "d-none" )
37
  .html( oResponse.data.html );
 
 
 
38
  }
39
  else {
40
- var sMessage = 'Communications error with site.';
41
  if ( oResponse.data.message !== undefined ) {
42
- sMessage = oResponse.data.message;
43
  }
44
  jQuery( '#IpSelectContent' ).removeClass( "d-none" );
45
  jQuery( '#IpReviewContent' ).addClass( "d-none" );
46
- alert( sMessage );
47
  }
48
 
49
  }
@@ -79,7 +82,7 @@ jQuery.fn.icwpWpsfIpAnalyse = function ( options ) {
79
  let urlParams = new URLSearchParams( window.location.search );
80
  let theIP = urlParams.get( 'analyse_ip' );
81
  if ( theIP ) {
82
- $oIpSelect.selectpicker( 'val', theIP );
83
  runAnalysis();
84
  }
85
  else {
1
  jQuery.fn.icwpWpsfIpAnalyse = function ( options ) {
2
 
3
  var runAnalysis = function () {
4
+ let newUrl = window.location.href.replace( /&analyse_ip=.+/i, "" );
5
  if ( $oIpSelect.val().length > 0 ) {
6
  newUrl += "&analyse_ip=" + $oIpSelect.val();
7
  }
18
  window.history.replaceState(
19
  {},
20
  document.title,
21
+ window.location.href.replace( /&analyse_ip=.*/i, "" )
22
  );
23
  };
24
 
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" );
49
+ alert( msg );
50
  }
51
 
52
  }
82
  let urlParams = new URLSearchParams( window.location.search );
83
  let theIP = urlParams.get( 'analyse_ip' );
84
  if ( theIP ) {
85
+ $oIpSelect.val( theIP );
86
  runAnalysis();
87
  }
88
  else {
resources/js/{shield-antibot.js → shield/loginbot.js} RENAMED
@@ -43,9 +43,9 @@ var iCWP_WPSF_LoginGuard_Gasp = new function () {
43
  };
44
 
45
  var cleanDuplicates = function ( form ) {
46
- let $oPlaceholders = jQuery( 'p.shield_gasp_placeholder', form );
47
- if ( $oPlaceholders.length > 1 ) {
48
- $oPlaceholders.each(
49
  function ( nkey ) {
50
  if ( nkey > 0 && this !== null ) {
51
  jQuery( this ).remove();
@@ -65,39 +65,47 @@ var iCWP_WPSF_LoginGuard_Gasp = new function () {
65
  };
66
 
67
  var processPlaceHolder_Gasp = function ( shiep ) {
68
- var shishoney = document.createElement( "input" );
69
- shishoney.type = "hidden";
70
- shishoney.name = "icwp_wpsf_login_email";
71
-
72
- shiep.innerHTML = '';
73
- shiep.appendChild( shishoney );
74
-
75
  var shieThe_lab = document.createElement( "label" );
76
  var shieThe_txt = document.createTextNode( ' ' + icwp_wpsf_vars_lpantibot.strings.label );
77
  var shieThe_cb = document.createElement( "input" );
78
- shieThe_cb.type = "checkbox";
79
- shieThe_cb.name = icwp_wpsf_vars_lpantibot.cbname;
80
- shieThe_cb.id = '_' + shieThe_cb.name;
81
- shiep.appendChild( shieThe_lab );
82
- shieThe_lab.appendChild( shieThe_cb );
83
- shieThe_lab.appendChild( shieThe_txt );
84
 
85
  let $oPH = jQuery( shiep );
86
  if ( [ 'p', 'P' ].includes( $oPH.parent()[ 0 ].nodeName ) ) {
87
- /** try to prevent nested paragraphs */
88
  jQuery( shiep ).insertBefore( $oPH.parent() )
89
  }
90
 
91
- let $oParentForm = $oPH.closest( 'form' );
92
- if ( $oParentForm.length > 0 ) {
93
- $oParentForm[ 0 ].onsubmit = function () {
94
- if ( shieThe_cb.checked !== true ) {
 
 
 
 
 
95
  alert( icwp_wpsf_vars_lpantibot.strings.alert );
96
- return false;
97
  }
98
- return true;
99
  };
 
 
 
 
 
100
  }
 
 
 
 
 
 
 
 
 
101
  };
102
  }();
103
  iCWP_WPSF_LoginGuard_Gasp.initialise();
43
  };
44
 
45
  var cleanDuplicates = function ( form ) {
46
+ let $placeHolders = jQuery( 'p.shield_gasp_placeholder', form );
47
+ if ( $placeHolders.length > 1 ) {
48
+ $placeHolders.each(
49
  function ( nkey ) {
50
  if ( nkey > 0 && this !== null ) {
51
  jQuery( this ).remove();
65
  };
66
 
67
  var processPlaceHolder_Gasp = function ( shiep ) {
 
 
 
 
 
 
 
68
  var shieThe_lab = document.createElement( "label" );
69
  var shieThe_txt = document.createTextNode( ' ' + icwp_wpsf_vars_lpantibot.strings.label );
70
  var shieThe_cb = document.createElement( "input" );
71
+
72
+ shiep.style.display = "inherit";
 
 
 
 
73
 
74
  let $oPH = jQuery( shiep );
75
  if ( [ 'p', 'P' ].includes( $oPH.parent()[ 0 ].nodeName ) ) {
76
+ /** prevent nested paragraphs */
77
  jQuery( shiep ).insertBefore( $oPH.parent() )
78
  }
79
 
80
+ let parentForm = $oPH.closest( 'form' );
81
+ if ( parentForm.length > 0 ) {
82
+ parentForm[ 0 ].addEventListener( "mouseover", function () {
83
+ if ( !shieThe_cb.checked ) {
84
+ // shieThe_cb.checked = true;
85
+ }
86
+ } );
87
+ parentForm[ 0 ].onsubmit = function () {
88
+ if ( !shieThe_cb.checked ) {
89
  alert( icwp_wpsf_vars_lpantibot.strings.alert );
90
+ shiep.style.display = "inherit";
91
  }
92
+ return shieThe_cb.checked;
93
  };
94
+
95
+ var shishoney = document.createElement( "input" );
96
+ shishoney.type = "hidden";
97
+ shishoney.name = "icwp_wpsf_login_email";
98
+ parentForm[ 0 ].appendChild( shishoney );
99
  }
100
+
101
+ shiep.innerHTML = '';
102
+
103
+ shieThe_cb.type = "checkbox";
104
+ shieThe_cb.name = icwp_wpsf_vars_lpantibot.cbname;
105
+ shieThe_cb.id = '_' + shieThe_cb.name;
106
+ shiep.appendChild( shieThe_lab );
107
+ shieThe_lab.appendChild( shieThe_cb );
108
+ shieThe_lab.appendChild( shieThe_txt );
109
  };
110
  }();
111
  iCWP_WPSF_LoginGuard_Gasp.initialise();
resources/js/{shield-card-shuffle.js → shield/shuffle.js} RENAMED
File without changes
resources/js/shield/tours.js ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery.fn.icwpWpsfTours = function ( options ) {
2
+
3
+ var setupAllTours = function ( forceShow = false ) {
4
+ shield_vars_tourmanager.tours.forEach( function ( tour_key, i ) {
5
+ if ( forceShow || !(tour_key in shield_vars_tourmanager.tour_states) ) {
6
+ setupTour( tour_key, forceShow );
7
+ }
8
+ } );
9
+ }
10
+
11
+ var setupTour = function ( tour_key ) {
12
+ introJs().setOptions( getTourSettings( tour_key ) )
13
+ .onexit( function () {
14
+ markTourFinished( tour_key );
15
+ } )
16
+ .start();
17
+ }
18
+
19
+ var getTourSettings = function ( tourKey ) {
20
+ return {
21
+ overlayOpacity: 0.7,
22
+ highlightClass: "tour-" + tourKey,
23
+ tooltipClass: "shield_tour_tooltip",
24
+ showProgress: true,
25
+ scrollToElement: true
26
+ }
27
+ };
28
+
29
+ var markTourFinished = function ( tourKey ) {
30
+ shield_vars_tourmanager.ajax[ 'tour_key' ] = tourKey;
31
+ jQuery.post( ajaxurl, shield_vars_tourmanager.ajax );
32
+ };
33
+
34
+ var initialise = function () {
35
+ jQuery( document ).ready( function () {
36
+ setupAllTours( false );
37
+ } );
38
+ };
39
+
40
+ initialise();
41
+
42
+ return this;
43
+ };
resources/js/shuffle.js DELETED
@@ -1,2190 +0,0 @@
1
- (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
- typeof define === 'function' && define.amd ? define(factory) :
4
- (global = global || self, global.Shuffle = factory());
5
- }(this, function () { 'use strict';
6
-
7
- function _classCallCheck(instance, Constructor) {
8
- if (!(instance instanceof Constructor)) {
9
- throw new TypeError("Cannot call a class as a function");
10
- }
11
- }
12
-
13
- function _defineProperties(target, props) {
14
- for (var i = 0; i < props.length; i++) {
15
- var descriptor = props[i];
16
- descriptor.enumerable = descriptor.enumerable || false;
17
- descriptor.configurable = true;
18
- if ("value" in descriptor) descriptor.writable = true;
19
- Object.defineProperty(target, descriptor.key, descriptor);
20
- }
21
- }
22
-
23
- function _createClass(Constructor, protoProps, staticProps) {
24
- if (protoProps) _defineProperties(Constructor.prototype, protoProps);
25
- if (staticProps) _defineProperties(Constructor, staticProps);
26
- return Constructor;
27
- }
28
-
29
- function _inherits(subClass, superClass) {
30
- if (typeof superClass !== "function" && superClass !== null) {
31
- throw new TypeError("Super expression must either be null or a function");
32
- }
33
-
34
- subClass.prototype = Object.create(superClass && superClass.prototype, {
35
- constructor: {
36
- value: subClass,
37
- writable: true,
38
- configurable: true
39
- }
40
- });
41
- if (superClass) _setPrototypeOf(subClass, superClass);
42
- }
43
-
44
- function _getPrototypeOf(o) {
45
- _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
46
- return o.__proto__ || Object.getPrototypeOf(o);
47
- };
48
- return _getPrototypeOf(o);
49
- }
50
-
51
- function _setPrototypeOf(o, p) {
52
- _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
53
- o.__proto__ = p;
54
- return o;
55
- };
56
-
57
- return _setPrototypeOf(o, p);
58
- }
59
-
60
- function _assertThisInitialized(self) {
61
- if (self === void 0) {
62
- throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
63
- }
64
-
65
- return self;
66
- }
67
-
68
- function _possibleConstructorReturn(self, call) {
69
- if (call && (typeof call === "object" || typeof call === "function")) {
70
- return call;
71
- }
72
-
73
- return _assertThisInitialized(self);
74
- }
75
-
76
- function E () {
77
- // Keep this empty so it's easier to inherit from
78
- // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
79
- }
80
-
81
- E.prototype = {
82
- on: function (name, callback, ctx) {
83
- var e = this.e || (this.e = {});
84
-
85
- (e[name] || (e[name] = [])).push({
86
- fn: callback,
87
- ctx: ctx
88
- });
89
-
90
- return this;
91
- },
92
-
93
- once: function (name, callback, ctx) {
94
- var self = this;
95
- function listener () {
96
- self.off(name, listener);
97
- callback.apply(ctx, arguments);
98
- }
99
- listener._ = callback;
100
- return this.on(name, listener, ctx);
101
- },
102
-
103
- emit: function (name) {
104
- var data = [].slice.call(arguments, 1);
105
- var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
106
- var i = 0;
107
- var len = evtArr.length;
108
-
109
- for (i; i < len; i++) {
110
- evtArr[i].fn.apply(evtArr[i].ctx, data);
111
- }
112
-
113
- return this;
114
- },
115
-
116
- off: function (name, callback) {
117
- var e = this.e || (this.e = {});
118
- var evts = e[name];
119
- var liveEvents = [];
120
-
121
- if (evts && callback) {
122
- for (var i = 0, len = evts.length; i < len; i++) {
123
- if (evts[i].fn !== callback && evts[i].fn._ !== callback)
124
- liveEvents.push(evts[i]);
125
- }
126
- }
127
-
128
- // Remove event from queue to prevent memory leak
129
- // Suggested by https://github.com/lazd
130
- // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910
131
-
132
- (liveEvents.length)
133
- ? e[name] = liveEvents
134
- : delete e[name];
135
-
136
- return this;
137
- }
138
- };
139
-
140
- var tinyEmitter = E;
141
- var TinyEmitter = E;
142
- tinyEmitter.TinyEmitter = TinyEmitter;
143
-
144
- var proto = typeof Element !== 'undefined' ? Element.prototype : {};
145
- var vendor = proto.matches
146
- || proto.matchesSelector
147
- || proto.webkitMatchesSelector
148
- || proto.mozMatchesSelector
149
- || proto.msMatchesSelector
150
- || proto.oMatchesSelector;
151
-
152
- var matchesSelector = match;
153
-
154
- /**
155
- * Match `el` to `selector`.
156
- *
157
- * @param {Element} el
158
- * @param {String} selector
159
- * @return {Boolean}
160
- * @api public
161
- */
162
-
163
- function match(el, selector) {
164
- if (!el || el.nodeType !== 1) return false;
165
- if (vendor) return vendor.call(el, selector);
166
- var nodes = el.parentNode.querySelectorAll(selector);
167
- for (var i = 0; i < nodes.length; i++) {
168
- if (nodes[i] == el) return true;
169
- }
170
- return false;
171
- }
172
-
173
- var throttleit = throttle;
174
-
175
- /**
176
- * Returns a new function that, when invoked, invokes `func` at most once per `wait` milliseconds.
177
- *
178
- * @param {Function} func Function to wrap.
179
- * @param {Number} wait Number of milliseconds that must elapse between `func` invocations.
180
- * @return {Function} A new function that wraps the `func` function passed in.
181
- */
182
-
183
- function throttle (func, wait) {
184
- var ctx, args, rtn, timeoutID; // caching
185
- var last = 0;
186
-
187
- return function throttled () {
188
- ctx = this;
189
- args = arguments;
190
- var delta = new Date() - last;
191
- if (!timeoutID)
192
- if (delta >= wait) call();
193
- else timeoutID = setTimeout(call, wait - delta);
194
- return rtn;
195
- };
196
-
197
- function call () {
198
- timeoutID = 0;
199
- last = +new Date();
200
- rtn = func.apply(ctx, args);
201
- ctx = null;
202
- args = null;
203
- }
204
- }
205
-
206
- var arrayParallel = function parallel(fns, context, callback) {
207
- if (!callback) {
208
- if (typeof context === 'function') {
209
- callback = context;
210
- context = null;
211
- } else {
212
- callback = noop;
213
- }
214
- }
215
-
216
- var pending = fns && fns.length;
217
- if (!pending) return callback(null, []);
218
-
219
- var finished = false;
220
- var results = new Array(pending);
221
-
222
- fns.forEach(context ? function (fn, i) {
223
- fn.call(context, maybeDone(i));
224
- } : function (fn, i) {
225
- fn(maybeDone(i));
226
- });
227
-
228
- function maybeDone(i) {
229
- return function (err, result) {
230
- if (finished) return;
231
-
232
- if (err) {
233
- callback(err, results);
234
- finished = true;
235
- return
236
- }
237
-
238
- results[i] = result;
239
-
240
- if (!--pending) callback(null, results);
241
- }
242
- }
243
- };
244
-
245
- function noop() {}
246
-
247
- /**
248
- * Always returns a numeric value, given a value. Logic from jQuery's `isNumeric`.
249
- * @param {*} value Possibly numeric value.
250
- * @return {number} `value` or zero if `value` isn't numeric.
251
- */
252
- function getNumber(value) {
253
- return parseFloat(value) || 0;
254
- }
255
-
256
- var Point =
257
- /*#__PURE__*/
258
- function () {
259
- /**
260
- * Represents a coordinate pair.
261
- * @param {number} [x=0] X.
262
- * @param {number} [y=0] Y.
263
- */
264
- function Point(x, y) {
265
- _classCallCheck(this, Point);
266
-
267
- this.x = getNumber(x);
268
- this.y = getNumber(y);
269
- }
270
- /**
271
- * Whether two points are equal.
272
- * @param {Point} a Point A.
273
- * @param {Point} b Point B.
274
- * @return {boolean}
275
- */
276
-
277
-
278
- _createClass(Point, null, [{
279
- key: "equals",
280
- value: function equals(a, b) {
281
- return a.x === b.x && a.y === b.y;
282
- }
283
- }]);
284
-
285
- return Point;
286
- }();
287
-
288
- var Rect =
289
- /*#__PURE__*/
290
- function () {
291
- /**
292
- * Class for representing rectangular regions.
293
- * https://github.com/google/closure-library/blob/master/closure/goog/math/rect.js
294
- * @param {number} x Left.
295
- * @param {number} y Top.
296
- * @param {number} w Width.
297
- * @param {number} h Height.
298
- * @param {number} id Identifier
299
- * @constructor
300
- */
301
- function Rect(x, y, w, h, id) {
302
- _classCallCheck(this, Rect);
303
-
304
- this.id = id;
305
- /** @type {number} */
306
-
307
- this.left = x;
308
- /** @type {number} */
309
-
310
- this.top = y;
311
- /** @type {number} */
312
-
313
- this.width = w;
314
- /** @type {number} */
315
-
316
- this.height = h;
317
- }
318
- /**
319
- * Returns whether two rectangles intersect.
320
- * @param {Rect} a A Rectangle.
321
- * @param {Rect} b A Rectangle.
322
- * @return {boolean} Whether a and b intersect.
323
- */
324
-
325
-
326
- _createClass(Rect, null, [{
327
- key: "intersects",
328
- value: function intersects(a, b) {
329
- return a.left < b.left + b.width && b.left < a.left + a.width && a.top < b.top + b.height && b.top < a.top + a.height;
330
- }
331
- }]);
332
-
333
- return Rect;
334
- }();
335
-
336
- var Classes = {
337
- BASE: 'shuffle',
338
- SHUFFLE_ITEM: 'shuffle-item',
339
- VISIBLE: 'shuffle-item--visible',
340
- HIDDEN: 'shuffle-item--hidden'
341
- };
342
-
343
- var id = 0;
344
-
345
- var ShuffleItem =
346
- /*#__PURE__*/
347
- function () {
348
- function ShuffleItem(element) {
349
- _classCallCheck(this, ShuffleItem);
350
-
351
- id += 1;
352
- this.id = id;
353
- this.element = element;
354
- /**
355
- * Used to separate items for layout and shrink.
356
- */
357
-
358
- this.isVisible = true;
359
- /**
360
- * Used to determine if a transition will happen. By the time the _layout
361
- * and _shrink methods get the ShuffleItem instances, the `isVisible` value
362
- * has already been changed by the separation methods, so this property is
363
- * needed to know if the item was visible/hidden before the shrink/layout.
364
- */
365
-
366
- this.isHidden = false;
367
- }
368
-
369
- _createClass(ShuffleItem, [{
370
- key: "show",
371
- value: function show() {
372
- this.isVisible = true;
373
- this.element.classList.remove(Classes.HIDDEN);
374
- this.element.classList.add(Classes.VISIBLE);
375
- this.element.removeAttribute('aria-hidden');
376
- }
377
- }, {
378
- key: "hide",
379
- value: function hide() {
380
- this.isVisible = false;
381
- this.element.classList.remove(Classes.VISIBLE);
382
- this.element.classList.add(Classes.HIDDEN);
383
- this.element.setAttribute('aria-hidden', true);
384
- }
385
- }, {
386
- key: "init",
387
- value: function init() {
388
- this.addClasses([Classes.SHUFFLE_ITEM, Classes.VISIBLE]);
389
- this.applyCss(ShuffleItem.Css.INITIAL);
390
- this.scale = ShuffleItem.Scale.VISIBLE;
391
- this.point = new Point();
392
- }
393
- }, {
394
- key: "addClasses",
395
- value: function addClasses(classes) {
396
- var _this = this;
397
-
398
- classes.forEach(function (className) {
399
- _this.element.classList.add(className);
400
- });
401
- }
402
- }, {
403
- key: "removeClasses",
404
- value: function removeClasses(classes) {
405
- var _this2 = this;
406
-
407
- classes.forEach(function (className) {
408
- _this2.element.classList.remove(className);
409
- });
410
- }
411
- }, {
412
- key: "applyCss",
413
- value: function applyCss(obj) {
414
- var _this3 = this;
415
-
416
- Object.keys(obj).forEach(function (key) {
417
- _this3.element.style[key] = obj[key];
418
- });
419
- }
420
- }, {
421
- key: "dispose",
422
- value: function dispose() {
423
- this.removeClasses([Classes.HIDDEN, Classes.VISIBLE, Classes.SHUFFLE_ITEM]);
424
- this.element.removeAttribute('style');
425
- this.element = null;
426
- }
427
- }]);
428
-
429
- return ShuffleItem;
430
- }();
431
-
432
- ShuffleItem.Css = {
433
- INITIAL: {
434
- position: 'absolute',
435
- top: 0,
436
- left: 0,
437
- visibility: 'visible',
438
- willChange: 'transform'
439
- },
440
- VISIBLE: {
441
- before: {
442
- opacity: 1,
443
- visibility: 'visible'
444
- },
445
- after: {
446
- transitionDelay: ''
447
- }
448
- },
449
- HIDDEN: {
450
- before: {
451
- opacity: 0
452
- },
453
- after: {
454
- visibility: 'hidden',
455
- transitionDelay: ''
456
- }
457
- }
458
- };
459
- ShuffleItem.Scale = {
460
- VISIBLE: 1,
461
- HIDDEN: 0.001
462
- };
463
-
464
- var value = null;
465
- var testComputedSize = (function () {
466
- if (value !== null) {
467
- return value;
468
- }
469
-
470
- var element = document.body || document.documentElement;
471
- var e = document.createElement('div');
472
- e.style.cssText = 'width:10px;padding:2px;box-sizing:border-box;';
473
- element.appendChild(e);
474
- value = window.getComputedStyle(e, null).width === '10px';
475
- element.removeChild(e);
476
- return value;
477
- });
478
-
479
- /**
480
- * Retrieve the computed style for an element, parsed as a float.
481
- * @param {Element} element Element to get style for.
482
- * @param {string} style Style property.
483
- * @param {CSSStyleDeclaration} [styles] Optionally include clean styles to
484
- * use instead of asking for them again.
485
- * @return {number} The parsed computed value or zero if that fails because IE
486
- * will return 'auto' when the element doesn't have margins instead of
487
- * the computed style.
488
- */
489
-
490
- function getNumberStyle(element, style) {
491
- var styles = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : window.getComputedStyle(element, null);
492
- var value = getNumber(styles[style]); // Support IE<=11 and W3C spec.
493
-
494
- if (!testComputedSize() && style === 'width') {
495
- value += getNumber(styles.paddingLeft) + getNumber(styles.paddingRight) + getNumber(styles.borderLeftWidth) + getNumber(styles.borderRightWidth);
496
- } else if (!testComputedSize() && style === 'height') {
497
- value += getNumber(styles.paddingTop) + getNumber(styles.paddingBottom) + getNumber(styles.borderTopWidth) + getNumber(styles.borderBottomWidth);
498
- }
499
-
500
- return value;
501
- }
502
-
503
- /**
504
- * Fisher-Yates shuffle.
505
- * http://stackoverflow.com/a/962890/373422
506
- * https://bost.ocks.org/mike/shuffle/
507
- * @param {Array} array Array to shuffle.
508
- * @return {Array} Randomly sorted array.
509
- */
510
- function randomize(array) {
511
- var n = array.length;
512
-
513
- while (n) {
514
- n -= 1;
515
- var i = Math.floor(Math.random() * (n + 1));
516
- var temp = array[i];
517
- array[i] = array[n];
518
- array[n] = temp;
519
- }
520
-
521
- return array;
522
- }
523
-
524
- var defaults = {
525
- // Use array.reverse() to reverse the results
526
- reverse: false,
527
- // Sorting function
528
- by: null,
529
- // Custom sort function
530
- compare: null,
531
- // If true, this will skip the sorting and return a randomized order in the array
532
- randomize: false,
533
- // Determines which property of each item in the array is passed to the
534
- // sorting method.
535
- key: 'element'
536
- };
537
- /**
538
- * You can return `undefined` from the `by` function to revert to DOM order.
539
- * @param {Array<T>} arr Array to sort.
540
- * @param {SortOptions} options Sorting options.
541
- * @return {Array<T>}
542
- */
543
-
544
- function sorter(arr, options) {
545
- var opts = Object.assign({}, defaults, options);
546
- var original = Array.from(arr);
547
- var revert = false;
548
-
549
- if (!arr.length) {
550
- return [];
551
- }
552
-
553
- if (opts.randomize) {
554
- return randomize(arr);
555
- } // Sort the elements by the opts.by function.
556
- // If we don't have opts.by, default to DOM order
557
-
558
-
559
- if (typeof opts.by === 'function') {
560
- arr.sort(function (a, b) {
561
- // Exit early if we already know we want to revert
562
- if (revert) {
563
- return 0;
564
- }
565
-
566
- var valA = opts.by(a[opts.key]);
567
- var valB = opts.by(b[opts.key]); // If both values are undefined, use the DOM order
568
-
569
- if (valA === undefined && valB === undefined) {
570
- revert = true;
571
- return 0;
572
- }
573
-
574
- if (valA < valB || valA === 'sortFirst' || valB === 'sortLast') {
575
- return -1;
576
- }
577
-
578
- if (valA > valB || valA === 'sortLast' || valB === 'sortFirst') {
579
- return 1;
580
- }
581
-
582
- return 0;
583
- });
584
- } else if (typeof opts.compare === 'function') {
585
- arr.sort(opts.compare);
586
- } // Revert to the original array if necessary
587
-
588
-
589
- if (revert) {
590
- return original;
591
- }
592
-
593
- if (opts.reverse) {
594
- arr.reverse();
595
- }
596
-
597
- return arr;
598
- }
599
-
600
- var transitions = {};
601
- var eventName = 'transitionend';
602
- var count = 0;
603
-
604
- function uniqueId() {
605
- count += 1;
606
- return eventName + count;
607
- }
608
-
609
- function cancelTransitionEnd(id) {
610
- if (transitions[id]) {
611
- transitions[id].element.removeEventListener(eventName, transitions[id].listener);
612
- transitions[id] = null;
613
- return true;
614
- }
615
-
616
- return false;
617
- }
618
- function onTransitionEnd(element, callback) {
619
- var id = uniqueId();
620
-
621
- var listener = function listener(evt) {
622
- if (evt.currentTarget === evt.target) {
623
- cancelTransitionEnd(id);
624
- callback(evt);
625
- }
626
- };
627
-
628
- element.addEventListener(eventName, listener);
629
- transitions[id] = {
630
- element: element,
631
- listener: listener
632
- };
633
- return id;
634
- }
635
-
636
- function arrayMax(array) {
637
- return Math.max.apply(Math, array); // eslint-disable-line prefer-spread
638
- }
639
-
640
- function arrayMin(array) {
641
- return Math.min.apply(Math, array); // eslint-disable-line prefer-spread
642
- }
643
-
644
- /**
645
- * Determine the number of columns an items spans.
646
- * @param {number} itemWidth Width of the item.
647
- * @param {number} columnWidth Width of the column (includes gutter).
648
- * @param {number} columns Total number of columns
649
- * @param {number} threshold A buffer value for the size of the column to fit.
650
- * @return {number}
651
- */
652
-
653
- function getColumnSpan(itemWidth, columnWidth, columns, threshold) {
654
- var columnSpan = itemWidth / columnWidth; // If the difference between the rounded column span number and the
655
- // calculated column span number is really small, round the number to
656
- // make it fit.
657
-
658
- if (Math.abs(Math.round(columnSpan) - columnSpan) < threshold) {
659
- // e.g. columnSpan = 4.0089945390298745
660
- columnSpan = Math.round(columnSpan);
661
- } // Ensure the column span is not more than the amount of columns in the whole layout.
662
-
663
-
664
- return Math.min(Math.ceil(columnSpan), columns);
665
- }
666
- /**
667
- * Retrieves the column set to use for placement.
668
- * @param {number} columnSpan The number of columns this current item spans.
669
- * @param {number} columns The total columns in the grid.
670
- * @return {Array.<number>} An array of numbers represeting the column set.
671
- */
672
-
673
- function getAvailablePositions(positions, columnSpan, columns) {
674
- // The item spans only one column.
675
- if (columnSpan === 1) {
676
- return positions;
677
- } // The item spans more than one column, figure out how many different
678
- // places it could fit horizontally.
679
- // The group count is the number of places within the positions this block
680
- // could fit, ignoring the current positions of items.
681
- // Imagine a 2 column brick as the second item in a 4 column grid with
682
- // 10px height each. Find the places it would fit:
683
- // [20, 10, 10, 0]
684
- // | | |
685
- // * * *
686
- //
687
- // Then take the places which fit and get the bigger of the two:
688
- // max([20, 10]), max([10, 10]), max([10, 0]) = [20, 10, 10]
689
- //
690
- // Next, find the first smallest number (the short column).
691
- // [20, 10, 10]
692
- // |
693
- // *
694
- //
695
- // And that's where it should be placed!
696
- //
697
- // Another example where the second column's item extends past the first:
698
- // [10, 20, 10, 0] => [20, 20, 10] => 10
699
-
700
-
701
- var available = []; // For how many possible positions for this item there are.
702
-
703
- for (var i = 0; i <= columns - columnSpan; i++) {
704
- // Find the bigger value for each place it could fit.
705
- available.push(arrayMax(positions.slice(i, i + columnSpan)));
706
- }
707
-
708
- return available;
709
- }
710
- /**
711
- * Find index of short column, the first from the left where this item will go.
712
- *
713
- * @param {Array.<number>} positions The array to search for the smallest number.
714
- * @param {number} buffer Optional buffer which is very useful when the height
715
- * is a percentage of the width.
716
- * @return {number} Index of the short column.
717
- */
718
-
719
- function getShortColumn(positions, buffer) {
720
- var minPosition = arrayMin(positions);
721
-
722
- for (var i = 0, len = positions.length; i < len; i++) {
723
- if (positions[i] >= minPosition - buffer && positions[i] <= minPosition + buffer) {
724
- return i;
725
- }
726
- }
727
-
728
- return 0;
729
- }
730
- /**
731
- * Determine the location of the next item, based on its size.
732
- * @param {Object} itemSize Object with width and height.
733
- * @param {Array.<number>} positions Positions of the other current items.
734
- * @param {number} gridSize The column width or row height.
735
- * @param {number} total The total number of columns or rows.
736
- * @param {number} threshold Buffer value for the column to fit.
737
- * @param {number} buffer Vertical buffer for the height of items.
738
- * @return {Point}
739
- */
740
-
741
- function getItemPosition(_ref) {
742
- var itemSize = _ref.itemSize,
743
- positions = _ref.positions,
744
- gridSize = _ref.gridSize,
745
- total = _ref.total,
746
- threshold = _ref.threshold,
747
- buffer = _ref.buffer;
748
- var span = getColumnSpan(itemSize.width, gridSize, total, threshold);
749
- var setY = getAvailablePositions(positions, span, total);
750
- var shortColumnIndex = getShortColumn(setY, buffer); // Position the item
751
-
752
- var point = new Point(gridSize * shortColumnIndex, setY[shortColumnIndex]); // Update the columns array with the new values for each column.
753
- // e.g. before the update the columns could be [250, 0, 0, 0] for an item
754
- // which spans 2 columns. After it would be [250, itemHeight, itemHeight, 0].
755
-
756
- var setHeight = setY[shortColumnIndex] + itemSize.height;
757
-
758
- for (var i = 0; i < span; i++) {
759
- positions[shortColumnIndex + i] = setHeight;
760
- }
761
-
762
- return point;
763
- }
764
- /**
765
- * This method attempts to center items. This method could potentially be slow
766
- * with a large number of items because it must place items, then check every
767
- * previous item to ensure there is no overlap.
768
- * @param {Array.<Rect>} itemRects Item data objects.
769
- * @param {number} containerWidth Width of the containing element.
770
- * @return {Array.<Point>}
771
- */
772
-
773
- function getCenteredPositions(itemRects, containerWidth) {
774
- var rowMap = {}; // Populate rows by their offset because items could jump between rows like:
775
- // a c
776
- // bbb
777
-
778
- itemRects.forEach(function (itemRect) {
779
- if (rowMap[itemRect.top]) {
780
- // Push the point to the last row array.
781
- rowMap[itemRect.top].push(itemRect);
782
- } else {
783
- // Start of a new row.
784
- rowMap[itemRect.top] = [itemRect];
785
- }
786
- }); // For each row, find the end of the last item, then calculate
787
- // the remaining space by dividing it by 2. Then add that
788
- // offset to the x position of each point.
789
-
790
- var rects = [];
791
- var rows = [];
792
- var centeredRows = [];
793
- Object.keys(rowMap).forEach(function (key) {
794
- var itemRects = rowMap[key];
795
- rows.push(itemRects);
796
- var lastItem = itemRects[itemRects.length - 1];
797
- var end = lastItem.left + lastItem.width;
798
- var offset = Math.round((containerWidth - end) / 2);
799
- var finalRects = itemRects;
800
- var canMove = false;
801
-
802
- if (offset > 0) {
803
- var newRects = [];
804
- canMove = itemRects.every(function (r) {
805
- var newRect = new Rect(r.left + offset, r.top, r.width, r.height, r.id); // Check all current rects to make sure none overlap.
806
-
807
- var noOverlap = !rects.some(function (r) {
808
- return Rect.intersects(newRect, r);
809
- });
810
- newRects.push(newRect);
811
- return noOverlap;
812
- }); // If none of the rectangles overlapped, the whole group can be centered.
813
-
814
- if (canMove) {
815
- finalRects = newRects;
816
- }
817
- } // If the items are not going to be offset, ensure that the original
818
- // placement for this row will not overlap previous rows (row-spanning
819
- // elements could be in the way).
820
-
821
-
822
- if (!canMove) {
823
- var intersectingRect;
824
- var hasOverlap = itemRects.some(function (itemRect) {
825
- return rects.some(function (r) {
826
- var intersects = Rect.intersects(itemRect, r);
827
-
828
- if (intersects) {
829
- intersectingRect = r;
830
- }
831
-
832
- return intersects;
833
- });
834
- }); // If there is any overlap, replace the overlapping row with the original.
835
-
836
- if (hasOverlap) {
837
- var rowIndex = centeredRows.findIndex(function (items) {
838
- return items.includes(intersectingRect);
839
- });
840
- centeredRows.splice(rowIndex, 1, rows[rowIndex]);
841
- }
842
- }
843
-
844
- rects = rects.concat(finalRects);
845
- centeredRows.push(finalRects);
846
- }); // Reduce array of arrays to a single array of points.
847
- // https://stackoverflow.com/a/10865042/373422
848
- // Then reset sort back to how the items were passed to this method.
849
- // Remove the wrapper object with index, map to a Point.
850
-
851
- return [].concat.apply([], centeredRows) // eslint-disable-line prefer-spread
852
- .sort(function (a, b) {
853
- return a.id - b.id;
854
- }).map(function (itemRect) {
855
- return new Point(itemRect.left, itemRect.top);
856
- });
857
- }
858
-
859
- /**
860
- * Hyphenates a javascript style string to a css one. For example:
861
- * MozBoxSizing -> -moz-box-sizing.
862
- * @param {string} str The string to hyphenate.
863
- * @return {string} The hyphenated string.
864
- */
865
- function hyphenate(str) {
866
- return str.replace(/([A-Z])/g, function (str, m1) {
867
- return "-".concat(m1.toLowerCase());
868
- });
869
- }
870
-
871
- function arrayUnique(x) {
872
- return Array.from(new Set(x));
873
- } // Used for unique instance variables
874
-
875
-
876
- var id$1 = 0;
877
-
878
- var Shuffle =
879
- /*#__PURE__*/
880
- function (_TinyEmitter) {
881
- _inherits(Shuffle, _TinyEmitter);
882
-
883
- /**
884
- * Categorize, sort, and filter a responsive grid of items.
885
- *
886
- * @param {Element} element An element which is the parent container for the grid items.
887
- * @param {Object} [options=Shuffle.options] Options object.
888
- * @constructor
889
- */
890
- function Shuffle(element) {
891
- var _this;
892
-
893
- var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
894
-
895
- _classCallCheck(this, Shuffle);
896
-
897
- _this = _possibleConstructorReturn(this, _getPrototypeOf(Shuffle).call(this));
898
- _this.options = Object.assign({}, Shuffle.options, options); // Allow misspelling of delimiter since that's how it used to be.
899
- // Remove in v6.
900
-
901
- if (_this.options.delimeter) {
902
- _this.options.delimiter = _this.options.delimeter;
903
- }
904
-
905
- _this.lastSort = {};
906
- _this.group = Shuffle.ALL_ITEMS;
907
- _this.lastFilter = Shuffle.ALL_ITEMS;
908
- _this.isEnabled = true;
909
- _this.isDestroyed = false;
910
- _this.isInitialized = false;
911
- _this._transitions = [];
912
- _this.isTransitioning = false;
913
- _this._queue = [];
914
-
915
- var el = _this._getElementOption(element);
916
-
917
- if (!el) {
918
- throw new TypeError('Shuffle needs to be initialized with an element.');
919
- }
920
-
921
- _this.element = el;
922
- _this.id = 'shuffle_' + id$1;
923
- id$1 += 1;
924
-
925
- _this._init();
926
-
927
- _this.isInitialized = true;
928
- return _this;
929
- }
930
-
931
- _createClass(Shuffle, [{
932
- key: "_init",
933
- value: function _init() {
934
- this.items = this._getItems();
935
- this.options.sizer = this._getElementOption(this.options.sizer); // Add class and invalidate styles
936
-
937
- this.element.classList.add(Shuffle.Classes.BASE); // Set initial css for each item
938
-
939
- this._initItems(this.items); // Bind resize events
940
-
941
-
942
- this._onResize = this._getResizeFunction();
943
- window.addEventListener('resize', this._onResize); // If the page has not already emitted the `load` event, call layout on load.
944
- // This avoids layout issues caused by images and fonts loading after the
945
- // instance has been initialized.
946
-
947
- if (document.readyState !== 'complete') {
948
- var layout = this.layout.bind(this);
949
- window.addEventListener('load', function onLoad() {
950
- window.removeEventListener('load', onLoad);
951
- layout();
952
- });
953
- } // Get container css all in one request. Causes reflow
954
-
955
-
956
- var containerCss = window.getComputedStyle(this.element, null);
957
- var containerWidth = Shuffle.getSize(this.element).width; // Add styles to the container if it doesn't have them.
958
-
959
- this._validateStyles(containerCss); // We already got the container's width above, no need to cause another
960
- // reflow getting it again... Calculate the number of columns there will be
961
-
962
-
963
- this._setColumns(containerWidth); // Kick off!
964
-
965
-
966
- this.filter(this.options.group, this.options.initialSort); // The shuffle items haven't had transitions set on them yet so the user
967
- // doesn't see the first layout. Set them now that the first layout is done.
968
- // First, however, a synchronous layout must be caused for the previous
969
- // styles to be applied without transitions.
970
-
971
- this.element.offsetWidth; // eslint-disable-line no-unused-expressions
972
-
973
- this.setItemTransitions(this.items);
974
- this.element.style.transition = "height ".concat(this.options.speed, "ms ").concat(this.options.easing);
975
- }
976
- /**
977
- * Returns a throttled and proxied function for the resize handler.
978
- * @return {function}
979
- * @private
980
- */
981
-
982
- }, {
983
- key: "_getResizeFunction",
984
- value: function _getResizeFunction() {
985
- var resizeFunction = this._handleResize.bind(this);
986
-
987
- return this.options.throttle ? this.options.throttle(resizeFunction, this.options.throttleTime) : resizeFunction;
988
- }
989
- /**
990
- * Retrieve an element from an option.
991
- * @param {string|jQuery|Element} option The option to check.
992
- * @return {?Element} The plain element or null.
993
- * @private
994
- */
995
-
996
- }, {
997
- key: "_getElementOption",
998
- value: function _getElementOption(option) {
999
- // If column width is a string, treat is as a selector and search for the
1000
- // sizer element within the outermost container
1001
- if (typeof option === 'string') {
1002
- return this.element.querySelector(option);
1003
- } // Check for an element
1004
-
1005
-
1006
- if (option && option.nodeType && option.nodeType === 1) {
1007
- return option;
1008
- } // Check for jQuery object
1009
-
1010
-
1011
- if (option && option.jquery) {
1012
- return option[0];
1013
- }
1014
-
1015
- return null;
1016
- }
1017
- /**
1018
- * Ensures the shuffle container has the css styles it needs applied to it.
1019
- * @param {Object} styles Key value pairs for position and overflow.
1020
- * @private
1021
- */
1022
-
1023
- }, {
1024
- key: "_validateStyles",
1025
- value: function _validateStyles(styles) {
1026
- // Position cannot be static.
1027
- if (styles.position === 'static') {
1028
- this.element.style.position = 'relative';
1029
- } // Overflow has to be hidden.
1030
-
1031
-
1032
- if (styles.overflow !== 'hidden') {
1033
- this.element.style.overflow = 'hidden';
1034
- }
1035
- }
1036
- /**
1037
- * Filter the elements by a category.
1038
- * @param {string|string[]|function(Element):boolean} [category] Category to
1039
- * filter by. If it's given, the last category will be used to filter the items.
1040
- * @param {Array} [collection] Optionally filter a collection. Defaults to
1041
- * all the items.
1042
- * @return {{visible: ShuffleItem[], hidden: ShuffleItem[]}}
1043
- * @private
1044
- */
1045
-
1046
- }, {
1047
- key: "_filter",
1048
- value: function _filter() {
1049
- var category = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.lastFilter;
1050
- var collection = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.items;
1051
-
1052
- var set = this._getFilteredSets(category, collection); // Individually add/remove hidden/visible classes
1053
-
1054
-
1055
- this._toggleFilterClasses(set); // Save the last filter in case elements are appended.
1056
-
1057
-
1058
- this.lastFilter = category; // This is saved mainly because providing a filter function (like searching)
1059
- // will overwrite the `lastFilter` property every time its called.
1060
-
1061
- if (typeof category === 'string') {
1062
- this.group = category;
1063
- }
1064
-
1065
- return set;
1066
- }
1067
- /**
1068
- * Returns an object containing the visible and hidden elements.
1069
- * @param {string|string[]|function(Element):boolean} category Category or function to filter by.
1070
- * @param {ShuffleItem[]} items A collection of items to filter.
1071
- * @return {{visible: ShuffleItem[], hidden: ShuffleItem[]}}
1072
- * @private
1073
- */
1074
-
1075
- }, {
1076
- key: "_getFilteredSets",
1077
- value: function _getFilteredSets(category, items) {
1078
- var _this2 = this;
1079
-
1080
- var visible = [];
1081
- var hidden = []; // category === 'all', add visible class to everything
1082
-
1083
- if (category === Shuffle.ALL_ITEMS) {
1084
- visible = items; // Loop through each item and use provided function to determine
1085
- // whether to hide it or not.
1086
- } else {
1087
- items.forEach(function (item) {
1088
- if (_this2._doesPassFilter(category, item.element)) {
1089
- visible.push(item);
1090
- } else {
1091
- hidden.push(item);
1092
- }
1093
- });
1094
- }
1095
-
1096
- return {
1097
- visible: visible,
1098
- hidden: hidden
1099
- };
1100
- }
1101
- /**
1102
- * Test an item to see if it passes a category.
1103
- * @param {string|string[]|function():boolean} category Category or function to filter by.
1104
- * @param {Element} element An element to test.
1105
- * @return {boolean} Whether it passes the category/filter.
1106
- * @private
1107
- */
1108
-
1109
- }, {
1110
- key: "_doesPassFilter",
1111
- value: function _doesPassFilter(category, element) {
1112
- if (typeof category === 'function') {
1113
- return category.call(element, element, this);
1114
- } // Check each element's data-groups attribute against the given category.
1115
-
1116
-
1117
- var attr = element.getAttribute('data-' + Shuffle.FILTER_ATTRIBUTE_KEY);
1118
- var keys = this.options.delimiter ? attr.split(this.options.delimiter) : JSON.parse(attr);
1119
-
1120
- function testCategory(category) {
1121
- return keys.includes(category);
1122
- }
1123
-
1124
- if (Array.isArray(category)) {
1125
- if (this.options.filterMode === Shuffle.FilterMode.ANY) {
1126
- return category.some(testCategory);
1127
- }
1128
-
1129
- return category.every(testCategory);
1130
- }
1131
-
1132
- return keys.includes(category);
1133
- }
1134
- /**
1135
- * Toggles the visible and hidden class names.
1136
- * @param {{visible, hidden}} Object with visible and hidden arrays.
1137
- * @private
1138
- */
1139
-
1140
- }, {
1141
- key: "_toggleFilterClasses",
1142
- value: function _toggleFilterClasses(_ref) {
1143
- var visible = _ref.visible,
1144
- hidden = _ref.hidden;
1145
- visible.forEach(function (item) {
1146
- item.show();
1147
- });
1148
- hidden.forEach(function (item) {
1149
- item.hide();
1150
- });
1151
- }
1152
- /**
1153
- * Set the initial css for each item
1154
- * @param {ShuffleItem[]} items Set to initialize.
1155
- * @private
1156
- */
1157
-
1158
- }, {
1159
- key: "_initItems",
1160
- value: function _initItems(items) {
1161
- items.forEach(function (item) {
1162
- item.init();
1163
- });
1164
- }
1165
- /**
1166
- * Remove element reference and styles.
1167
- * @param {ShuffleItem[]} items Set to dispose.
1168
- * @private
1169
- */
1170
-
1171
- }, {
1172
- key: "_disposeItems",
1173
- value: function _disposeItems(items) {
1174
- items.forEach(function (item) {
1175
- item.dispose();
1176
- });
1177
- }
1178
- /**
1179
- * Updates the visible item count.
1180
- * @private
1181
- */
1182
-
1183
- }, {
1184
- key: "_updateItemCount",
1185
- value: function _updateItemCount() {
1186
- this.visibleItems = this._getFilteredItems().length;
1187
- }
1188
- /**
1189
- * Sets css transform transition on a group of elements. This is not executed
1190
- * at the same time as `item.init` so that transitions don't occur upon
1191
- * initialization of a new Shuffle instance.
1192
- * @param {ShuffleItem[]} items Shuffle items to set transitions on.
1193
- * @protected
1194
- */
1195
-
1196
- }, {
1197
- key: "setItemTransitions",
1198
- value: function setItemTransitions(items) {
1199
- var _this$options = this.options,
1200
- speed = _this$options.speed,
1201
- easing = _this$options.easing;
1202
- var positionProps = this.options.useTransforms ? ['transform'] : ['top', 'left']; // Allow users to transtion other properties if they exist in the `before`
1203
- // css mapping of the shuffle item.
1204
-
1205
- var cssProps = Object.keys(ShuffleItem.Css.HIDDEN.before).map(function (k) {
1206
- return hyphenate(k);
1207
- });
1208
- var properties = positionProps.concat(cssProps).join();
1209
- items.forEach(function (item) {
1210
- item.element.style.transitionDuration = speed + 'ms';
1211
- item.element.style.transitionTimingFunction = easing;
1212
- item.element.style.transitionProperty = properties;
1213
- });
1214
- }
1215
- }, {
1216
- key: "_getItems",
1217
- value: function _getItems() {
1218
- var _this3 = this;
1219
-
1220
- return Array.from(this.element.children).filter(function (el) {
1221
- return matchesSelector(el, _this3.options.itemSelector);
1222
- }).map(function (el) {
1223
- return new ShuffleItem(el);
1224
- });
1225
- }
1226
- /**
1227
- * Combine the current items array with a new one and sort it by DOM order.
1228
- * @param {ShuffleItem[]} items Items to track.
1229
- * @return {ShuffleItem[]}
1230
- */
1231
-
1232
- }, {
1233
- key: "_mergeNewItems",
1234
- value: function _mergeNewItems(items) {
1235
- var children = Array.from(this.element.children);
1236
- return sorter(this.items.concat(items), {
1237
- by: function by(element) {
1238
- return children.indexOf(element);
1239
- }
1240
- });
1241
- }
1242
- }, {
1243
- key: "_getFilteredItems",
1244
- value: function _getFilteredItems() {
1245
- return this.items.filter(function (item) {
1246
- return item.isVisible;
1247
- });
1248
- }
1249
- }, {
1250
- key: "_getConcealedItems",
1251
- value: function _getConcealedItems() {
1252
- return this.items.filter(function (item) {
1253
- return !item.isVisible;
1254
- });
1255
- }
1256
- /**
1257
- * Returns the column size, based on column width and sizer options.
1258
- * @param {number} containerWidth Size of the parent container.
1259
- * @param {number} gutterSize Size of the gutters.
1260
- * @return {number}
1261
- * @private
1262
- */
1263
-
1264
- }, {
1265
- key: "_getColumnSize",
1266
- value: function _getColumnSize(containerWidth, gutterSize) {
1267
- var size; // If the columnWidth property is a function, then the grid is fluid
1268
-
1269
- if (typeof this.options.columnWidth === 'function') {
1270
- size = this.options.columnWidth(containerWidth); // columnWidth option isn't a function, are they using a sizing element?
1271
- } else if (this.options.sizer) {
1272
- size = Shuffle.getSize(this.options.sizer).width; // if not, how about the explicitly set option?
1273
- } else if (this.options.columnWidth) {
1274
- size = this.options.columnWidth; // or use the size of the first item
1275
- } else if (this.items.length > 0) {
1276
- size = Shuffle.getSize(this.items[0].element, true).width; // if there's no items, use size of container
1277
- } else {
1278
- size = containerWidth;
1279
- } // Don't let them set a column width of zero.
1280
-
1281
-
1282
- if (size === 0) {
1283
- size = containerWidth;
1284
- }
1285
-
1286
- return size + gutterSize;
1287
- }
1288
- /**
1289
- * Returns the gutter size, based on gutter width and sizer options.
1290
- * @param {number} containerWidth Size of the parent container.
1291
- * @return {number}
1292
- * @private
1293
- */
1294
-
1295
- }, {
1296
- key: "_getGutterSize",
1297
- value: function _getGutterSize(containerWidth) {
1298
- var size;
1299
-
1300
- if (typeof this.options.gutterWidth === 'function') {
1301
- size = this.options.gutterWidth(containerWidth);
1302
- } else if (this.options.sizer) {
1303
- size = getNumberStyle(this.options.sizer, 'marginLeft');
1304
- } else {
1305
- size = this.options.gutterWidth;
1306
- }
1307
-
1308
- return size;
1309
- }
1310
- /**
1311
- * Calculate the number of columns to be used. Gets css if using sizer element.
1312
- * @param {number} [containerWidth] Optionally specify a container width if
1313
- * it's already available.
1314
- */
1315
-
1316
- }, {
1317
- key: "_setColumns",
1318
- value: function _setColumns() {
1319
- var containerWidth = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Shuffle.getSize(this.element).width;
1320
-
1321
- var gutter = this._getGutterSize(containerWidth);
1322
-
1323
- var columnWidth = this._getColumnSize(containerWidth, gutter);
1324
-
1325
- var calculatedColumns = (containerWidth + gutter) / columnWidth; // Widths given from getStyles are not precise enough...
1326
-
1327
- if (Math.abs(Math.round(calculatedColumns) - calculatedColumns) < this.options.columnThreshold) {
1328
- // e.g. calculatedColumns = 11.998876
1329
- calculatedColumns = Math.round(calculatedColumns);
1330
- }
1331
-
1332
- this.cols = Math.max(Math.floor(calculatedColumns || 0), 1);
1333
- this.containerWidth = containerWidth;
1334
- this.colWidth = columnWidth;
1335
- }
1336
- /**
1337
- * Adjust the height of the grid
1338
- */
1339
-
1340
- }, {
1341
- key: "_setContainerSize",
1342
- value: function _setContainerSize() {
1343
- this.element.style.height = this._getContainerSize() + 'px';
1344
- }
1345
- /**
1346
- * Based on the column heights, it returns the biggest one.
1347
- * @return {number}
1348
- * @private
1349
- */
1350
-
1351
- }, {
1352
- key: "_getContainerSize",
1353
- value: function _getContainerSize() {
1354
- return arrayMax(this.positions);
1355
- }
1356
- /**
1357
- * Get the clamped stagger amount.
1358
- * @param {number} index Index of the item to be staggered.
1359
- * @return {number}
1360
- */
1361
-
1362
- }, {
1363
- key: "_getStaggerAmount",
1364
- value: function _getStaggerAmount(index) {
1365
- return Math.min(index * this.options.staggerAmount, this.options.staggerAmountMax);
1366
- }
1367
- /**
1368
- * Emit an event from this instance.
1369
- * @param {string} name Event name.
1370
- * @param {Object} [data={}] Optional object data.
1371
- */
1372
-
1373
- }, {
1374
- key: "_dispatch",
1375
- value: function _dispatch(name) {
1376
- var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1377
-
1378
- if (this.isDestroyed) {
1379
- return;
1380
- }
1381
-
1382
- data.shuffle = this;
1383
- this.emit(name, data);
1384
- }
1385
- /**
1386
- * Zeros out the y columns array, which is used to determine item placement.
1387
- * @private
1388
- */
1389
-
1390
- }, {
1391
- key: "_resetCols",
1392
- value: function _resetCols() {
1393
- var i = this.cols;
1394
- this.positions = [];
1395
-
1396
- while (i) {
1397
- i -= 1;
1398
- this.positions.push(0);
1399
- }
1400
- }
1401
- /**
1402
- * Loops through each item that should be shown and calculates the x, y position.
1403
- * @param {ShuffleItem[]} items Array of items that will be shown/layed
1404
- * out in order in their array.
1405
- */
1406
-
1407
- }, {
1408
- key: "_layout",
1409
- value: function _layout(items) {
1410
- var _this4 = this;
1411
-
1412
- var itemPositions = this._getNextPositions(items);
1413
-
1414
- var count = 0;
1415
- items.forEach(function (item, i) {
1416
- function callback() {
1417
- item.applyCss(ShuffleItem.Css.VISIBLE.after);
1418
- } // If the item will not change its position, do not add it to the render
1419
- // queue. Transitions don't fire when setting a property to the same value.
1420
-
1421
-
1422
- if (Point.equals(item.point, itemPositions[i]) && !item.isHidden) {
1423
- item.applyCss(ShuffleItem.Css.VISIBLE.before);
1424
- callback();
1425
- return;
1426
- }
1427
-
1428
- item.point = itemPositions[i];
1429
- item.scale = ShuffleItem.Scale.VISIBLE;
1430
- item.isHidden = false; // Clone the object so that the `before` object isn't modified when the
1431
- // transition delay is added.
1432
-
1433
- var styles = _this4.getStylesForTransition(item, ShuffleItem.Css.VISIBLE.before);
1434
-
1435
- styles.transitionDelay = _this4._getStaggerAmount(count) + 'ms';
1436
-
1437
- _this4._queue.push({
1438
- item: item,
1439
- styles: styles,
1440
- callback: callback
1441
- });
1442
-
1443
- count += 1;
1444
- });
1445
- }
1446
- /**
1447
- * Return an array of Point instances representing the future positions of
1448
- * each item.
1449
- * @param {ShuffleItem[]} items Array of sorted shuffle items.
1450
- * @return {Point[]}
1451
- * @private
1452
- */
1453
-
1454
- }, {
1455
- key: "_getNextPositions",
1456
- value: function _getNextPositions(items) {
1457
- var _this5 = this;
1458
-
1459
- // If position data is going to be changed, add the item's size to the
1460
- // transformer to allow for calculations.
1461
- if (this.options.isCentered) {
1462
- var itemsData = items.map(function (item, i) {
1463
- var itemSize = Shuffle.getSize(item.element, true);
1464
-
1465
- var point = _this5._getItemPosition(itemSize);
1466
-
1467
- return new Rect(point.x, point.y, itemSize.width, itemSize.height, i);
1468
- });
1469
- return this.getTransformedPositions(itemsData, this.containerWidth);
1470
- } // If no transforms are going to happen, simply return an array of the
1471
- // future points of each item.
1472
-
1473
-
1474
- return items.map(function (item) {
1475
- return _this5._getItemPosition(Shuffle.getSize(item.element, true));
1476
- });
1477
- }
1478
- /**
1479
- * Determine the location of the next item, based on its size.
1480
- * @param {{width: number, height: number}} itemSize Object with width and height.
1481
- * @return {Point}
1482
- * @private
1483
- */
1484
-
1485
- }, {
1486
- key: "_getItemPosition",
1487
- value: function _getItemPosition(itemSize) {
1488
- return getItemPosition({
1489
- itemSize: itemSize,
1490
- positions: this.positions,
1491
- gridSize: this.colWidth,
1492
- total: this.cols,
1493
- threshold: this.options.columnThreshold,
1494
- buffer: this.options.buffer
1495
- });
1496
- }
1497
- /**
1498
- * Mutate positions before they're applied.
1499
- * @param {Rect[]} itemRects Item data objects.
1500
- * @param {number} containerWidth Width of the containing element.
1501
- * @return {Point[]}
1502
- * @protected
1503
- */
1504
-
1505
- }, {
1506
- key: "getTransformedPositions",
1507
- value: function getTransformedPositions(itemRects, containerWidth) {
1508
- return getCenteredPositions(itemRects, containerWidth);
1509
- }
1510
- /**
1511
- * Hides the elements that don't match our filter.
1512
- * @param {ShuffleItem[]} collection Collection to shrink.
1513
- * @private
1514
- */
1515
-
1516
- }, {
1517
- key: "_shrink",
1518
- value: function _shrink() {
1519
- var _this6 = this;
1520
-
1521
- var collection = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this._getConcealedItems();
1522
- var count = 0;
1523
- collection.forEach(function (item) {
1524
- function callback() {
1525
- item.applyCss(ShuffleItem.Css.HIDDEN.after);
1526
- } // Continuing would add a transitionend event listener to the element, but
1527
- // that listener would not execute because the transform and opacity would
1528
- // stay the same.
1529
- // The callback is executed here because it is not guaranteed to be called
1530
- // after the transitionend event because the transitionend could be
1531
- // canceled if another animation starts.
1532
-
1533
-
1534
- if (item.isHidden) {
1535
- item.applyCss(ShuffleItem.Css.HIDDEN.before);
1536
- callback();
1537
- return;
1538
- }
1539
-
1540
- item.scale = ShuffleItem.Scale.HIDDEN;
1541
- item.isHidden = true;
1542
-
1543
- var styles = _this6.getStylesForTransition(item, ShuffleItem.Css.HIDDEN.before);
1544
-
1545
- styles.transitionDelay = _this6._getStaggerAmount(count) + 'ms';
1546
-
1547
- _this6._queue.push({
1548
- item: item,
1549
- styles: styles,
1550
- callback: callback
1551
- });
1552
-
1553
- count += 1;
1554
- });
1555
- }
1556
- /**
1557
- * Resize handler.
1558
- * @private
1559
- */
1560
-
1561
- }, {
1562
- key: "_handleResize",
1563
- value: function _handleResize() {
1564
- // If shuffle is disabled, destroyed, don't do anything
1565
- if (!this.isEnabled || this.isDestroyed) {
1566
- return;
1567
- }
1568
-
1569
- this.update();
1570
- }
1571
- /**
1572
- * Returns styles which will be applied to the an item for a transition.
1573
- * @param {ShuffleItem} item Item to get styles for. Should have updated
1574
- * scale and point properties.
1575
- * @param {Object} styleObject Extra styles that will be used in the transition.
1576
- * @return {!Object} Transforms for transitions, left/top for animate.
1577
- * @protected
1578
- */
1579
-
1580
- }, {
1581
- key: "getStylesForTransition",
1582
- value: function getStylesForTransition(item, styleObject) {
1583
- // Clone the object to avoid mutating the original.
1584
- var styles = Object.assign({}, styleObject);
1585
-
1586
- if (this.options.useTransforms) {
1587
- var x = this.options.roundTransforms ? Math.round(item.point.x) : item.point.x;
1588
- var y = this.options.roundTransforms ? Math.round(item.point.y) : item.point.y;
1589
- styles.transform = "translate(".concat(x, "px, ").concat(y, "px) scale(").concat(item.scale, ")");
1590
- } else {
1591
- styles.left = item.point.x + 'px';
1592
- styles.top = item.point.y + 'px';
1593
- }
1594
-
1595
- return styles;
1596
- }
1597
- /**
1598
- * Listen for the transition end on an element and execute the itemCallback
1599
- * when it finishes.
1600
- * @param {Element} element Element to listen on.
1601
- * @param {function} itemCallback Callback for the item.
1602
- * @param {function} done Callback to notify `parallel` that this one is done.
1603
- */
1604
-
1605
- }, {
1606
- key: "_whenTransitionDone",
1607
- value: function _whenTransitionDone(element, itemCallback, done) {
1608
- var id = onTransitionEnd(element, function (evt) {
1609
- itemCallback();
1610
- done(null, evt);
1611
- });
1612
-
1613
- this._transitions.push(id);
1614
- }
1615
- /**
1616
- * Return a function which will set CSS styles and call the `done` function
1617
- * when (if) the transition finishes.
1618
- * @param {Object} opts Transition object.
1619
- * @return {function} A function to be called with a `done` function.
1620
- */
1621
-
1622
- }, {
1623
- key: "_getTransitionFunction",
1624
- value: function _getTransitionFunction(opts) {
1625
- var _this7 = this;
1626
-
1627
- return function (done) {
1628
- opts.item.applyCss(opts.styles);
1629
-
1630
- _this7._whenTransitionDone(opts.item.element, opts.callback, done);
1631
- };
1632
- }
1633
- /**
1634
- * Execute the styles gathered in the style queue. This applies styles to elements,
1635
- * triggering transitions.
1636
- * @private
1637
- */
1638
-
1639
- }, {
1640
- key: "_processQueue",
1641
- value: function _processQueue() {
1642
- if (this.isTransitioning) {
1643
- this._cancelMovement();
1644
- }
1645
-
1646
- var hasSpeed = this.options.speed > 0;
1647
- var hasQueue = this._queue.length > 0;
1648
-
1649
- if (hasQueue && hasSpeed && this.isInitialized) {
1650
- this._startTransitions(this._queue);
1651
- } else if (hasQueue) {
1652
- this._styleImmediately(this._queue);
1653
-
1654
- this._dispatch(Shuffle.EventType.LAYOUT); // A call to layout happened, but none of the newly visible items will
1655
- // change position or the transition duration is zero, which will not trigger
1656
- // the transitionend event.
1657
-
1658
- } else {
1659
- this._dispatch(Shuffle.EventType.LAYOUT);
1660
- } // Remove everything in the style queue
1661
-
1662
-
1663
- this._queue.length = 0;
1664
- }
1665
- /**
1666
- * Wait for each transition to finish, the emit the layout event.
1667
- * @param {Object[]} transitions Array of transition objects.
1668
- */
1669
-
1670
- }, {
1671
- key: "_startTransitions",
1672
- value: function _startTransitions(transitions) {
1673
- var _this8 = this;
1674
-
1675
- // Set flag that shuffle is currently in motion.
1676
- this.isTransitioning = true; // Create an array of functions to be called.
1677
-
1678
- var callbacks = transitions.map(function (obj) {
1679
- return _this8._getTransitionFunction(obj);
1680
- });
1681
- arrayParallel(callbacks, this._movementFinished.bind(this));
1682
- }
1683
- }, {
1684
- key: "_cancelMovement",
1685
- value: function _cancelMovement() {
1686
- // Remove the transition end event for each listener.
1687
- this._transitions.forEach(cancelTransitionEnd); // Reset the array.
1688
-
1689
-
1690
- this._transitions.length = 0; // Show it's no longer active.
1691
-
1692
- this.isTransitioning = false;
1693
- }
1694
- /**
1695
- * Apply styles without a transition.
1696
- * @param {Object[]} objects Array of transition objects.
1697
- * @private
1698
- */
1699
-
1700
- }, {
1701
- key: "_styleImmediately",
1702
- value: function _styleImmediately(objects) {
1703
- if (objects.length) {
1704
- var elements = objects.map(function (obj) {
1705
- return obj.item.element;
1706
- });
1707
-
1708
- Shuffle._skipTransitions(elements, function () {
1709
- objects.forEach(function (obj) {
1710
- obj.item.applyCss(obj.styles);
1711
- obj.callback();
1712
- });
1713
- });
1714
- }
1715
- }
1716
- }, {
1717
- key: "_movementFinished",
1718
- value: function _movementFinished() {
1719
- this._transitions.length = 0;
1720
- this.isTransitioning = false;
1721
-
1722
- this._dispatch(Shuffle.EventType.LAYOUT);
1723
- }
1724
- /**
1725
- * The magic. This is what makes the plugin 'shuffle'
1726
- * @param {string|string[]|function(Element):boolean} [category] Category to filter by.
1727
- * Can be a function, string, or array of strings.
1728
- * @param {SortOptions} [sortOptions] A sort object which can sort the visible set
1729
- */
1730
-
1731
- }, {
1732
- key: "filter",
1733
- value: function filter(category, sortOptions) {
1734
- if (!this.isEnabled) {
1735
- return;
1736
- }
1737
-
1738
- if (!category || category && category.length === 0) {
1739
- category = Shuffle.ALL_ITEMS; // eslint-disable-line no-param-reassign
1740
- }
1741
-
1742
- this._filter(category); // Shrink each hidden item
1743
-
1744
-
1745
- this._shrink(); // How many visible elements?
1746
-
1747
-
1748
- this._updateItemCount(); // Update transforms on visible elements so they will animate to their new positions.
1749
-
1750
-
1751
- this.sort(sortOptions);
1752
- }
1753
- /**
1754
- * Gets the visible elements, sorts them, and passes them to layout.
1755
- * @param {SortOptions} [sortOptions] The options object to pass to `sorter`.
1756
- */
1757
-
1758
- }, {
1759
- key: "sort",
1760
- value: function sort() {
1761
- var sortOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.lastSort;
1762
-
1763
- if (!this.isEnabled) {
1764
- return;
1765
- }
1766
-
1767
- this._resetCols();
1768
-
1769
- var items = sorter(this._getFilteredItems(), sortOptions);
1770
-
1771
- this._layout(items); // `_layout` always happens after `_shrink`, so it's safe to process the style
1772
- // queue here with styles from the shrink method.
1773
-
1774
-
1775
- this._processQueue(); // Adjust the height of the container.
1776
-
1777
-
1778
- this._setContainerSize();
1779
-
1780
- this.lastSort = sortOptions;
1781
- }
1782
- /**
1783
- * Reposition everything.
1784
- * @param {boolean} [isOnlyLayout=false] If true, column and gutter widths won't be recalculated.
1785
- */
1786
-
1787
- }, {
1788
- key: "update",
1789
- value: function update() {
1790
- var isOnlyLayout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
1791
-
1792
- if (this.isEnabled) {
1793
- if (!isOnlyLayout) {
1794
- // Get updated colCount
1795
- this._setColumns();
1796
- } // Layout items
1797
-
1798
-
1799
- this.sort();
1800
- }
1801
- }
1802
- /**
1803
- * Use this instead of `update()` if you don't need the columns and gutters updated
1804
- * Maybe an image inside `shuffle` loaded (and now has a height), which means calculations
1805
- * could be off.
1806
- */
1807
-
1808
- }, {
1809
- key: "layout",
1810
- value: function layout() {
1811
- this.update(true);
1812
- }
1813
- /**
1814
- * New items have been appended to shuffle. Mix them in with the current
1815
- * filter or sort status.
1816
- * @param {Element[]} newItems Collection of new items.
1817
- */
1818
-
1819
- }, {
1820
- key: "add",
1821
- value: function add(newItems) {
1822
- var _this9 = this;
1823
-
1824
- var items = arrayUnique(newItems).map(function (el) {
1825
- return new ShuffleItem(el);
1826
- }); // Add classes and set initial positions.
1827
-
1828
- this._initItems(items); // Determine which items will go with the current filter.
1829
-
1830
-
1831
- this._resetCols();
1832
-
1833
- var allItems = this._mergeNewItems(items);
1834
-
1835
- var sortedItems = sorter(allItems, this.lastSort);
1836
-
1837
- var allSortedItemsSet = this._filter(this.lastFilter, sortedItems);
1838
-
1839
- var isNewItem = function isNewItem(item) {
1840
- return items.includes(item);
1841
- };
1842
-
1843
- var applyHiddenState = function applyHiddenState(item) {
1844
- item.scale = ShuffleItem.Scale.HIDDEN;
1845
- item.isHidden = true;
1846
- item.applyCss(ShuffleItem.Css.HIDDEN.before);
1847
- item.applyCss(ShuffleItem.Css.HIDDEN.after);
1848
- }; // Layout all items again so that new items get positions.
1849
- // Synchonously apply positions.
1850
-
1851
-
1852
- var itemPositions = this._getNextPositions(allSortedItemsSet.visible);
1853
-
1854
- allSortedItemsSet.visible.forEach(function (item, i) {
1855
- if (isNewItem(item)) {
1856
- item.point = itemPositions[i];
1857
- applyHiddenState(item);
1858
- item.applyCss(_this9.getStylesForTransition(item, {}));
1859
- }
1860
- });
1861
- allSortedItemsSet.hidden.forEach(function (item) {
1862
- if (isNewItem(item)) {
1863
- applyHiddenState(item);
1864
- }
1865
- }); // Cause layout so that the styles above are applied.
1866
-
1867
- this.element.offsetWidth; // eslint-disable-line no-unused-expressions
1868
- // Add transition to each item.
1869
-
1870
- this.setItemTransitions(items); // Update the list of items.
1871
-
1872
- this.items = this._mergeNewItems(items); // Update layout/visibility of new and old items.
1873
-
1874
- this.filter(this.lastFilter);
1875
- }
1876
- /**
1877
- * Disables shuffle from updating dimensions and layout on resize
1878
- */
1879
-
1880
- }, {
1881
- key: "disable",
1882
- value: function disable() {
1883
- this.isEnabled = false;
1884
- }
1885
- /**
1886
- * Enables shuffle again
1887
- * @param {boolean} [isUpdateLayout=true] if undefined, shuffle will update columns and gutters
1888
- */
1889
-
1890
- }, {
1891
- key: "enable",
1892
- value: function enable() {
1893
- var isUpdateLayout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
1894
- this.isEnabled = true;
1895
-
1896
- if (isUpdateLayout) {
1897
- this.update();
1898
- }
1899
- }
1900
- /**
1901
- * Remove 1 or more shuffle items.
1902
- * @param {Element[]} elements An array containing one or more
1903
- * elements in shuffle
1904
- * @return {Shuffle} The shuffle instance.
1905
- */
1906
-
1907
- }, {
1908
- key: "remove",
1909
- value: function remove(elements) {
1910
- var _this10 = this;
1911
-
1912
- if (!elements.length) {
1913
- return;
1914
- }
1915
-
1916
- var collection = arrayUnique(elements);
1917
- var oldItems = collection.map(function (element) {
1918
- return _this10.getItemByElement(element);
1919
- }).filter(function (item) {
1920
- return !!item;
1921
- });
1922
-
1923
- var handleLayout = function handleLayout() {
1924
- _this10._disposeItems(oldItems); // Remove the collection in the callback
1925
-
1926
-
1927
- collection.forEach(function (element) {
1928
- element.parentNode.removeChild(element);
1929
- });
1930
-
1931
- _this10._dispatch(Shuffle.EventType.REMOVED, {
1932
- collection: collection
1933
- });
1934
- }; // Hide collection first.
1935
-
1936
-
1937
- this._toggleFilterClasses({
1938
- visible: [],
1939
- hidden: oldItems
1940
- });
1941
-
1942
- this._shrink(oldItems);
1943
-
1944
- this.sort(); // Update the list of items here because `remove` could be called again
1945
- // with an item that is in the process of being removed.
1946
-
1947
- this.items = this.items.filter(function (item) {
1948
- return !oldItems.includes(item);
1949
- });
1950
-
1951
- this._updateItemCount();
1952
-
1953
- this.once(Shuffle.EventType.LAYOUT, handleLayout);
1954
- }
1955
- /**
1956
- * Retrieve a shuffle item by its element.
1957
- * @param {Element} element Element to look for.
1958
- * @return {?ShuffleItem} A shuffle item or undefined if it's not found.
1959
- */
1960
-
1961
- }, {
1962
- key: "getItemByElement",
1963
- value: function getItemByElement(element) {
1964
- return this.items.find(function (item) {
1965
- return item.element === element;
1966
- });
1967
- }
1968
- /**
1969
- * Dump the elements currently stored and reinitialize all child elements which
1970
- * match the `itemSelector`.
1971
- */
1972
-
1973
- }, {
1974
- key: "resetItems",
1975
- value: function resetItems() {
1976
- var _this11 = this;
1977
-
1978
- // Remove refs to current items.
1979
- this._disposeItems(this.items);
1980
-
1981
- this.isInitialized = false; // Find new items in the DOM.
1982
-
1983
- this.items = this._getItems(); // Set initial styles on the new items.
1984
-
1985
- this._initItems(this.items);
1986
-
1987
- this.once(Shuffle.EventType.LAYOUT, function () {
1988
- // Add transition to each item.
1989
- _this11.setItemTransitions(_this11.items);
1990
-
1991
- _this11.isInitialized = true;
1992
- }); // Lay out all items.
1993
-
1994
- this.filter(this.lastFilter);
1995
- }
1996
- /**
1997
- * Destroys shuffle, removes events, styles, and classes
1998
- */
1999
-
2000
- }, {
2001
- key: "destroy",
2002
- value: function destroy() {
2003
- this._cancelMovement();
2004
-
2005
- window.removeEventListener('resize', this._onResize); // Reset container styles
2006
-
2007
- this.element.classList.remove('shuffle');
2008
- this.element.removeAttribute('style'); // Reset individual item styles
2009
-
2010
- this._disposeItems(this.items);
2011
-
2012
- this.items.length = 0;
2013
- this._transitions.length = 0; // Null DOM references
2014
-
2015
- this.options.sizer = null;
2016
- this.element = null; // Set a flag so if a debounced resize has been triggered,
2017
- // it can first check if it is actually isDestroyed and not doing anything
2018
-
2019
- this.isDestroyed = true;
2020
- this.isEnabled = false;
2021
- }
2022
- /**
2023
- * Returns the outer width of an element, optionally including its margins.
2024
- *
2025
- * There are a few different methods for getting the width of an element, none of
2026
- * which work perfectly for all Shuffle's use cases.
2027
- *
2028
- * 1. getBoundingClientRect() `left` and `right` properties.
2029
- * - Accounts for transform scaled elements, making it useless for Shuffle
2030
- * elements which have shrunk.
2031
- * 2. The `offsetWidth` property.
2032
- * - This value stays the same regardless of the elements transform property,
2033
- * however, it does not return subpixel values.
2034
- * 3. getComputedStyle()
2035
- * - This works great Chrome, Firefox, Safari, but IE<=11 does not include
2036
- * padding and border when box-sizing: border-box is set, requiring a feature
2037
- * test and extra work to add the padding back for IE and other browsers which
2038
- * follow the W3C spec here.
2039
- *
2040
- * @param {Element} element The element.
2041
- * @param {boolean} [includeMargins=false] Whether to include margins.
2042
- * @return {{width: number, height: number}} The width and height.
2043
- */
2044
-
2045
- }], [{
2046
- key: "getSize",
2047
- value: function getSize(element) {
2048
- var includeMargins = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
2049
- // Store the styles so that they can be used by others without asking for it again.
2050
- var styles = window.getComputedStyle(element, null);
2051
- var width = getNumberStyle(element, 'width', styles);
2052
- var height = getNumberStyle(element, 'height', styles);
2053
-
2054
- if (includeMargins) {
2055
- var marginLeft = getNumberStyle(element, 'marginLeft', styles);
2056
- var marginRight = getNumberStyle(element, 'marginRight', styles);
2057
- var marginTop = getNumberStyle(element, 'marginTop', styles);
2058
- var marginBottom = getNumberStyle(element, 'marginBottom', styles);
2059
- width += marginLeft + marginRight;
2060
- height += marginTop + marginBottom;
2061
- }
2062
-
2063
- return {
2064
- width: width,
2065
- height: height
2066
- };
2067
- }
2068
- /**
2069
- * Change a property or execute a function which will not have a transition
2070
- * @param {Element[]} elements DOM elements that won't be transitioned.
2071
- * @param {function} callback A function which will be called while transition
2072
- * is set to 0ms.
2073
- * @private
2074
- */
2075
-
2076
- }, {
2077
- key: "_skipTransitions",
2078
- value: function _skipTransitions(elements, callback) {
2079
- var zero = '0ms'; // Save current duration and delay.
2080
-
2081
- var data = elements.map(function (element) {
2082
- var style = element.style;
2083
- var duration = style.transitionDuration;
2084
- var delay = style.transitionDelay; // Set the duration to zero so it happens immediately
2085
-
2086
- style.transitionDuration = zero;
2087
- style.transitionDelay = zero;
2088
- return {
2089
- duration: duration,
2090
- delay: delay
2091
- };
2092
- });
2093
- callback(); // Cause forced synchronous layout.
2094
-
2095
- elements[0].offsetWidth; // eslint-disable-line no-unused-expressions
2096
- // Put the duration back
2097
-
2098
- elements.forEach(function (element, i) {
2099
- element.style.transitionDuration = data[i].duration;
2100
- element.style.transitionDelay = data[i].delay;
2101
- });
2102
- }
2103
- }]);
2104
-
2105
- return Shuffle;
2106
- }(tinyEmitter);
2107
-
2108
- Shuffle.ShuffleItem = ShuffleItem;
2109
- Shuffle.ALL_ITEMS = 'all';
2110
- Shuffle.FILTER_ATTRIBUTE_KEY = 'groups';
2111
- /** @enum {string} */
2112
-
2113
- Shuffle.EventType = {
2114
- LAYOUT: 'shuffle:layout',
2115
- REMOVED: 'shuffle:removed'
2116
- };
2117
- /** @enum {string} */
2118
-
2119
- Shuffle.Classes = Classes;
2120
- /** @enum {string} */
2121
-
2122
- Shuffle.FilterMode = {
2123
- ANY: 'any',
2124
- ALL: 'all'
2125
- }; // Overrideable options
2126
-
2127
- Shuffle.options = {
2128
- // Initial filter group.
2129
- group: Shuffle.ALL_ITEMS,
2130
- // Transition/animation speed (milliseconds).
2131
- speed: 250,
2132
- // CSS easing function to use.
2133
- easing: 'cubic-bezier(0.4, 0.0, 0.2, 1)',
2134
- // e.g. '.picture-item'.
2135
- itemSelector: '*',
2136
- // Element or selector string. Use an element to determine the size of columns
2137
- // and gutters.
2138
- sizer: null,
2139
- // A static number or function that tells the plugin how wide the gutters
2140
- // between columns are (in pixels).
2141
- gutterWidth: 0,
2142
- // A static number or function that returns a number which tells the plugin
2143
- // how wide the columns are (in pixels).
2144
- columnWidth: 0,
2145
- // If your group is not json, and is comma delimeted, you could set delimiter
2146
- // to ','.
2147
- delimiter: null,
2148
- // Useful for percentage based heights when they might not always be exactly
2149
- // the same (in pixels).
2150
- buffer: 0,
2151
- // Reading the width of elements isn't precise enough and can cause columns to
2152
- // jump between values.
2153
- columnThreshold: 0.01,
2154
- // Shuffle can be isInitialized with a sort object. It is the same object
2155
- // given to the sort method.
2156
- initialSort: null,
2157
- // By default, shuffle will throttle resize events. This can be changed or
2158
- // removed.
2159
- throttle: throttleit,
2160
- // How often shuffle can be called on resize (in milliseconds).
2161
- throttleTime: 300,
2162
- // Transition delay offset for each item in milliseconds.
2163
- staggerAmount: 15,
2164
- // Maximum stagger delay in milliseconds.
2165
- staggerAmountMax: 150,
2166
- // Whether to use transforms or absolute positioning.
2167
- useTransforms: true,
2168
- // Affects using an array with filter. e.g. `filter(['one', 'two'])`. With "any",
2169
- // the element passes the test if any of its groups are in the array. With "all",
2170
- // the element only passes if all groups are in the array.
2171
- filterMode: Shuffle.FilterMode.ANY,
2172
- // Attempt to center grid items in each row.
2173
- isCentered: false,
2174
- // Whether to round pixel values used in translate(x, y). This usually avoids
2175
- // blurriness.
2176
- roundTransforms: true
2177
- };
2178
- Shuffle.Point = Point;
2179
- Shuffle.Rect = Rect; // Expose for testing. Hack at your own risk.
2180
-
2181
- Shuffle.__sorter = sorter;
2182
- Shuffle.__getColumnSpan = getColumnSpan;
2183
- Shuffle.__getAvailablePositions = getAvailablePositions;
2184
- Shuffle.__getShortColumn = getShortColumn;
2185
- Shuffle.__getCenteredPositions = getCenteredPositions;
2186
-
2187
- return Shuffle;
2188
-
2189
- }));
2190
- //# sourceMappingURL=shuffle.js.map
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
resources/js/shuffle.min.js DELETED
@@ -1,2 +0,0 @@
1
- !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Shuffle=e()}(this,function(){"use strict";function t(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function e(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}function i(t,i,n){return i&&e(t.prototype,i),n&&e(t,n),t}function n(t){return(n=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function s(t,e){return(s=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}function o(t,e){return!e||"object"!=typeof e&&"function"!=typeof e?function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(t):e}function r(){}r.prototype={on:function(t,e,i){var n=this.e||(this.e={});return(n[t]||(n[t]=[])).push({fn:e,ctx:i}),this},once:function(t,e,i){var n=this;function s(){n.off(t,s),e.apply(i,arguments)}return s._=e,this.on(t,s,i)},emit:function(t){for(var e=[].slice.call(arguments,1),i=((this.e||(this.e={}))[t]||[]).slice(),n=0,s=i.length;n<s;n++)i[n].fn.apply(i[n].ctx,e);return this},off:function(t,e){var i=this.e||(this.e={}),n=i[t],s=[];if(n&&e)for(var o=0,r=n.length;o<r;o++)n[o].fn!==e&&n[o].fn._!==e&&s.push(n[o]);return s.length?i[t]=s:delete i[t],this}};var l=r,a=r;l.TinyEmitter=a;var u="undefined"!=typeof Element?Element.prototype:{},h=u.matches||u.matchesSelector||u.webkitMatchesSelector||u.mozMatchesSelector||u.msMatchesSelector||u.oMatchesSelector,f=function(t,e){if(!t||1!==t.nodeType)return!1;if(h)return h.call(t,e);for(var i=t.parentNode.querySelectorAll(e),n=0;n<i.length;n++)if(i[n]==t)return!0;return!1};var c=function(t,e){var i,n,s,o,r=0;return function(){i=this,n=arguments;var t=new Date-r;return o||(t>=e?l():o=setTimeout(l,e-t)),s};function l(){o=0,r=+new Date,s=t.apply(i,n),i=null,n=null}};function d(){}function m(t){return parseFloat(t)||0}var p=function(){function e(i,n){t(this,e),this.x=m(i),this.y=m(n)}return i(e,null,[{key:"equals",value:function(t,e){return t.x===e.x&&t.y===e.y}}]),e}(),v=function(){function e(i,n,s,o,r){t(this,e),this.id=r,this.left=i,this.top=n,this.width=s,this.height=o}return i(e,null,[{key:"intersects",value:function(t,e){return t.left<e.left+e.width&&e.left<t.left+t.width&&t.top<e.top+e.height&&e.top<t.top+t.height}}]),e}(),y={BASE:"shuffle",SHUFFLE_ITEM:"shuffle-item",VISIBLE:"shuffle-item--visible",HIDDEN:"shuffle-item--hidden"},g=0,_=function(){function e(i){t(this,e),g+=1,this.id=g,this.element=i,this.isVisible=!0,this.isHidden=!1}return i(e,[{key:"show",value:function(){this.isVisible=!0,this.element.classList.remove(y.HIDDEN),this.element.classList.add(y.VISIBLE),this.element.removeAttribute("aria-hidden")}},{key:"hide",value:function(){this.isVisible=!1,this.element.classList.remove(y.VISIBLE),this.element.classList.add(y.HIDDEN),this.element.setAttribute("aria-hidden",!0)}},{key:"init",value:function(){this.addClasses([y.SHUFFLE_ITEM,y.VISIBLE]),this.applyCss(e.Css.INITIAL),this.scale=e.Scale.VISIBLE,this.point=new p}},{key:"addClasses",value:function(t){var e=this;t.forEach(function(t){e.element.classList.add(t)})}},{key:"removeClasses",value:function(t){var e=this;t.forEach(function(t){e.element.classList.remove(t)})}},{key:"applyCss",value:function(t){var e=this;Object.keys(t).forEach(function(i){e.element.style[i]=t[i]})}},{key:"dispose",value:function(){this.removeClasses([y.HIDDEN,y.VISIBLE,y.SHUFFLE_ITEM]),this.element.removeAttribute("style"),this.element=null}}]),e}();_.Css={INITIAL:{position:"absolute",top:0,left:0,visibility:"visible",willChange:"transform"},VISIBLE:{before:{opacity:1,visibility:"visible"},after:{transitionDelay:""}},HIDDEN:{before:{opacity:0},after:{visibility:"hidden",transitionDelay:""}}},_.Scale={VISIBLE:1,HIDDEN:.001};var E=null,I=function(){if(null!==E)return E;var t=document.body||document.documentElement,e=document.createElement("div");return e.style.cssText="width:10px;padding:2px;box-sizing:border-box;",t.appendChild(e),E="10px"===window.getComputedStyle(e,null).width,t.removeChild(e),E};function b(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:window.getComputedStyle(t,null),n=m(i[e]);return I()||"width"!==e?I()||"height"!==e||(n+=m(i.paddingTop)+m(i.paddingBottom)+m(i.borderTopWidth)+m(i.borderBottomWidth)):n+=m(i.paddingLeft)+m(i.paddingRight)+m(i.borderLeftWidth)+m(i.borderRightWidth),n}var S={reverse:!1,by:null,compare:null,randomize:!1,key:"element"};function T(t,e){var i=Object.assign({},S,e),n=Array.from(t),s=!1;return t.length?i.randomize?function(t){for(var e=t.length;e;){e-=1;var i=Math.floor(Math.random()*(e+1)),n=t[i];t[i]=t[e],t[e]=n}return t}(t):("function"==typeof i.by?t.sort(function(t,e){if(s)return 0;var n=i.by(t[i.key]),o=i.by(e[i.key]);return void 0===n&&void 0===o?(s=!0,0):n<o||"sortFirst"===n||"sortLast"===o?-1:n>o||"sortLast"===n||"sortFirst"===o?1:0}):"function"==typeof i.compare&&t.sort(i.compare),s?n:(i.reverse&&t.reverse(),t)):[]}var k={},w="transitionend",C=0;function L(t){return!!k[t]&&(k[t].element.removeEventListener(w,k[t].listener),k[t]=null,!0)}function D(t,e){var i=w+(C+=1),n=function(t){t.currentTarget===t.target&&(L(i),e(t))};return t.addEventListener(w,n),k[i]={element:t,listener:n},i}function z(t){return Math.max.apply(Math,t)}function M(t,e,i,n){var s=t/e;return Math.abs(Math.round(s)-s)<n&&(s=Math.round(s)),Math.min(Math.ceil(s),i)}function A(t,e,i){if(1===e)return t;for(var n=[],s=0;s<=i-e;s++)n.push(z(t.slice(s,s+e)));return n}function F(t,e){for(var i,n=(i=t,Math.min.apply(Math,i)),s=0,o=t.length;s<o;s++)if(t[s]>=n-e&&t[s]<=n+e)return s;return 0}function x(t,e){var i={};t.forEach(function(t){i[t.top]?i[t.top].push(t):i[t.top]=[t]});var n=[],s=[],o=[];return Object.keys(i).forEach(function(t){var r=i[t];s.push(r);var l,a=r[r.length-1],u=a.left+a.width,h=Math.round((e-u)/2),f=r,c=!1;if(h>0){var d=[];(c=r.every(function(t){var e=new v(t.left+h,t.top,t.width,t.height,t.id),i=!n.some(function(t){return v.intersects(e,t)});return d.push(e),i}))&&(f=d)}if(!c&&r.some(function(t){return n.some(function(e){var i=v.intersects(t,e);return i&&(l=e),i})})){var m=o.findIndex(function(t){return t.includes(l)});o.splice(m,1,s[m])}n=n.concat(f),o.push(f)}),[].concat.apply([],o).sort(function(t,e){return t.id-e.id}).map(function(t){return new p(t.left,t.top)})}function O(t){return Array.from(new Set(t))}var N=0,H=function(e){function r(e){var i,s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t(this,r),(i=o(this,n(r).call(this))).options=Object.assign({},r.options,s),i.options.delimeter&&(i.options.delimiter=i.options.delimeter),i.lastSort={},i.group=r.ALL_ITEMS,i.lastFilter=r.ALL_ITEMS,i.isEnabled=!0,i.isDestroyed=!1,i.isInitialized=!1,i._transitions=[],i.isTransitioning=!1,i._queue=[];var l=i._getElementOption(e);if(!l)throw new TypeError("Shuffle needs to be initialized with an element.");return i.element=l,i.id="shuffle_"+N,N+=1,i._init(),i.isInitialized=!0,i}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),e&&s(t,e)}(r,l),i(r,[{key:"_init",value:function(){if(this.items=this._getItems(),this.options.sizer=this._getElementOption(this.options.sizer),this.element.classList.add(r.Classes.BASE),this._initItems(this.items),this._onResize=this._getResizeFunction(),window.addEventListener("resize",this._onResize),"complete"!==document.readyState){var t=this.layout.bind(this);window.addEventListener("load",function e(){window.removeEventListener("load",e),t()})}var e=window.getComputedStyle(this.element,null),i=r.getSize(this.element).width;this._validateStyles(e),this._setColumns(i),this.filter(this.options.group,this.options.initialSort),this.element.offsetWidth,this.setItemTransitions(this.items),this.element.style.transition="height ".concat(this.options.speed,"ms ").concat(this.options.easing)}},{key:"_getResizeFunction",value:function(){var t=this._handleResize.bind(this);return this.options.throttle?this.options.throttle(t,this.options.throttleTime):t}},{key:"_getElementOption",value:function(t){return"string"==typeof t?this.element.querySelector(t):t&&t.nodeType&&1===t.nodeType?t:t&&t.jquery?t[0]:null}},{key:"_validateStyles",value:function(t){"static"===t.position&&(this.element.style.position="relative"),"hidden"!==t.overflow&&(this.element.style.overflow="hidden")}},{key:"_filter",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.lastFilter,e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.items,i=this._getFilteredSets(t,e);return this._toggleFilterClasses(i),this.lastFilter=t,"string"==typeof t&&(this.group=t),i}},{key:"_getFilteredSets",value:function(t,e){var i=this,n=[],s=[];return t===r.ALL_ITEMS?n=e:e.forEach(function(e){i._doesPassFilter(t,e.element)?n.push(e):s.push(e)}),{visible:n,hidden:s}}},{key:"_doesPassFilter",value:function(t,e){if("function"==typeof t)return t.call(e,e,this);var i=e.getAttribute("data-"+r.FILTER_ATTRIBUTE_KEY),n=this.options.delimiter?i.split(this.options.delimiter):JSON.parse(i);function s(t){return n.includes(t)}return Array.isArray(t)?this.options.filterMode===r.FilterMode.ANY?t.some(s):t.every(s):n.includes(t)}},{key:"_toggleFilterClasses",value:function(t){var e=t.visible,i=t.hidden;e.forEach(function(t){t.show()}),i.forEach(function(t){t.hide()})}},{key:"_initItems",value:function(t){t.forEach(function(t){t.init()})}},{key:"_disposeItems",value:function(t){t.forEach(function(t){t.dispose()})}},{key:"_updateItemCount",value:function(){this.visibleItems=this._getFilteredItems().length}},{key:"setItemTransitions",value:function(t){var e=this.options,i=e.speed,n=e.easing,s=this.options.useTransforms?["transform"]:["top","left"],o=Object.keys(_.Css.HIDDEN.before).map(function(t){return t.replace(/([A-Z])/g,function(t,e){return"-".concat(e.toLowerCase())})}),r=s.concat(o).join();t.forEach(function(t){t.element.style.transitionDuration=i+"ms",t.element.style.transitionTimingFunction=n,t.element.style.transitionProperty=r})}},{key:"_getItems",value:function(){var t=this;return Array.from(this.element.children).filter(function(e){return f(e,t.options.itemSelector)}).map(function(t){return new _(t)})}},{key:"_mergeNewItems",value:function(t){var e=Array.from(this.element.children);return T(this.items.concat(t),{by:function(t){return e.indexOf(t)}})}},{key:"_getFilteredItems",value:function(){return this.items.filter(function(t){return t.isVisible})}},{key:"_getConcealedItems",value:function(){return this.items.filter(function(t){return!t.isVisible})}},{key:"_getColumnSize",value:function(t,e){var i;return 0===(i="function"==typeof this.options.columnWidth?this.options.columnWidth(t):this.options.sizer?r.getSize(this.options.sizer).width:this.options.columnWidth?this.options.columnWidth:this.items.length>0?r.getSize(this.items[0].element,!0).width:t)&&(i=t),i+e}},{key:"_getGutterSize",value:function(t){return"function"==typeof this.options.gutterWidth?this.options.gutterWidth(t):this.options.sizer?b(this.options.sizer,"marginLeft"):this.options.gutterWidth}},{key:"_setColumns",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:r.getSize(this.element).width,e=this._getGutterSize(t),i=this._getColumnSize(t,e),n=(t+e)/i;Math.abs(Math.round(n)-n)<this.options.columnThreshold&&(n=Math.round(n)),this.cols=Math.max(Math.floor(n||0),1),this.containerWidth=t,this.colWidth=i}},{key:"_setContainerSize",value:function(){this.element.style.height=this._getContainerSize()+"px"}},{key:"_getContainerSize",value:function(){return z(this.positions)}},{key:"_getStaggerAmount",value:function(t){return Math.min(t*this.options.staggerAmount,this.options.staggerAmountMax)}},{key:"_dispatch",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};this.isDestroyed||(e.shuffle=this,this.emit(t,e))}},{key:"_resetCols",value:function(){var t=this.cols;for(this.positions=[];t;)t-=1,this.positions.push(0)}},{key:"_layout",value:function(t){var e=this,i=this._getNextPositions(t),n=0;t.forEach(function(t,s){function o(){t.applyCss(_.Css.VISIBLE.after)}if(p.equals(t.point,i[s])&&!t.isHidden)return t.applyCss(_.Css.VISIBLE.before),void o();t.point=i[s],t.scale=_.Scale.VISIBLE,t.isHidden=!1;var r=e.getStylesForTransition(t,_.Css.VISIBLE.before);r.transitionDelay=e._getStaggerAmount(n)+"ms",e._queue.push({item:t,styles:r,callback:o}),n+=1})}},{key:"_getNextPositions",value:function(t){var e=this;if(this.options.isCentered){var i=t.map(function(t,i){var n=r.getSize(t.element,!0),s=e._getItemPosition(n);return new v(s.x,s.y,n.width,n.height,i)});return this.getTransformedPositions(i,this.containerWidth)}return t.map(function(t){return e._getItemPosition(r.getSize(t.element,!0))})}},{key:"_getItemPosition",value:function(t){return function(t){for(var e=t.itemSize,i=t.positions,n=t.gridSize,s=t.total,o=t.threshold,r=t.buffer,l=M(e.width,n,s,o),a=A(i,l,s),u=F(a,r),h=new p(n*u,a[u]),f=a[u]+e.height,c=0;c<l;c++)i[u+c]=f;return h}({itemSize:t,positions:this.positions,gridSize:this.colWidth,total:this.cols,threshold:this.options.columnThreshold,buffer:this.options.buffer})}},{key:"getTransformedPositions",value:function(t,e){return x(t,e)}},{key:"_shrink",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this._getConcealedItems(),i=0;e.forEach(function(e){function n(){e.applyCss(_.Css.HIDDEN.after)}if(e.isHidden)return e.applyCss(_.Css.HIDDEN.before),void n();e.scale=_.Scale.HIDDEN,e.isHidden=!0;var s=t.getStylesForTransition(e,_.Css.HIDDEN.before);s.transitionDelay=t._getStaggerAmount(i)+"ms",t._queue.push({item:e,styles:s,callback:n}),i+=1})}},{key:"_handleResize",value:function(){this.isEnabled&&!this.isDestroyed&&this.update()}},{key:"getStylesForTransition",value:function(t,e){var i=Object.assign({},e);if(this.options.useTransforms){var n=this.options.roundTransforms?Math.round(t.point.x):t.point.x,s=this.options.roundTransforms?Math.round(t.point.y):t.point.y;i.transform="translate(".concat(n,"px, ").concat(s,"px) scale(").concat(t.scale,")")}else i.left=t.point.x+"px",i.top=t.point.y+"px";return i}},{key:"_whenTransitionDone",value:function(t,e,i){var n=D(t,function(t){e(),i(null,t)});this._transitions.push(n)}},{key:"_getTransitionFunction",value:function(t){var e=this;return function(i){t.item.applyCss(t.styles),e._whenTransitionDone(t.item.element,t.callback,i)}}},{key:"_processQueue",value:function(){this.isTransitioning&&this._cancelMovement();var t=this.options.speed>0,e=this._queue.length>0;e&&t&&this.isInitialized?this._startTransitions(this._queue):e?(this._styleImmediately(this._queue),this._dispatch(r.EventType.LAYOUT)):this._dispatch(r.EventType.LAYOUT),this._queue.length=0}},{key:"_startTransitions",value:function(t){var e=this;this.isTransitioning=!0,function(t,e,i){i||("function"==typeof e?(i=e,e=null):i=d);var n=t&&t.length;if(!n)return i(null,[]);var s=!1,o=new Array(n);function r(t){return function(e,r){if(!s){if(e)return i(e,o),void(s=!0);o[t]=r,--n||i(null,o)}}}t.forEach(e?function(t,i){t.call(e,r(i))}:function(t,e){t(r(e))})}(t.map(function(t){return e._getTransitionFunction(t)}),this._movementFinished.bind(this))}},{key:"_cancelMovement",value:function(){this._transitions.forEach(L),this._transitions.length=0,this.isTransitioning=!1}},{key:"_styleImmediately",value:function(t){if(t.length){var e=t.map(function(t){return t.item.element});r._skipTransitions(e,function(){t.forEach(function(t){t.item.applyCss(t.styles),t.callback()})})}}},{key:"_movementFinished",value:function(){this._transitions.length=0,this.isTransitioning=!1,this._dispatch(r.EventType.LAYOUT)}},{key:"filter",value:function(t,e){this.isEnabled&&((!t||t&&0===t.length)&&(t=r.ALL_ITEMS),this._filter(t),this._shrink(),this._updateItemCount(),this.sort(e))}},{key:"sort",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.lastSort;if(this.isEnabled){this._resetCols();var e=T(this._getFilteredItems(),t);this._layout(e),this._processQueue(),this._setContainerSize(),this.lastSort=t}}},{key:"update",value:function(){var t=arguments.length>0&&void 0!==arguments[0]&&arguments[0];this.isEnabled&&(t||this._setColumns(),this.sort())}},{key:"layout",value:function(){this.update(!0)}},{key:"add",value:function(t){var e=this,i=O(t).map(function(t){return new _(t)});this._initItems(i),this._resetCols();var n=T(this._mergeNewItems(i),this.lastSort),s=this._filter(this.lastFilter,n),o=function(t){return i.includes(t)},r=function(t){t.scale=_.Scale.HIDDEN,t.isHidden=!0,t.applyCss(_.Css.HIDDEN.before),t.applyCss(_.Css.HIDDEN.after)},l=this._getNextPositions(s.visible);s.visible.forEach(function(t,i){o(t)&&(t.point=l[i],r(t),t.applyCss(e.getStylesForTransition(t,{})))}),s.hidden.forEach(function(t){o(t)&&r(t)}),this.element.offsetWidth,this.setItemTransitions(i),this.items=this._mergeNewItems(i),this.filter(this.lastFilter)}},{key:"disable",value:function(){this.isEnabled=!1}},{key:"enable",value:function(){var t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];this.isEnabled=!0,t&&this.update()}},{key:"remove",value:function(t){var e=this;if(t.length){var i=O(t),n=i.map(function(t){return e.getItemByElement(t)}).filter(function(t){return!!t});this._toggleFilterClasses({visible:[],hidden:n}),this._shrink(n),this.sort(),this.items=this.items.filter(function(t){return!n.includes(t)}),this._updateItemCount(),this.once(r.EventType.LAYOUT,function(){e._disposeItems(n),i.forEach(function(t){t.parentNode.removeChild(t)}),e._dispatch(r.EventType.REMOVED,{collection:i})})}}},{key:"getItemByElement",value:function(t){return this.items.find(function(e){return e.element===t})}},{key:"resetItems",value:function(){var t=this;this._disposeItems(this.items),this.isInitialized=!1,this.items=this._getItems(),this._initItems(this.items),this.once(r.EventType.LAYOUT,function(){t.setItemTransitions(t.items),t.isInitialized=!0}),this.filter(this.lastFilter)}},{key:"destroy",value:function(){this._cancelMovement(),window.removeEventListener("resize",this._onResize),this.element.classList.remove("shuffle"),this.element.removeAttribute("style"),this._disposeItems(this.items),this.items.length=0,this._transitions.length=0,this.options.sizer=null,this.element=null,this.isDestroyed=!0,this.isEnabled=!1}}],[{key:"getSize",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=window.getComputedStyle(t,null),n=b(t,"width",i),s=b(t,"height",i);e&&(n+=b(t,"marginLeft",i)+b(t,"marginRight",i),s+=b(t,"marginTop",i)+b(t,"marginBottom",i));return{width:n,height:s}}},{key:"_skipTransitions",value:function(t,e){var i=t.map(function(t){var e=t.style,i=e.transitionDuration,n=e.transitionDelay;return e.transitionDuration="0ms",e.transitionDelay="0ms",{duration:i,delay:n}});e(),t[0].offsetWidth,t.forEach(function(t,e){t.style.transitionDuration=i[e].duration,t.style.transitionDelay=i[e].delay})}}]),r}();return H.ShuffleItem=_,H.ALL_ITEMS="all",H.FILTER_ATTRIBUTE_KEY="groups",H.EventType={LAYOUT:"shuffle:layout",REMOVED:"shuffle:removed"},H.Classes=y,H.FilterMode={ANY:"any",ALL:"all"},H.options={group:H.ALL_ITEMS,speed:250,easing:"cubic-bezier(0.4, 0.0, 0.2, 1)",itemSelector:"*",sizer:null,gutterWidth:0,columnWidth:0,delimiter:null,buffer:0,columnThreshold:.01,initialSort:null,throttle:c,throttleTime:300,staggerAmount:15,staggerAmountMax:150,useTransforms:!0,filterMode:H.FilterMode.ANY,isCentered:!1,roundTransforms:!0},H.Point=p,H.Rect=v,H.__sorter=T,H.__getColumnSpan=M,H.__getAvailablePositions=A,H.__getShortColumn=F,H.__getCenteredPositions=x,H});
2
- //# sourceMappingURL=shuffle.min.js.map
 
 
resources/js/whitelabel.js DELETED
@@ -1,8 +0,0 @@
1
- var iCWP_WPSF_WhiteLabel = new function () {
2
- this.initialise = function () {
3
- jQuery( document ).ready( function () {
4
- jQuery( 'select#plugin option[value="%s"]' ).remove();
5
- } );
6
- };
7
- }();
8
- iCWP_WPSF_WhiteLabel.initialise();
 
 
 
 
 
 
 
 
resources/js/wizard.js DELETED
File without changes
src/config/feature-audit_trail.php CHANGED
@@ -19,8 +19,7 @@
19
  "menu_items": [
20
  {
21
  "title": "Audit Trail",
22
- "slug": "audit-redirect",
23
- "callback": ""
24
  }
25
  ],
26
  "custom_redirects": [
@@ -164,7 +163,22 @@
164
  ],
165
  "definitions": {
166
  "db_classes": {
167
- "audit": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\AuditTrail\\Handler"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  },
169
  "audit_trail_free_max_entries": 100,
170
  "audit_trail_table_name": "audit_trail",
@@ -175,9 +189,7 @@
175
  "context": "varchar(32) NOT NULL DEFAULT 'none' COMMENT 'Audit Context'",
176
  "event": "varchar(50) NOT NULL DEFAULT 'none' COMMENT 'Specific Audit Event'",
177
  "category": "int(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Severity'",
178
- "message": "text COMMENT 'Audit Event Description'",
179
  "meta": "text COMMENT 'Audit Event Data'",
180
- "immutable": "tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'May Be Deleted'",
181
  "count": "SMALLINT(5) UNSIGNED NOT NULL DEFAULT 1 COMMENT 'Repeat Count'"
182
  },
183
  "audittrail_table_timestamp_columns": {
19
  "menu_items": [
20
  {
21
  "title": "Audit Trail",
22
+ "slug": "audit-redirect"
 
23
  }
24
  ],
25
  "custom_redirects": [
163
  ],
164
  "definitions": {
165
  "db_classes": {
166
+ "audit_trail": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\AuditTrail\\Handler",
167
+ "audit": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\AuditTrail\\Handler"
168
+ },
169
+ "db_table_audit_trail": {
170
+ "slug": "audit_trail",
171
+ "has_updated_at": true,
172
+ "cols_custom": {
173
+ "rid": "varchar(10) NOT NULL DEFAULT '' COMMENT 'Request ID'",
174
+ "ip": "varchar(40) NOT NULL DEFAULT 0 COMMENT 'Visitor IP Address'",
175
+ "wp_username": "varchar(255) NOT NULL DEFAULT '-' COMMENT 'WP User'",
176
+ "context": "varchar(32) NOT NULL DEFAULT 'none' COMMENT 'Audit Context'",
177
+ "event": "varchar(50) NOT NULL DEFAULT 'none' COMMENT 'Specific Audit Event'",
178
+ "category": "int(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Severity'",
179
+ "meta": "text COMMENT 'Audit Event Data'",
180
+ "count": "SMALLINT(5) UNSIGNED NOT NULL DEFAULT 1 COMMENT 'Repeat Count'"
181
+ }
182
  },
183
  "audit_trail_free_max_entries": 100,
184
  "audit_trail_table_name": "audit_trail",
189
  "context": "varchar(32) NOT NULL DEFAULT 'none' COMMENT 'Audit Context'",
190
  "event": "varchar(50) NOT NULL DEFAULT 'none' COMMENT 'Specific Audit Event'",
191
  "category": "int(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Severity'",
 
192
  "meta": "text COMMENT 'Audit Event Data'",
 
193
  "count": "SMALLINT(5) UNSIGNED NOT NULL DEFAULT 1 COMMENT 'Repeat Count'"
194
  },
195
  "audittrail_table_timestamp_columns": {
src/config/feature-comments_filter.php CHANGED
@@ -119,6 +119,46 @@
119
  "summary": "Don't Scan Comments For Users With The Following Roles",
120
  "description": "Shield doesn't normally scan comments from logged-in or registered users. Specify user roles here that shouldn't be scanned."
121
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  {
123
  "key": "google_recaptcha_style_comments",
124
  "section": "section_bot_comment_spam_protection_filter",
@@ -163,35 +203,6 @@
163
  "summary": "Block Bot Comment SPAM",
164
  "description": "Taking the lead from the original GASP plugin for WordPress, we have extended it to include advanced spam-bot protection."
165
  },
166
- {
167
- "key": "comments_default_action_spam_bot",
168
- "section": "section_bot_comment_spam_protection_filter",
169
- "default": "spam",
170
- "type": "select",
171
- "value_options": [
172
- {
173
- "value_key": "0",
174
- "text": "Move To Pending Moderation"
175
- },
176
- {
177
- "value_key": "spam",
178
- "text": "Move To SPAM"
179
- },
180
- {
181
- "value_key": "trash",
182
- "text": "Move To Trash"
183
- },
184
- {
185
- "value_key": "reject",
186
- "text": "Block And Redirect"
187
- }
188
- ],
189
- "link_info": "https://shsec.io/6j",
190
- "link_blog": "",
191
- "name": "SPAM Action",
192
- "summary": "How To Categorise Comments When Identified To Be SPAM",
193
- "description": "When a comment is detected as being SPAM from an automatic bot, the comment will be categorised based on this setting."
194
- },
195
  {
196
  "key": "enable_comments_human_spam_filter",
197
  "section": "section_human_spam_filter",
@@ -303,6 +314,10 @@
303
  "comments_expire": 1800,
304
  "url_spam_blacklist_terms": "https://raw.githubusercontent.com/splorp/wordpress-comment-blacklist/master/blacklist.txt",
305
  "events": {
 
 
 
 
306
  "spam_block_bot": {
307
  "recent": true,
308
  "offense": true
119
  "summary": "Don't Scan Comments For Users With The Following Roles",
120
  "description": "Shield doesn't normally scan comments from logged-in or registered users. Specify user roles here that shouldn't be scanned."
121
  },
122
+ {
123
+ "key": "enable_antibot_check",
124
+ "section": "section_bot_comment_spam_protection_filter",
125
+ "default": "N",
126
+ "type": "checkbox",
127
+ "link_info": "https://shsec.io/jn",
128
+ "link_blog": "https://shsec.io/jo",
129
+ "name": "AntiBot Detection Engine",
130
+ "summary": "Use Experimental AntiBot Detection Engine",
131
+ "description": "Use Shield's AntiBot Detection Engine In-Place of GASP Bot checking."
132
+ },
133
+ {
134
+ "key": "comments_default_action_spam_bot",
135
+ "section": "section_bot_comment_spam_protection_filter",
136
+ "default": "spam",
137
+ "type": "select",
138
+ "value_options": [
139
+ {
140
+ "value_key": "0",
141
+ "text": "Move To Pending Moderation"
142
+ },
143
+ {
144
+ "value_key": "spam",
145
+ "text": "Move To SPAM"
146
+ },
147
+ {
148
+ "value_key": "trash",
149
+ "text": "Move To Trash"
150
+ },
151
+ {
152
+ "value_key": "reject",
153
+ "text": "Block And Redirect"
154
+ }
155
+ ],
156
+ "link_info": "https://shsec.io/6j",
157
+ "link_blog": "",
158
+ "name": "SPAM Action",
159
+ "summary": "How To Categorise Comments When Identified To Be SPAM",
160
+ "description": "When a comment is detected as being SPAM from an automatic bot, the comment will be categorised based on this setting."
161
+ },
162
  {
163
  "key": "google_recaptcha_style_comments",
164
  "section": "section_bot_comment_spam_protection_filter",
203
  "summary": "Block Bot Comment SPAM",
204
  "description": "Taking the lead from the original GASP plugin for WordPress, we have extended it to include advanced spam-bot protection."
205
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  {
207
  "key": "enable_comments_human_spam_filter",
208
  "section": "section_human_spam_filter",
314
  "comments_expire": 1800,
315
  "url_spam_blacklist_terms": "https://raw.githubusercontent.com/splorp/wordpress-comment-blacklist/master/blacklist.txt",
316
  "events": {
317
+ "spam_block_antibot": {
318
+ "recent": true,
319
+ "offense": true
320
+ },
321
  "spam_block_bot": {
322
  "recent": true,
323
  "offense": true
src/config/feature-events.php CHANGED
@@ -49,6 +49,13 @@
49
  "db_classes": {
50
  "events": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Events\\Handler"
51
  },
 
 
 
 
 
 
 
52
  "events_table_name": "events",
53
  "events_table_columns": {
54
  "event": "varchar(50) NOT NULL DEFAULT 'none' COMMENT 'Event ID'",
49
  "db_classes": {
50
  "events": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Events\\Handler"
51
  },
52
+ "db_table_events": {
53
+ "slug": "events",
54
+ "cols_custom": {
55
+ "event": "varchar(50) NOT NULL DEFAULT 'none' COMMENT 'Event ID'",
56
+ "count": "int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Total'"
57
+ }
58
+ },
59
  "events_table_name": "events",
60
  "events_table_columns": {
61
  "event": "varchar(50) NOT NULL DEFAULT 'none' COMMENT 'Event ID'",
src/config/feature-firewall.php CHANGED
@@ -318,6 +318,8 @@
318
  "icwp_wpsf_new_u2f_response",
319
  "icwp_wpsf_u2f_otp",
320
  "appId",
 
 
321
  "aioseo-post-settings"
322
  ]
323
  },
318
  "icwp_wpsf_new_u2f_response",
319
  "icwp_wpsf_u2f_otp",
320
  "appId",
321
+ "/^et_.*/",
322
+ "ping_sites",
323
  "aioseo-post-settings"
324
  ]
325
  },
src/config/feature-hack_protect.php CHANGED
@@ -22,8 +22,7 @@
22
  "menu_items": [
23
  {
24
  "title": "Scans",
25
- "slug": "scans-redirect",
26
- "callback": ""
27
  }
28
  ],
29
  "custom_redirects": [
@@ -396,14 +395,65 @@
396
  "tracking_exclude": true,
397
  "type": "array",
398
  "default": []
 
 
 
 
 
 
 
 
399
  }
400
  ],
401
  "definitions": {
402
  "db_classes": {
403
  "file_protect": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\FileLocker\\Handler",
404
- "scanresults": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Scanner\\Handler",
 
405
  "scanq": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\ScanQueue\\Handler"
406
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
  "all_scan_slugs": [
408
  "apc",
409
  "mal",
@@ -424,28 +474,6 @@
424
  "notified_at": "int(15) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'TS Notification Sent'",
425
  "updated_at": "int(15) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'TS Updated'"
426
  },
427
- "table_name_scanner": "scanner",
428
- "table_columns_scanner": {
429
- "hash": "varchar(32) NOT NULL DEFAULT '' COMMENT 'Unique Item Hash'",
430
- "meta": "text COMMENT 'Relevant Item Data'",
431
- "scan": "varchar(10) NOT NULL DEFAULT 0 COMMENT 'Scan Type'",
432
- "severity": "int(3) NOT NULL DEFAULT 1 COMMENT 'Severity'"
433
- },
434
- "scanresults_table_timestamp_columns": {
435
- "ignored_at": "Scan Result Ignored",
436
- "notified_at": "Scan Notifiation Sent"
437
- },
438
- "table_name_scanqueue": "scanq",
439
- "table_columns_scanqueue": {
440
- "scan": "varchar(3) NOT NULL DEFAULT 0 COMMENT 'Scan Slug'",
441
- "items": "text COMMENT 'Array of scan items'",
442
- "results": "text COMMENT 'Array of results'",
443
- "meta": "text COMMENT 'Meta Data'"
444
- },
445
- "scanqueue_table_timestamp_columns": {
446
- "started_at": "Scan Started",
447
- "finished_at": "Scan Completed"
448
- },
449
  "url_mal_sigs_simple": "https://raw.githubusercontent.com/scr34m/php-malware-scanner/master/definitions/patterns_raw.txt",
450
  "url_mal_sigs_regex": "https://raw.githubusercontent.com/scr34m/php-malware-scanner/master/definitions/patterns_re.txt",
451
  "malware_whitelist_paths": [
22
  "menu_items": [
23
  {
24
  "title": "Scans",
25
+ "slug": "scans-redirect"
 
26
  }
27
  ],
28
  "custom_redirects": [
395
  "tracking_exclude": true,
396
  "type": "array",
397
  "default": []
398
+ },
399
+ {
400
+ "key": "filelocker_state",
401
+ "section": "section_non_ui",
402
+ "transferable": false,
403
+ "tracking_exclude": true,
404
+ "type": "array",
405
+ "default": []
406
  }
407
  ],
408
  "definitions": {
409
  "db_classes": {
410
  "file_protect": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\FileLocker\\Handler",
411
+ "filelocker": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\FileLocker\\Handler",
412
+ "scanner": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Scanner\\Handler",
413
  "scanq": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\ScanQueue\\Handler"
414
  },
415
+ "db_table_filelocker": {
416
+ "slug": "filelocker",
417
+ "has_updated_at": true,
418
+ "cols_custom": {
419
+ "file": "varchar(256) NOT NULL COMMENT 'File Path relative to ABSPATH'",
420
+ "hash_original": "varchar(40) NOT NULL COMMENT 'SHA1 File Hash Original'",
421
+ "hash_current": "varchar(40) NOT NULL COMMENT 'SHA1 File Hash Current'",
422
+ "content": "MEDIUMBLOB COMMENT 'Content'",
423
+ "public_key_id": "TINYINT(2) UNSIGNED NOT NULL COMMENT 'Public Key ID'"
424
+ },
425
+ "cols_timestamps": {
426
+ "detected_at": "Change Last Detected",
427
+ "reverted_at": "Reverted To Backup",
428
+ "notified_at": "Notification Sent"
429
+ }
430
+ },
431
+ "db_table_scanner": {
432
+ "slug": "scanner",
433
+ "cols_custom": {
434
+ "hash": "varchar(32) NOT NULL DEFAULT '' COMMENT 'Unique Item Hash'",
435
+ "meta": "text COMMENT 'Relevant Item Data'",
436
+ "scan": "varchar(10) NOT NULL DEFAULT 0 COMMENT 'Scan Type'",
437
+ "severity": "int(3) NOT NULL DEFAULT 1 COMMENT 'Severity'"
438
+ },
439
+ "cols_timestamps": {
440
+ "ignored_at": "Scan Result Ignored",
441
+ "notified_at": "Scan Notifiation Sent"
442
+ }
443
+ },
444
+ "db_table_scanq": {
445
+ "slug": "scanq",
446
+ "cols_custom": {
447
+ "scan": "varchar(3) NOT NULL DEFAULT '' COMMENT 'Scan Slug'",
448
+ "items": "text COMMENT 'Array of scan items'",
449
+ "results": "text COMMENT 'Array of results'",
450
+ "meta": "text COMMENT 'Meta Data'"
451
+ },
452
+ "cols_timestamps": {
453
+ "started_at": "Scan Started",
454
+ "finished_at": "Scan Completed"
455
+ }
456
+ },
457
  "all_scan_slugs": [
458
  "apc",
459
  "mal",
474
  "notified_at": "int(15) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'TS Notification Sent'",
475
  "updated_at": "int(15) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'TS Updated'"
476
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  "url_mal_sigs_simple": "https://raw.githubusercontent.com/scr34m/php-malware-scanner/master/definitions/patterns_raw.txt",
478
  "url_mal_sigs_regex": "https://raw.githubusercontent.com/scr34m/php-malware-scanner/master/definitions/patterns_re.txt",
479
  "malware_whitelist_paths": [
src/config/feature-integrations.php CHANGED
@@ -27,6 +27,11 @@
27
  "title": "Integrations",
28
  "title_short": "Integrations"
29
  },
 
 
 
 
 
30
  {
31
  "slug": "section_non_ui",
32
  "hidden": true
@@ -43,10 +48,87 @@
43
  "name": "Enable MainWP",
44
  "summary": "Enable The Built-In MainWP Extension",
45
  "description": "This option will enable Shield's built-in MainWP extension for both server and client."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  }
47
  ],
48
  "definitions": {
49
  "events": {
 
 
 
 
 
 
 
 
 
 
50
  }
51
  }
52
  }
27
  "title": "Integrations",
28
  "title_short": "Integrations"
29
  },
30
+ {
31
+ "slug": "section_spam",
32
+ "title": "SPAM Detection",
33
+ "title_short": "SPAM Detection"
34
+ },
35
  {
36
  "slug": "section_non_ui",
37
  "hidden": true
48
  "name": "Enable MainWP",
49
  "summary": "Enable The Built-In MainWP Extension",
50
  "description": "This option will enable Shield's built-in MainWP extension for both server and client."
51
+ },
52
+ {
53
+ "key": "enable_spam_antibot",
54
+ "section": "section_spam",
55
+ "premium": true,
56
+ "default": "N",
57
+ "type": "checkbox",
58
+ "link_info": "",
59
+ "link_blog": "",
60
+ "name": "AntiBot SPAM Detection",
61
+ "summary": "Enable The AntiBot SPAM Detection",
62
+ "description": "Use Shield's built-in AntiBot Detection Engine to identify contact form SPAM."
63
+ },
64
+ {
65
+ "key": "form_spam_providers",
66
+ "section": "section_spam",
67
+ "premium": true,
68
+ "advanced": true,
69
+ "type": "multiple_select",
70
+ "default": [],
71
+ "value_options": [
72
+ {
73
+ "value_key": "contactform7",
74
+ "text": "Contact Form 7"
75
+ },
76
+ {
77
+ "value_key": "elementorpro",
78
+ "text": "Elementor Pro"
79
+ },
80
+ {
81
+ "value_key": "fluentforms",
82
+ "text": "Fluent Forms"
83
+ },
84
+ {
85
+ "value_key": "formidableforms",
86
+ "text": "Formidable Forms"
87
+ },
88
+ {
89
+ "value_key": "forminator",
90
+ "text": "Forminator"
91
+ },
92
+ {
93
+ "value_key": "gravityforms",
94
+ "text": "Gravity Forms"
95
+ },
96
+ {
97
+ "value_key": "kaliforms",
98
+ "text": "Kali Forms"
99
+ },
100
+ {
101
+ "value_key": "ninjaforms",
102
+ "text": "Ninja Forms"
103
+ },
104
+ {
105
+ "value_key": "wpforo",
106
+ "text": "wpForo"
107
+ },
108
+ {
109
+ "value_key": "wpforms",
110
+ "text": "WPForms"
111
+ }
112
+ ],
113
+ "link_info": "",
114
+ "link_blog": "",
115
+ "name": "SPAM Form Checking",
116
+ "summary": "Select The Form Providers That Should Be Checked For SPAM",
117
+ "description": "Select The Form Providers That Should Be Checked For SPAM."
118
  }
119
  ],
120
  "definitions": {
121
  "events": {
122
+ "spam_form_pass": {
123
+ "stat": true,
124
+ "audit": true,
125
+ "offense": false
126
+ },
127
+ "spam_form_fail": {
128
+ "stat": true,
129
+ "audit": true,
130
+ "offense": false
131
+ }
132
  }
133
  }
134
  }
src/config/feature-ips.php CHANGED
@@ -1,6 +1,6 @@
1
  {
2
- "slug": "ips",
3
- "properties": {
4
  "slug": "ips",
5
  "name": "Block Bad IPs/Visitors",
6
  "sidebar_name": "IP Blocking",
@@ -19,8 +19,7 @@
19
  "menu_items": [
20
  {
21
  "title": "IP Lists",
22
- "slug": "ips-redirect",
23
- "callback": ""
24
  }
25
  ],
26
  "custom_redirects": [
@@ -32,7 +31,7 @@
32
  }
33
  }
34
  ],
35
- "admin_notices": {
36
  "visitor-whitelisted": {
37
  "id": "visitor-whitelisted",
38
  "schedule": "conditions",
@@ -41,7 +40,7 @@
41
  "type": "info"
42
  }
43
  },
44
- "requirements": {
45
  "php": {
46
  "functions": [
47
  "filter_var"
@@ -55,7 +54,7 @@
55
  ]
56
  }
57
  },
58
- "sections": [
59
  {
60
  "slug": "section_auto_black_list",
61
  "primary": true,
@@ -66,6 +65,13 @@
66
  "Recommendation - Keep the Automatic IP Black List feature turned on."
67
  ]
68
  },
 
 
 
 
 
 
 
69
  {
70
  "slug": "section_logins",
71
  "title": "Capture Login Bots",
@@ -123,7 +129,7 @@
123
  "hidden": true
124
  }
125
  ],
126
- "options": [
127
  {
128
  "key": "enable_ips",
129
  "section": "section_enable_plugin_feature_ips",
@@ -136,6 +142,19 @@
136
  "summary": "Enable (or Disable) The IP Manager module",
137
  "description": "Un-Checking this option will completely disable the IP Manager module"
138
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  {
140
  "key": "transgression_limit",
141
  "section": "section_auto_black_list",
@@ -332,6 +351,40 @@
332
  "summary": "Identify A Bot When It Accesses XML-RPC",
333
  "description": "If you don't use XML-RPC, why would anyone access it?"
334
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  {
336
  "key": "track_loginfailed",
337
  "section": "section_logins",
@@ -495,9 +548,11 @@
495
  "default": []
496
  }
497
  ],
498
- "definitions": {
499
  "db_classes": {
500
- "ips": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\IPs\\Handler"
 
 
501
  },
502
  "ip_lists_table_name": "ip_lists",
503
  "ip_list_table_columns": {
@@ -512,6 +567,55 @@
512
  "last_access_at": "Last Access By IP",
513
  "blocked_at": "IP Blocked"
514
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
515
  "events": {
516
  "custom_offense": {
517
  "cat": 3,
@@ -526,9 +630,25 @@
526
  "ip_blocked": {
527
  "cat": 2
528
  },
 
 
 
 
 
 
 
 
 
 
529
  "ip_unblock_flag": {
530
  "cat": 1
531
  },
 
 
 
 
 
 
532
  "bottrack_404": {
533
  "cat": 2,
534
  "offense": true
@@ -556,6 +676,19 @@
556
  "bottrack_xmlrpc": {
557
  "cat": 2,
558
  "offense": true
 
 
 
 
 
 
 
 
 
 
 
 
 
559
  }
560
  }
561
  }
1
  {
2
+ "slug": "ips",
3
+ "properties": {
4
  "slug": "ips",
5
  "name": "Block Bad IPs/Visitors",
6
  "sidebar_name": "IP Blocking",
19
  "menu_items": [
20
  {
21
  "title": "IP Lists",
22
+ "slug": "ips-redirect"
 
23
  }
24
  ],
25
  "custom_redirects": [
31
  }
32
  }
33
  ],
34
+ "admin_notices": {
35
  "visitor-whitelisted": {
36
  "id": "visitor-whitelisted",
37
  "schedule": "conditions",
40
  "type": "info"
41
  }
42
  },
43
+ "requirements": {
44
  "php": {
45
  "functions": [
46
  "filter_var"
54
  ]
55
  }
56
  },
57
+ "sections": [
58
  {
59
  "slug": "section_auto_black_list",
60
  "primary": true,
65
  "Recommendation - Keep the Automatic IP Black List feature turned on."
66
  ]
67
  },
68
+ {
69
+ "slug": "section_antibot",
70
+ "title": "AntiBot System",
71
+ "title_short": "AntiBot System",
72
+ "summary": [
73
+ ]
74
+ },
75
  {
76
  "slug": "section_logins",
77
  "title": "Capture Login Bots",
129
  "hidden": true
130
  }
131
  ],
132
+ "options": [
133
  {
134
  "key": "enable_ips",
135
  "section": "section_enable_plugin_feature_ips",
142
  "summary": "Enable (or Disable) The IP Manager module",
143
  "description": "Un-Checking this option will completely disable the IP Manager module"
144
  },
145
+ {
146
+ "key": "antibot_minimum",
147
+ "section": "section_antibot",
148
+ "default": 35,
149
+ "type": "integer",
150
+ "min": 1,
151
+ "max": 99,
152
+ "link_info": "",
153
+ "link_blog": "",
154
+ "name": "AntiBot Threshold",
155
+ "summary": "AntiBot Testing Threshold (Percentage)",
156
+ "description": "When using Shield's AntiBot system, this is the threshold used for testing (between 1 and 99)."
157
+ },
158
  {
159
  "key": "transgression_limit",
160
  "section": "section_auto_black_list",
351
  "summary": "Identify A Bot When It Accesses XML-RPC",
352
  "description": "If you don't use XML-RPC, why would anyone access it?"
353
  },
354
+ {
355
+ "key": "track_invalidscript",
356
+ "section": "section_probes",
357
+ "premium": true,
358
+ "default": "log",
359
+ "type": "select",
360
+ "value_options": [
361
+ {
362
+ "value_key": "disabled",
363
+ "text": "Disabled"
364
+ },
365
+ {
366
+ "value_key": "log",
367
+ "text": "Audit Log Only"
368
+ },
369
+ {
370
+ "value_key": "transgression-single",
371
+ "text": "Increment Offense Counter"
372
+ },
373
+ {
374
+ "value_key": "transgression-double",
375
+ "text": "Double-Increment Offense Counter"
376
+ },
377
+ {
378
+ "value_key": "block",
379
+ "text": "Immediate Block"
380
+ }
381
+ ],
382
+ "link_info": "https://shsec.io/fo",
383
+ "link_blog": "https://shsec.io/f7",
384
+ "name": "Invalid Script Load",
385
+ "summary": "Identify A Bot Attempts To Load WordPress In A Non-Standard Way",
386
+ "description": "WordPress should only be loaded in a limited number of ways."
387
+ },
388
  {
389
  "key": "track_loginfailed",
390
  "section": "section_logins",
548
  "default": []
549
  }
550
  ],
551
+ "definitions": {
552
  "db_classes": {
553
+ "botsignals": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\BotSignals\\Handler",
554
+ "ip_lists": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\IPs\\Handler",
555
+ "ips": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\IPs\\Handler"
556
  },
557
  "ip_lists_table_name": "ip_lists",
558
  "ip_list_table_columns": {
567
  "last_access_at": "Last Access By IP",
568
  "blocked_at": "IP Blocked"
569
  },
570
+ "db_table_ip_lists": {
571
+ "slug": "ip_lists",
572
+ "cols_custom": {
573
+ "ip": "varchar(60) NOT NULL DEFAULT '' COMMENT 'Human readable IP address or range'",
574
+ "label": "varchar(255) NOT NULL DEFAULT '' COMMENT 'Description'",
575
+ "list": "varchar(4) NOT NULL DEFAULT '' COMMENT 'Block or Bypass'",
576
+ "ip6": "tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Is IPv6'",
577
+ "is_range": "tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Is Range'",
578
+ "transgressions": "int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Total Offenses'"
579
+ },
580
+ "cols_timestamps": {
581
+ "last_access_at": "Last Access By IP",
582
+ "blocked_at": "IP Blocked"
583
+ }
584
+ },
585
+ "db_table_botsignals": {
586
+ "autoexpire": 3,
587
+ "slug": "botsignals",
588
+ "col_older_than": "updated_at",
589
+ "has_updated_at": true,
590
+ "cols_custom": {
591
+ "ip": "varbinary(16) DEFAULT NULL COMMENT 'IP Address'"
592
+ },
593
+ "cols_timestamps": {
594
+ "notbot_at": "NotBot",
595
+ "frontpage_at": "Front Page Loaded",
596
+ "bt404_at": "BotTrack 404",
597
+ "btfake_at": "BotTrack FakeWebCrawler",
598
+ "btcheese_at": "BotTrack LinkCheese",
599
+ "btloginfail_at": "BotTrack LoginFailed",
600
+ "btua_at": "BotTrack Useragent Fail",
601
+ "btxml_at": "BotTrack XMLRPC Access",
602
+ "btlogininvalid_at": "BotTrack LoginInvalid",
603
+ "btinvalidscript_at": "BotTrack InvalidScript",
604
+ "cooldown_at": "Triggered Cooldown",
605
+ "humanspam_at": "Comment Marked As Human SPAM",
606
+ "markspam_at": "Mark Comment As SPAM",
607
+ "unmarkspam_at": "Unmark Comment As SPAM",
608
+ "captchapass_at": "Captcha Passed",
609
+ "captchafail_at": "Captcha Failed",
610
+ "auth_at": "Successful Login",
611
+ "firewall_at": "Triggered Firewall",
612
+ "ratelimit_at": "Rate Limit Exceeded",
613
+ "offense_at": "Last Offense",
614
+ "blocked_at": "Last Block",
615
+ "unblocked_at": "Unblocked",
616
+ "bypass_at": "Bypass"
617
+ }
618
+ },
619
  "events": {
620
  "custom_offense": {
621
  "cat": 3,
630
  "ip_blocked": {
631
  "cat": 2
632
  },
633
+ "ip_unblock": {
634
+ "offense": false,
635
+ "audit": false,
636
+ "stat": false
637
+ },
638
+ "ip_bypass": {
639
+ "offense": false,
640
+ "audit": false,
641
+ "stat": false
642
+ },
643
  "ip_unblock_flag": {
644
  "cat": 1
645
  },
646
+ "bottrack_notbot": {
647
+ "cat": 0,
648
+ "offense": false,
649
+ "audit": false,
650
+ "stat": false
651
+ },
652
  "bottrack_404": {
653
  "cat": 2,
654
  "offense": true
676
  "bottrack_xmlrpc": {
677
  "cat": 2,
678
  "offense": true
679
+ },
680
+ "bottrack_invalidscript": {
681
+ "cat": 2,
682
+ "offense": true
683
+ },
684
+ "comment_markspam": {
685
+ "cat": 2,
686
+ "offense": true
687
+ },
688
+ "comment_unmarkspam": {
689
+ "audit": false,
690
+ "offense": false,
691
+ "stat": false
692
  }
693
  }
694
  }
src/config/feature-license.php CHANGED
@@ -1,10 +1,10 @@
1
  {
2
- "slug": "license",
3
- "properties": {
4
  "slug": "license",
5
  "name": "Pro Security",
6
- "menu_title": "Go Pro!",
7
- "show_module_menu_item": true,
8
  "highlight_menu_item": true,
9
  "tagline": "The Best In WordPress Security, Only Better.",
10
  "auto_enabled": true,
@@ -16,7 +16,7 @@
16
  "run_if_verified_bot": true,
17
  "run_if_wpcli": true
18
  },
19
- "admin_notices": {
20
  "wphashes-token-fail": {
21
  "id": "wphashes-token-fail",
22
  "schedule": "conditions",
@@ -26,13 +26,29 @@
26
  "type": "error"
27
  }
28
  },
29
- "sections": [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  {
31
  "slug": "section_non_ui",
32
  "hidden": true
33
  }
34
  ],
35
- "options": [
36
  {
37
  "key": "license_key",
38
  "section": "section_non_ui",
@@ -123,7 +139,7 @@
123
  "default": []
124
  }
125
  ],
126
- "definitions": {
127
  "license_store_url_api": "https://api.getshieldsecurity.com/wp-json/odp-eddkeyless/v1",
128
  "keyless_cp": "https://shsec.io/c5",
129
  "license_item_name": "Shield Security Pro",
1
  {
2
+ "slug": "license",
3
+ "properties": {
4
  "slug": "license",
5
  "name": "Pro Security",
6
+ "menu_title": "",
7
+ "show_module_menu_item": false,
8
  "highlight_menu_item": true,
9
  "tagline": "The Best In WordPress Security, Only Better.",
10
  "auto_enabled": true,
16
  "run_if_verified_bot": true,
17
  "run_if_wpcli": true
18
  },
19
+ "admin_notices": {
20
  "wphashes-token-fail": {
21
  "id": "wphashes-token-fail",
22
  "schedule": "conditions",
26
  "type": "error"
27
  }
28
  },
29
+ "menu_items": [
30
+ {
31
+ "title": "Go PRO!",
32
+ "slug": "pro-redirect",
33
+ "highlight": true
34
+ }
35
+ ],
36
+ "custom_redirects": [
37
+ {
38
+ "source_mod_page": "pro-redirect",
39
+ "target_mod_page": "insights",
40
+ "query_args": {
41
+ "inav": "license"
42
+ }
43
+ }
44
+ ],
45
+ "sections": [
46
  {
47
  "slug": "section_non_ui",
48
  "hidden": true
49
  }
50
  ],
51
+ "options": [
52
  {
53
  "key": "license_key",
54
  "section": "section_non_ui",
139
  "default": []
140
  }
141
  ],
142
+ "definitions": {
143
  "license_store_url_api": "https://api.getshieldsecurity.com/wp-json/odp-eddkeyless/v1",
144
  "keyless_cp": "https://shsec.io/c5",
145
  "license_item_name": "Shield Security Pro",
src/config/feature-login_protect.php CHANGED
@@ -308,6 +308,17 @@
308
  "summary": "Limit login attempts to every X seconds",
309
  "description": "WordPress will process only ONE login attempt for every number of seconds specified. Zero (0) turns this off."
310
  },
 
 
 
 
 
 
 
 
 
 
 
311
  {
312
  "key": "enable_login_gasp_check",
313
  "section": "section_brute_force_login_protection",
@@ -490,15 +501,18 @@
490
  "2fa_email_send_fail": {
491
  },
492
  "cooldown_fail": {
 
493
  },
494
  "honeypot_fail": {
 
495
  },
496
  "botbox_fail": {
 
497
  },
498
  "login_block": {
499
  "audit": false,
500
  "recent": true,
501
- "offense": true
502
  },
503
  "hide_login_url": {
504
  "audit": false
308
  "summary": "Limit login attempts to every X seconds",
309
  "description": "WordPress will process only ONE login attempt for every number of seconds specified. Zero (0) turns this off."
310
  },
311
+ {
312
+ "key": "enable_antibot_check",
313
+ "section": "section_brute_force_login_protection",
314
+ "default": "N",
315
+ "type": "checkbox",
316
+ "link_info": "https://shsec.io/jn",
317
+ "link_blog": "https://shsec.io/jo",
318
+ "name": "AntiBot",
319
+ "summary": "Use Experimental AntiBot Detection Engine",
320
+ "description": "Use Shield's AntiBot Detection Engine In-Place of GASP/CAPTCHA Bot checking."
321
+ },
322
  {
323
  "key": "enable_login_gasp_check",
324
  "section": "section_brute_force_login_protection",
501
  "2fa_email_send_fail": {
502
  },
503
  "cooldown_fail": {
504
+ "offense": true
505
  },
506
  "honeypot_fail": {
507
+ "offense": true
508
  },
509
  "botbox_fail": {
510
+ "offense": true
511
  },
512
  "login_block": {
513
  "audit": false,
514
  "recent": true,
515
+ "offense": false
516
  },
517
  "hide_login_url": {
518
  "audit": false
src/config/feature-plugin.php CHANGED
@@ -529,29 +529,33 @@
529
  }
530
  ],
531
  "definitions": {
532
- "survey_email": "c3VwcG9ydEBvbmVkb2xsYXJwbHVnaW4uY29t",
533
- "help_video_id": "",
534
- "tracking_cron_handle": "plugin_tracking_cron",
535
- "tracking_post_url": "https://tracking.icontrolwp.com/track/plugin/shield",
536
- "importexport_cron_name": "autoimport",
537
- "href_privacy_policy": "https://shsec.io/wpshieldprivacypolicy",
538
- "db_classes": {
539
  "geoip": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\GeoIp\\Handler",
540
  "notes": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\AdminNotes\\Handler"
541
  },
542
- "db_autoexpire_notes": 0,
543
- "db_autoexpire_geoip": 30,
544
- "db_notes_name": "notes",
545
- "db_notes_table_columns": {
546
- "wp_username": "varchar(255) NOT NULL DEFAULT 'unknown'",
547
- "note": "TEXT"
 
548
  },
549
- "geoip_table_name": "geoip",
550
- "geoip_table_columns": {
551
- "ip": "varbinary(16) DEFAULT NULL COMMENT 'IP Address'",
552
- "meta": "TEXT"
 
 
 
553
  },
554
- "active_plugin_features": [
555
  {
556
  "slug": "insights",
557
  "load_priority": 1,
@@ -632,7 +636,7 @@
632
  "slug": "email"
633
  }
634
  ],
635
- "events": {
636
  "test_cron_run": {
637
  "audit": false,
638
  "recent": true
@@ -664,10 +668,24 @@
664
  "audit": false
665
  },
666
  "recaptcha_fail": {
 
 
 
 
 
667
  "audit": true
 
 
 
 
 
 
 
 
 
668
  }
669
  },
670
- "wizards": {
671
  "welcome": {
672
  "title": "Getting Started Setup Wizard",
673
  "desc": "An introduction to this security plugin, helping you get setup and started quickly with the core features.",
529
  }
530
  ],
531
  "definitions": {
532
+ "survey_email": "c3VwcG9ydEBvbmVkb2xsYXJwbHVnaW4uY29t",
533
+ "help_video_id": "",
534
+ "tracking_cron_handle": "plugin_tracking_cron",
535
+ "tracking_post_url": "https://tracking.icontrolwp.com/track/plugin/shield",
536
+ "importexport_cron_name": "autoimport",
537
+ "href_privacy_policy": "https://shsec.io/wpshieldprivacypolicy",
538
+ "db_classes": {
539
  "geoip": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\GeoIp\\Handler",
540
  "notes": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\AdminNotes\\Handler"
541
  },
542
+ "db_table_notes": {
543
+ "slug": "notes",
544
+ "has_updated_at": true,
545
+ "cols_custom": {
546
+ "wp_username": "varchar(255) NOT NULL DEFAULT 'unknown'",
547
+ "note": "TEXT"
548
+ }
549
  },
550
+ "db_table_geoip": {
551
+ "autoexpire": 30,
552
+ "slug": "geoip",
553
+ "cols_custom": {
554
+ "ip": "varbinary(16) DEFAULT NULL COMMENT 'IP Address'",
555
+ "meta": "TEXT"
556
+ }
557
  },
558
+ "active_plugin_features": [
559
  {
560
  "slug": "insights",
561
  "load_priority": 1,
636
  "slug": "email"
637
  }
638
  ],
639
+ "events": {
640
  "test_cron_run": {
641
  "audit": false,
642
  "recent": true
668
  "audit": false
669
  },
670
  "recaptcha_fail": {
671
+ "offense": false,
672
+ "audit": true
673
+ },
674
+ "antibot_pass": {
675
+ "stat": true,
676
  "audit": true
677
+ },
678
+ "antibot_fail": {
679
+ "stat": true,
680
+ "audit": true
681
+ },
682
+ "frontpage_load": {
683
+ "offense": false,
684
+ "stat": false,
685
+ "audit": false
686
  }
687
  },
688
+ "wizards": {
689
  "welcome": {
690
  "title": "Getting Started Setup Wizard",
691
  "desc": "An introduction to this security plugin, helping you get setup and started quickly with the core features.",
src/config/feature-reporting.php CHANGED
@@ -14,6 +14,21 @@
14
  "run_if_wpcli": true,
15
  "tracking_exclude": true
16
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  "sections": [
18
  {
19
  "slug": "section_timings",
@@ -119,20 +134,21 @@
119
  }
120
  ],
121
  "definitions": {
122
- "db_classes": {
123
  "reports": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Reports\\Handler"
124
  },
125
- "reports_table_name": "reports",
126
- "reports_table_columns": {
127
- "rid": "int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Report ID'",
128
- "type": "varchar(3) NOT NULL DEFAULT '' COMMENT 'Report Type'",
129
- "frequency": "varchar(10) NOT NULL DEFAULT '' COMMENT 'Report Interval/Frequency'",
130
- "interval_end_at": "int(15) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'TS of end of interval'",
131
- "sent_at": "int(15) UNSIGNED NOT NULL DEFAULT 0"
132
- },
133
- "reports_table_timestamp_columns": {
134
- "interval_end_at": "Reporting Interval End",
135
- "sent_at": "Report Sent"
 
136
  }
137
  }
138
  }
14
  "run_if_wpcli": true,
15
  "tracking_exclude": true
16
  },
17
+ "menu_items": [
18
+ {
19
+ "title": "Stats (beta)",
20
+ "slug": "stats-redirect"
21
+ }
22
+ ],
23
+ "custom_redirects": [
24
+ {
25
+ "source_mod_page": "stats-redirect",
26
+ "target_mod_page": "insights",
27
+ "query_args": {
28
+ "inav": "reports"
29
+ }
30
+ }
31
+ ],
32
  "sections": [
33
  {
34
  "slug": "section_timings",
134
  }
135
  ],
136
  "definitions": {
137
+ "db_classes": {
138
  "reports": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Reports\\Handler"
139
  },
140
+ "db_table_reports": {
141
+ "slug": "reports",
142
+ "autoexpire": 30,
143
+ "cols_custom": {
144
+ "rid": "int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Report ID'",
145
+ "type": "varchar(3) NOT NULL DEFAULT '' COMMENT 'Report Type'",
146
+ "frequency": "varchar(10) NOT NULL DEFAULT '' COMMENT 'Report Interval/Frequency'"
147
+ },
148
+ "cols_timestamps": {
149
+ "interval_end_at": "Reporting Interval End",
150
+ "sent_at": "Report Sent"
151
+ }
152
  }
153
  }
154
  }
src/config/feature-sessions.php CHANGED
@@ -56,7 +56,8 @@
56
  ],
57
  "definitions": {
58
  "db_classes": {
59
- "session": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Session\\Handler"
 
60
  },
61
  "sessions_table_name": "sessions",
62
  "sessions_table_columns": {
@@ -72,6 +73,22 @@
72
  "login_intent_expires_at": "2FA Window Expires",
73
  "secadmin_at": "Security Admin Authenticated"
74
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  "events": {
76
  "session_start": {
77
  "audit": false
@@ -79,6 +96,11 @@
79
  "session_terminate": {
80
  "audit": false,
81
  "recent": true
 
 
 
 
 
82
  }
83
  }
84
  }
56
  ],
57
  "definitions": {
58
  "db_classes": {
59
+ "sessions": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Session\\Handler",
60
+ "session": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Session\\Handler"
61
  },
62
  "sessions_table_name": "sessions",
63
  "sessions_table_columns": {
73
  "login_intent_expires_at": "2FA Window Expires",
74
  "secadmin_at": "Security Admin Authenticated"
75
  },
76
+ "db_table_sessions": {
77
+ "slug": "sessions",
78
+ "cols_custom": {
79
+ "session_id": "varchar(32) NOT NULL DEFAULT ''",
80
+ "wp_username": "varchar(255) NOT NULL DEFAULT ''",
81
+ "ip": "varchar(60) NOT NULL DEFAULT '0'",
82
+ "browser": "varchar(32) NOT NULL DEFAULT ''",
83
+ "last_activity_uri": "text NOT NULL DEFAULT ''"
84
+ },
85
+ "cols_timestamps": {
86
+ "logged_in_at": "Session Started",
87
+ "last_activity_at": "Last Seen At",
88
+ "login_intent_expires_at": "2FA Window Expires",
89
+ "secadmin_at": "Security Admin Authenticated"
90
+ }
91
+ },
92
  "events": {
93
  "session_start": {
94
  "audit": false
96
  "session_terminate": {
97
  "audit": false,
98
  "recent": true
99
+ },
100
+ "login_success": {
101
+ "offense": false,
102
+ "audit": false,
103
+ "stat": false
104
  }
105
  }
106
  }
src/config/feature-traffic.php CHANGED
@@ -1,6 +1,6 @@
1
  {
2
- "slug": "traffic",
3
- "properties": {
4
  "slug": "traffic",
5
  "name": "Traffic Watch",
6
  "sidebar_name": "Traffic",
@@ -19,8 +19,7 @@
19
  "menu_items": [
20
  {
21
  "title": "Traffic Log",
22
- "slug": "traffic-redirect",
23
- "callback": ""
24
  }
25
  ],
26
  "custom_redirects": [
@@ -32,7 +31,7 @@
32
  }
33
  }
34
  ],
35
- "sections": [
36
  {
37
  "slug": "section_traffic_options",
38
  "primary": true,
@@ -66,7 +65,7 @@
66
  "hidden": true
67
  }
68
  ],
69
- "options": [
70
  {
71
  "key": "enable_traffic",
72
  "section": "section_enable_plugin_feature_traffic",
@@ -214,10 +213,23 @@
214
  "description": "The time limit within which to monitor for excessive requests that exceed the limit."
215
  }
216
  ],
217
- "definitions": {
218
  "db_classes": {
219
  "traffic": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Traffic\\Handler"
220
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  "traffic_table_name": "traffic",
222
  "traffic_table_columns": {
223
  "rid": "varchar(10) NOT NULL DEFAULT '' COMMENT 'Request ID'",
1
  {
2
+ "slug": "traffic",
3
+ "properties": {
4
  "slug": "traffic",
5
  "name": "Traffic Watch",
6
  "sidebar_name": "Traffic",
19
  "menu_items": [
20
  {
21
  "title": "Traffic Log",
22
+ "slug": "traffic-redirect"
 
23
  }
24
  ],
25
  "custom_redirects": [
31
  }
32
  }
33
  ],
34
+ "sections": [
35
  {
36
  "slug": "section_traffic_options",
37
  "primary": true,
65
  "hidden": true
66
  }
67
  ],
68
+ "options": [
69
  {
70
  "key": "enable_traffic",
71
  "section": "section_enable_plugin_feature_traffic",
213
  "description": "The time limit within which to monitor for excessive requests that exceed the limit."
214
  }
215
  ],
216
+ "definitions": {
217
  "db_classes": {
218
  "traffic": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Traffic\\Handler"
219
  },
220
+ "db_table_traffic": {
221
+ "slug": "traffic",
222
+ "cols_custom": {
223
+ "rid": "varchar(10) NOT NULL DEFAULT '' COMMENT 'Request ID'",
224
+ "uid": "int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'User ID'",
225
+ "ip": "varbinary(16) DEFAULT NULL COMMENT 'Visitor IP Address'",
226
+ "path": "text NOT NULL DEFAULT '' COMMENT 'Request Path or URI'",
227
+ "code": "int(5) NOT NULL DEFAULT '200' COMMENT 'HTTP Response Code'",
228
+ "verb": "varchar(10) NOT NULL DEFAULT 'get' COMMENT 'HTTP Method'",
229
+ "ua": "text COMMENT 'Browser User Agent String'",
230
+ "trans": "tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Trangression'"
231
+ }
232
+ },
233
  "traffic_table_name": "traffic",
234
  "traffic_table_columns": {
235
  "rid": "varchar(10) NOT NULL DEFAULT '' COMMENT 'Request ID'",
src/lib/functions/functions.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ use FernleafSystems\Wordpress\Plugin\Shield;
4
+ use FernleafSystems\Wordpress\Services\Services;
5
+
6
+ if ( function_exists( 'shield_security_get_plugin' ) ) {
7
+ return;
8
+ }
9
+
10
+ function shield_security_get_plugin() :ICWP_WPSF_Shield_Security {
11
+ return ICWP_WPSF_Shield_Security::GetInstance();
12
+ }
13
+
14
+ function shield_get_visitor_scores( $IP = null ) :array {
15
+ return ( new Shield\Modules\IPs\Lib\Bots\Calculator\CalculateVisitorBotScores() )
16
+ ->setMod( shield_security_get_plugin()->getController()->getModule_IPs() )
17
+ ->setIP( $IP ?? Services::IP()->getRequestIp() )
18
+ ->scores();
19
+ }
20
+
21
+ function shield_get_visitor_score( $IP = null ) :int {
22
+ return ( new Shield\Modules\IPs\Lib\Bots\Calculator\CalculateVisitorBotScores() )
23
+ ->setMod( shield_security_get_plugin()->getController()->getModule_IPs() )
24
+ ->setIP( $IP ?? Services::IP()->getRequestIp() )
25
+ ->probability();
26
+ }
27
+
28
+ /**
29
+ * Calculates the visitor score then compares it against the user-defined score minimum for bots
30
+ * @param null $IP - defaults to current visitor
31
+ * @return bool - true if bot, false otherwise
32
+ * @throws Exception
33
+ */
34
+ function shield_test_ip_is_bot( $IP = null ) :bool {
35
+ return shield_security_get_plugin()->getController()
36
+ ->getModule_IPs()
37
+ ->getBotSignalsController()
38
+ ->isBot( (string)$IP );
39
+ }
src/lib/src/Controller/Admin/MainAdminMenu.php CHANGED
@@ -32,9 +32,9 @@ class MainAdminMenu {
32
  if ( $menu[ 'top_level' ] ) {
33
 
34
  $labels = $con->getLabels();
35
- $sMenuTitle = empty( $labels[ 'MenuTitle' ] ) ? $menu[ 'title' ] : $labels[ 'MenuTitle' ];
36
- if ( is_null( $sMenuTitle ) ) {
37
- $sMenuTitle = $con->getHumanName();
38
  }
39
 
40
  $sMenuIcon = $con->urls->forImage( $menu[ 'icon_image' ] );
@@ -43,7 +43,7 @@ class MainAdminMenu {
43
  $parentMenuID = $con->getPluginPrefix();
44
  add_menu_page(
45
  $con->getHumanName(),
46
- $sMenuTitle,
47
  $con->getBasePermissions(),
48
  $parentMenuID,
49
  [ $this, 'onDisplayTopMenu' ],
@@ -54,11 +54,11 @@ class MainAdminMenu {
54
 
55
  $menuItems = apply_filters( $con->prefix( 'submenu_items' ), [] );
56
  if ( !empty( $menuItems ) ) {
57
- foreach ( $menuItems as $sMenuTitle => $aMenu ) {
58
- list( $sMenuItemText, $sMenuItemId, $aMenuCallBack, $bShowItem ) = $aMenu;
59
  add_submenu_page(
60
  $bShowItem ? $parentMenuID : null,
61
- $sMenuTitle,
62
  $sMenuItemText,
63
  $con->getBasePermissions(),
64
  $sMenuItemId,
32
  if ( $menu[ 'top_level' ] ) {
33
 
34
  $labels = $con->getLabels();
35
+ $menuTitle = empty( $labels[ 'MenuTitle' ] ) ? $menu[ 'title' ] : $labels[ 'MenuTitle' ];
36
+ if ( is_null( $menuTitle ) ) {
37
+ $menuTitle = $con->getHumanName();
38
  }
39
 
40
  $sMenuIcon = $con->urls->forImage( $menu[ 'icon_image' ] );
43
  $parentMenuID = $con->getPluginPrefix();
44
  add_menu_page(
45
  $con->getHumanName(),
46
+ $menuTitle,
47
  $con->getBasePermissions(),
48
  $parentMenuID,
49
  [ $this, 'onDisplayTopMenu' ],
54
 
55
  $menuItems = apply_filters( $con->prefix( 'submenu_items' ), [] );
56
  if ( !empty( $menuItems ) ) {
57
+ foreach ( $menuItems as $menuTitle => $menuItem ) {
58
+ list( $sMenuItemText, $sMenuItemId, $aMenuCallBack, $bShowItem ) = $menuItem;
59
  add_submenu_page(
60
  $bShowItem ? $parentMenuID : null,
61
+ $menuTitle,
62
  $sMenuItemText,
63
  $con->getBasePermissions(),
64
  $sMenuItemId,
src/lib/src/Controller/Ajax/Init.php CHANGED
@@ -20,13 +20,18 @@ class Init {
20
  $this->ajaxAction();
21
  } );
22
  add_action( 'wp_ajax_nopriv_'.$this->getCon()->prefix(), function () {
23
- $this->ajaxAction();
24
  } );
25
  }
26
 
27
- private function ajaxAction() {
28
- $nonceAction = Services::Request()->request( 'exec' );
29
- check_ajax_referer( $nonceAction, 'exec_nonce' );
 
 
 
 
 
30
 
31
  ob_start();
32
  $response = apply_filters(
@@ -52,4 +57,8 @@ class Init {
52
  false
53
  );
54
  }
 
 
 
 
55
  }
20
  $this->ajaxAction();
21
  } );
22
  add_action( 'wp_ajax_nopriv_'.$this->getCon()->prefix(), function () {
23
+ $this->ajaxAction( false );
24
  } );
25
  }
26
 
27
+ private function ajaxAction( bool $forceDie = true ) {
28
+ $req = Services::Request();
29
+ $nonceAction = $req->request( 'exec' );
30
+
31
+ // if the ajax action is part of the allow list, is may fail the nonce.
32
+ // This is work around for front-end caching plugin that screw everything up.
33
+ check_ajax_referer( $nonceAction, 'exec_nonce',
34
+ $forceDie || !in_array( $nonceAction, $this->getAllowedNoPrivExecs() ) );
35
 
36
  ob_start();
37
  $response = apply_filters(
57
  false
58
  );
59
  }
60
+
61
+ private function getAllowedNoPrivExecs() :array {
62
+ return [];
63
+ }
64
  }
src/lib/src/Controller/Assets/Enqueue.php CHANGED
@@ -23,6 +23,9 @@ class Enqueue {
23
  }
24
 
25
  protected function run() {
 
 
 
26
  add_action( 'wp_enqueue_scripts', function () {
27
  $this->enqueue();
28
  }, 1000 );
@@ -95,6 +98,7 @@ class Enqueue {
95
 
96
  $incl = $con->cfg->includes[ 'register' ];
97
 
 
98
  foreach ( array_keys( $assetKeys ) as $type ) {
99
 
100
  foreach ( $incl[ $type ] as $key => $spec ) {
@@ -102,24 +106,29 @@ class Enqueue {
102
 
103
  $handle = $this->normaliseHandle( $key );
104
  if ( $type === self::CSS ) {
105
- $url = $spec[ 'url' ] ?? $con->urls->forCss( $key );
106
  $reg = wp_register_style(
107
  $handle,
108
- $url,
109
  $this->prefixKeys( $spec[ 'deps' ] ?? [] ),
110
  $con->getVersion()
111
  );
112
  }
113
  else {
114
- $url = $spec[ 'url' ] ?? $con->urls->forJs( $key );
115
  $reg = wp_register_script(
116
  $handle,
117
- $url,
118
  $this->prefixKeys( $spec[ 'deps' ] ?? [] ),
119
- $con->getVersion()
 
120
  );
121
  }
122
 
 
 
 
 
 
 
123
  if ( $reg ) {
124
  $assetKeys[ $type ][] = $handle;
125
  }
@@ -167,7 +176,14 @@ class Enqueue {
167
 
168
  private function runEnqueueOnAssets( string $type, array $asset ) {
169
  array_map(
170
- $type == self::CSS ? 'wp_enqueue_style' : 'wp_enqueue_script',
 
 
 
 
 
 
 
171
  $this->prefixKeys( $asset )
172
  );
173
  }
23
  }
24
 
25
  protected function run() {
26
+ add_action( 'login_enqueue_scripts', function () {
27
+ $this->enqueue();
28
+ }, 1000 );
29
  add_action( 'wp_enqueue_scripts', function () {
30
  $this->enqueue();
31
  }, 1000 );
98
 
99
  $incl = $con->cfg->includes[ 'register' ];
100
 
101
+ $includesService = Services::Includes();
102
  foreach ( array_keys( $assetKeys ) as $type ) {
103
 
104
  foreach ( $incl[ $type ] as $key => $spec ) {
106
 
107
  $handle = $this->normaliseHandle( $key );
108
  if ( $type === self::CSS ) {
 
109
  $reg = wp_register_style(
110
  $handle,
111
+ $con->urls->forCss( $key ),
112
  $this->prefixKeys( $spec[ 'deps' ] ?? [] ),
113
  $con->getVersion()
114
  );
115
  }
116
  else {
 
117
  $reg = wp_register_script(
118
  $handle,
119
+ $con->urls->forJs( $key ),
120
  $this->prefixKeys( $spec[ 'deps' ] ?? [] ),
121
+ $con->getVersion(),
122
+ $spec[ 'footer' ] ?? false
123
  );
124
  }
125
 
126
+ if ( !empty( $spec[ 'attributes' ] ) ) {
127
+ foreach ( $spec[ 'attributes' ] as $attribute => $value ) {
128
+ $includesService->addIncludeAttribute( $handle, $attribute, $value );
129
+ }
130
+ }
131
+
132
  if ( $reg ) {
133
  $assetKeys[ $type ][] = $handle;
134
  }
176
 
177
  private function runEnqueueOnAssets( string $type, array $asset ) {
178
  array_map(
179
+ function ( $asset ) use ( $type ) {
180
+ if ( $type == self::CSS ) {
181
+ wp_enqueue_style( $asset );
182
+ }
183
+ else {
184
+ wp_enqueue_script( $asset );
185
+ }
186
+ },
187
  $this->prefixKeys( $asset )
188
  );
189
  }
src/lib/src/Controller/Assets/Paths.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Controller\Assets;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\PluginControllerConsumer;
6
+
7
+ class Paths {
8
+
9
+ use PluginControllerConsumer;
10
+
11
+ public function forAsset( string $asset = '' ) :string {
12
+ return $this->forPluginItem( $this->getCon()->cfg->paths[ 'assets' ].'/'.ltrim( $asset, '/' ) );
13
+ }
14
+
15
+ public function forFlag( string $flag = '' ) :string {
16
+ return $this->forPluginItem( $this->getCon()->cfg->paths[ 'flags' ].'/'.ltrim( $flag, '/' ) );
17
+ }
18
+
19
+ public function forImage( string $asset ) :string {
20
+ return $this->forAsset( 'images/'.ltrim( $asset, '/' ) );
21
+ }
22
+
23
+ public function forJs( string $asset ) :string {
24
+ return $this->forAsset( 'js/'.ltrim( $asset, '/' ) );
25
+ }
26
+
27
+ public function forPluginItem( string $item = '' ) :string {
28
+ return path_join( $this->getCon()->getRootDir(), ltrim( $item, '/' ) );
29
+ }
30
+
31
+ public function forSource( string $source = '' ) :string {
32
+ return $this->forPluginItem( $this->getCon()->cfg->paths[ 'source' ].'/'.ltrim( $source, '/' ) );
33
+ }
34
+
35
+ public function forTemplate( string $item = '' ) :string {
36
+ return $this->forPluginItem( $this->getCon()->cfg->paths[ 'templates' ].'/'.ltrim( $item, '/' ) );
37
+ }
38
+ }
src/lib/src/Controller/Assets/Urls.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Controller\Assets;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\PluginControllerConsumer;
 
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class Urls {
@@ -22,26 +23,26 @@ class Urls {
22
 
23
  public function forJs( string $asset ) :string {
24
  $url = $this->lookupAssetUrlInSpec( $asset, 'js' );
25
- return empty( $url ) ?
26
- $this->forAsset( 'js/'.Services::Data()->addExtensionToFilePath( $asset, 'js' ) )
27
- : $url;
 
 
 
 
 
 
 
 
28
  }
29
 
30
  public function forAsset( string $asset ) :string {
31
  $con = $this->getCon();
32
-
33
- $path = $con->getPath_Assets( $asset );
34
- if ( Services::WpFs()->exists( $path ) ) {
35
- $url = Services::Includes()->addIncludeModifiedParam(
36
- $this->forPluginItem( $con->getPluginSpec_Path( 'assets' ).'/'.$asset ),
37
- $path
38
- );
39
- }
40
- else {
41
- $url = '';
42
- }
43
-
44
- return $url;
45
  }
46
 
47
  public function forPluginItem( string $path = '' ) :string {
@@ -49,16 +50,27 @@ class Urls {
49
  return add_query_arg( [ 'ver' => $con->getVersion() ], plugins_url( $path, $con->getRootFile() ) );
50
  }
51
 
 
 
 
 
 
 
 
 
 
 
52
  /**
53
  * @param string $asset
54
  * @param string $type
55
  * @return mixed|null
56
  */
57
  protected function lookupAssetUrlInSpec( string $asset, string $type ) {
58
- $registrations = $this->getCon()->cfg->includes[ 'register' ][ $type ];
59
- if ( isset( $registrations[ $asset ] ) && !empty( $registrations[ $asset ][ 'url' ] ) ) {
60
- return $registrations[ $asset ][ 'url' ];
61
- }
62
- return null;
 
63
  }
64
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Controller\Assets;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\PluginControllerConsumer;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Resources\Dynamic;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class Urls {
23
 
24
  public function forJs( string $asset ) :string {
25
  $url = $this->lookupAssetUrlInSpec( $asset, 'js' );
26
+ if ( empty( $url ) ) {
27
+ if ( $this->isAssetDynamic( $asset, 'js' ) ) {
28
+ $url = ( new Dynamic() )
29
+ ->setCon( $this->getCon() )
30
+ ->getResourceUrl( Services::Data()->addExtensionToFilePath( $asset, 'js' ) );
31
+ }
32
+ else {
33
+ $url = $this->forAsset( 'js/'.Services::Data()->addExtensionToFilePath( $asset, 'js' ) );
34
+ }
35
+ }
36
+ return $url;
37
  }
38
 
39
  public function forAsset( string $asset ) :string {
40
  $con = $this->getCon();
41
+ $path = $con->paths->forAsset( $asset );
42
+ return Services::Includes()->addIncludeModifiedParam(
43
+ $this->forPluginItem( $con->cfg->paths[ 'assets' ].'/'.$asset ),
44
+ $path
45
+ );
 
 
 
 
 
 
 
 
46
  }
47
 
48
  public function forPluginItem( string $path = '' ) :string {
50
  return add_query_arg( [ 'ver' => $con->getVersion() ], plugins_url( $path, $con->getRootFile() ) );
51
  }
52
 
53
+ /**
54
+ * @param string $asset
55
+ * @param string $type
56
+ * @return mixed|null
57
+ */
58
+ protected function isAssetDynamic( string $asset, string $type ) :bool {
59
+ $asset = $this->lookupAssetInSpec( $asset, $type );
60
+ return !empty( $asset[ 'dynamic' ] );
61
+ }
62
+
63
  /**
64
  * @param string $asset
65
  * @param string $type
66
  * @return mixed|null
67
  */
68
  protected function lookupAssetUrlInSpec( string $asset, string $type ) {
69
+ $asset = $this->lookupAssetInSpec( $asset, $type );
70
+ return empty( $asset[ 'url' ] ) ? null : $asset[ 'url' ];
71
+ }
72
+
73
+ protected function lookupAssetInSpec( string $asset, string $type ) :array {
74
+ return $this->getCon()->cfg->includes[ 'register' ][ $type ][ $asset ] ?? [];
75
  }
76
  }
src/lib/src/Controller/Config/ConfigVO.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Controller\Config;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
 
7
  /**
8
  * Class ConfigVO
@@ -23,11 +23,7 @@ use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
23
  * @property string $previous_version
24
  * @property array $update_first_detected
25
  */
26
- class ConfigVO {
27
-
28
- use StdClassAdapter {
29
- __get as __adapterGet;
30
- }
31
 
32
  /**
33
  * @var bool
@@ -38,8 +34,8 @@ class ConfigVO {
38
  * @param string $key
39
  * @return mixed
40
  */
41
- public function __get( $key ) {
42
- $val = $this->__adapterGet( $key );
43
 
44
  switch ( $key ) {
45
 
@@ -70,4 +66,13 @@ class ConfigVO {
70
 
71
  return $val;
72
  }
 
 
 
 
 
 
 
 
 
73
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Controller\Config;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
 
7
  /**
8
  * Class ConfigVO
23
  * @property string $previous_version
24
  * @property array $update_first_detected
25
  */
26
+ class ConfigVO extends DynPropertiesClass {
 
 
 
 
27
 
28
  /**
29
  * @var bool
34
  * @param string $key
35
  * @return mixed
36
  */
37
+ public function __get( string $key ) {
38
+ $val = parent::__get( $key );
39
 
40
  switch ( $key ) {
41
 
66
 
67
  return $val;
68
  }
69
+
70
+ /**
71
+ * @param $key
72
+ * @return mixed|null
73
+ * @deprecated 10.3
74
+ */
75
+ private function __adapterGet( $key ) {
76
+ return $this->getRawData()[ $key ] ?? null;
77
+ }
78
  }
src/lib/src/Controller/Controller.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Controller;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
  use FernleafSystems\Wordpress\Plugin\Shield;
7
  use FernleafSystems\Wordpress\Services\Services;
8
  use FernleafSystems\Wordpress\Services\Utilities\Options\Transient;
@@ -12,6 +12,7 @@ use FernleafSystems\Wordpress\Services\Utilities\Options\Transient;
12
  * @package FernleafSystems\Wordpress\Plugin\Shield\Controller
13
  * @property Config\ConfigVO $cfg
14
  * @property Shield\Controller\Assets\Urls $urls
 
15
  * @property bool $is_activating
16
  * @property bool $is_debug
17
  * @property bool $modules_loaded
@@ -20,18 +21,16 @@ use FernleafSystems\Wordpress\Services\Utilities\Options\Transient;
20
  * @property bool $plugin_deactivating
21
  * @property bool $plugin_deleting
22
  * @property bool $plugin_reset
 
23
  * @property false|string $file_forceoff
24
  * @property string $base_file
25
  * @property string $root_file
 
26
  * @property bool $user_can_base_permissions
27
  * @property Shield\Modules\Events\Lib\EventsService $service_events
28
  * @property mixed[]|Shield\Modules\Base\ModCon[] $modules
29
  */
30
- class Controller {
31
-
32
- use StdClassAdapter {
33
- __get as __adapterGet;
34
- }
35
 
36
  /**
37
  * @var \stdClass
@@ -146,8 +145,8 @@ class Controller {
146
  * @param string $key
147
  * @return mixed
148
  */
149
- public function __get( $key ) {
150
- $val = $this->__adapterGet( $key );
151
 
152
  switch ( $key ) {
153
 
@@ -163,6 +162,13 @@ class Controller {
163
  }
164
  break;
165
 
 
 
 
 
 
 
 
166
  case 'is_debug':
167
  if ( is_null( $val ) ) {
168
  $val = ( new Shield\Controller\Utilities\DebugMode() )
@@ -179,6 +185,15 @@ class Controller {
179
  return $val;
180
  }
181
 
 
 
 
 
 
 
 
 
 
182
  /**
183
  * @throws \Exception
184
  */
@@ -299,38 +314,72 @@ class Controller {
299
  }
300
 
301
  /**
302
- * @param string $sFilePath
303
  * @return string|false
304
  */
305
- public function getPluginCachePath( $sFilePath = '' ) {
306
- if ( !$this->buildPluginCacheDir() ) {
307
- // throw new \Exception( sprintf( 'Failed to create cache path: "%s"', $this->getPath_PluginCache() ) );
308
- return false;
 
 
 
 
 
 
 
 
309
  }
310
- return path_join( $this->getPath_PluginCache(), $sFilePath );
 
 
 
 
 
 
 
 
 
 
311
  }
312
 
313
  /**
314
- * @return bool
 
315
  */
316
- private function buildPluginCacheDir() {
317
- $bSuccess = false;
318
- $sBase = $this->getPath_PluginCache();
319
- $oFs = Services::WpFs();
320
- if ( $oFs->mkdir( $sBase ) ) {
321
- $sHt = path_join( $sBase, '.htaccess' );
322
- $sHtContent = "Options -Indexes\ndeny from all";
323
- if ( !$oFs->exists( $sHt ) || ( md5_file( $sHt ) != md5( $sHtContent ) ) ) {
324
- $oFs->putFileContent( $sHt, $sHtContent );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
  }
326
- $sIndex = path_join( $sBase, 'index.php' );
327
- $sIndexContent = "<?php\nhttp_response_code(404);";
328
- if ( !$oFs->exists( $sIndex ) || ( md5_file( $sIndex ) != md5( $sIndexContent ) ) ) {
329
- $oFs->putFileContent( $sIndex, $sIndexContent );
330
  }
331
- $bSuccess = true;
332
  }
333
- return $bSuccess;
334
  }
335
 
336
  protected function doRegisterHooks() {
@@ -350,7 +399,7 @@ class Controller {
350
  add_filter( 'auto_update_plugin', [ $this, 'onWpAutoUpdate' ], 500, 2 );
351
  add_filter( 'set_site_transient_update_plugins', [ $this, 'setUpdateFirstDetectedAt' ] );
352
 
353
- add_action( 'shutdown', [ $this, 'onWpShutdown' ], -1 );
354
  add_action( 'wp_logout', [ $this, 'onWpLogout' ] );
355
 
356
  // GDPR
@@ -469,6 +518,9 @@ class Controller {
469
  ( new Admin\MainAdminMenu() )
470
  ->setCon( $this )
471
  ->execute();
 
 
 
472
  }
473
 
474
  protected function initCrons() {
@@ -495,14 +547,14 @@ class Controller {
495
  }
496
 
497
  /**
498
- * @param string $sAction
499
  * @return array
500
  */
501
- public function getNonceActionData( $sAction = '' ) {
502
  return [
503
  'action' => $this->prefix(), //wp ajax doesn't work without this.
504
- 'exec' => $sAction,
505
- 'exec_nonce' => wp_create_nonce( $sAction ),
506
  // 'rand' => wp_rand( 10000, 99999 )
507
  ];
508
  }
@@ -729,7 +781,7 @@ class Controller {
729
  foreach ( [ '16x16', '32x32', '128x128' ] as $dimension ) {
730
  $key = 'icon_url_'.$dimension;
731
  if ( !empty( $labels[ $key ] ) && !$oDP->isValidWebUrl( $labels[ $key ] ) ) {
732
- $labels[ $key ] = $this->getPluginUrl_Image( $labels[ $key ] );
733
  }
734
  }
735
 
@@ -752,11 +804,11 @@ class Controller {
752
 
753
  protected function deleteFlags() {
754
  $FS = Services::WpFs();
755
- if ( $FS->exists( $this->getPath_Flags( 'rebuild' ) ) ) {
756
- $FS->deleteFile( $this->getPath_Flags( 'rebuild' ) );
757
  }
758
  if ( $this->getIsResetPlugin() ) {
759
- $FS->deleteFile( $this->getPath_Flags( 'reset' ) );
760
  }
761
  }
762
 
@@ -825,7 +877,7 @@ class Controller {
825
  */
826
  public function getPluginSpec() {
827
  if ( isset( $this->cfg ) ) {
828
- return $this->cfg->getRawDataAsArray();
829
  }
830
  return $this->getPluginControllerOptions()->plugin_spec;
831
  }
@@ -835,11 +887,7 @@ class Controller {
835
  * @return string|null
836
  */
837
  public function getPluginSpec_Path( string $key ) {
838
- if ( isset( $this->cfg ) ) {
839
- return $this->cfg->paths[ $key ];
840
- }
841
- $aData = $this->getPluginSpec()[ 'paths' ];
842
- return $aData[ $key ] ?? null;
843
  }
844
 
845
  /**
@@ -850,15 +898,6 @@ class Controller {
850
  return $this->cfg->properties[ $key ] ?? null;
851
  }
852
 
853
- /**
854
- * @param string $key
855
- * @return mixed|null
856
- * @deprecated 10.2.0 - getCfgProperty()
857
- */
858
- protected function getPluginSpec_Property( string $key ) {
859
- return $this->cfg->properties[ $key ] ?? null;
860
- }
861
-
862
  public function getBasePermissions() :string {
863
  if ( isset( $this->cfg ) ) {
864
  return $this->cfg->properties[ 'base_permissions' ];
@@ -936,17 +975,9 @@ class Controller {
936
  return Services::WpGeneral()->getCurrentWpAdminPage() === $this->getPluginPrefix();
937
  }
938
 
939
- /**
940
- * @return bool
941
- * @deprecated 10.2
942
- */
943
- public function getIsRebuildOptionsFromFile() :bool {
944
- return $this->rebuild_options;
945
- }
946
-
947
  public function getIsResetPlugin() :bool {
948
  if ( !isset( $this->plugin_reset ) ) {
949
- $this->plugin_reset = (bool)Services::WpFs()->isFile( $this->getPath_Flags( 'reset' ) );
950
  }
951
  return (bool)$this->plugin_reset;
952
  }
@@ -959,17 +990,6 @@ class Controller {
959
  return $this->getCfgProperty( 'slug_parent' );
960
  }
961
 
962
- /**
963
- * @return string
964
- * @deprecated 10.2.0
965
- */
966
- public function getPluginBaseFile() :string {
967
- if ( !isset( $this->base_file ) ) {
968
- $this->base_file = plugin_basename( $this->getRootFile() );
969
- }
970
- return $this->base_file;
971
- }
972
-
973
  public function getPluginSlug() :string {
974
  return $this->getCfgProperty( 'slug_plugin' );
975
  }
@@ -978,26 +998,6 @@ class Controller {
978
  return add_query_arg( [ 'ver' => $this->getVersion() ], plugins_url( $path, $this->getRootFile() ) );
979
  }
980
 
981
- /**
982
- * @deprecated 10.2
983
- */
984
- public function getPluginUrl_Asset( string $asset ) :string {
985
- $url = '';
986
- $sAssetPath = $this->getPath_Assets( $asset );
987
- if ( Services::WpFs()->exists( $sAssetPath ) ) {
988
- $url = $this->getPluginUrl( $this->getPluginSpec_Path( 'assets' ).'/'.$asset );
989
- return Services::Includes()->addIncludeModifiedParam( $url, $sAssetPath );
990
- }
991
- return $url;
992
- }
993
-
994
- /**
995
- * @deprecated 10.2
996
- */
997
- public function getPluginUrl_Css( string $asset ) :string {
998
- return $this->urls->forCss( $asset );
999
- }
1000
-
1001
  /**
1002
  * @deprecated 10.2
1003
  */
@@ -1006,7 +1006,7 @@ class Controller {
1006
  }
1007
 
1008
  /**
1009
- * @deprecated 10.2
1010
  */
1011
  public function getPluginUrl_Js( string $asset ) :string {
1012
  return $this->urls->forJs( $asset );
@@ -1017,11 +1017,19 @@ class Controller {
1017
  }
1018
 
1019
  public function getPath_Assets( string $asset = '' ) :string {
1020
- $base = path_join( $this->getRootDir(), $this->getPluginSpec_Path( 'assets' ) );
1021
- return empty( $asset ) ? $base : path_join( $base, $asset );
1022
  }
1023
 
 
 
 
 
 
1024
  public function getPath_Flags( string $flag = '' ) :string {
 
 
 
1025
  $base = path_join( $this->getRootDir(), $this->getPluginSpec_Path( 'flags' ) );
1026
  return empty( $flag ) ? $base : path_join( $base, $flag );
1027
  }
@@ -1068,11 +1076,27 @@ class Controller {
1068
  return $this->getPath_SourceFile( $this->getPluginSpec_Path( 'autoload' ) );
1069
  }
1070
 
 
 
 
 
1071
  public function getPath_PluginCache() :string {
1072
- return path_join( WP_CONTENT_DIR, $this->getPluginSpec_Path( 'cache' ) );
 
 
 
 
1073
  }
1074
 
 
 
 
 
 
1075
  public function getPath_SourceFile( string $sourceFile ) :string {
 
 
 
1076
  $base = path_join( $this->getRootDir(), $this->getPluginSpec_Path( 'source' ) );
1077
  return empty( $sourceFile ) ? $base : path_join( $base, $sourceFile );
1078
  }
@@ -1081,8 +1105,11 @@ class Controller {
1081
  return path_join( $this->getRootDir(), $this->getPluginSpec_Path( 'templates' ) ).'/';
1082
  }
1083
 
1084
- public function getPath_TemplatesFile( string $sTemplate ) :string {
1085
- return path_join( $this->getPath_Templates(), $sTemplate );
 
 
 
1086
  }
1087
 
1088
  private function getPathPluginSpec() :string {
@@ -1334,7 +1361,7 @@ class Controller {
1334
  * @return Shield\Modules\Base\ModCon|null|mixed
1335
  */
1336
  public function getModule( string $slug ) {
1337
- $mod = isset( $this->modules[ $slug ] ) ? $this->modules[ $slug ] : null;
1338
  if ( !$mod instanceof Shield\Modules\Base\ModCon ) {
1339
  try {
1340
  $mods = $this->loadCorePluginFeatureHandler()->getActivePluginFeatures();
@@ -1587,7 +1614,7 @@ class Controller {
1587
  */
1588
  private function buildPrivacyPolicyContent() {
1589
  try {
1590
- if ( $this->getModule_SecAdmin()->isWlEnabled() ) {
1591
  $name = $this->getHumanName();
1592
  $href = $this->getLabels()[ 'PluginURI' ];
1593
  }
@@ -1626,61 +1653,4 @@ class Controller {
1626
  ->run();
1627
  }
1628
  }
1629
-
1630
- /**
1631
- * @deprecated 10.2
1632
- */
1633
- public function onWpAdminMenu() {
1634
- }
1635
-
1636
- /**
1637
- * @param \WP_Admin_Bar $adminBar
1638
- * @deprecated 10.2
1639
- */
1640
- public function onWpAdminBarMenu( $adminBar ) {
1641
- }
1642
-
1643
- /**
1644
- * @deprecated 10.2
1645
- */
1646
- public function onWpDashboardSetup() {
1647
- }
1648
-
1649
- /**
1650
- * @deprecated 10.2
1651
- */
1652
- protected function createPluginMenu() :bool {
1653
- }
1654
-
1655
- /**
1656
- * @deprecated 10.2
1657
- */
1658
- protected function fixSubmenu() {
1659
- }
1660
-
1661
- /**
1662
- * Displaying all views now goes through this central function and we work out
1663
- * what to display based on the name of current hook/filter being processed.
1664
- * @deprecated 10.2
1665
- */
1666
- public function onDisplayTopMenu() {
1667
- }
1668
-
1669
- /**
1670
- * @deprecated 10.2
1671
- */
1672
- public function onWpEnqueueFrontendCss() {
1673
- }
1674
-
1675
- /**
1676
- * @deprecated 10.2
1677
- */
1678
- public function onWpEnqueueAdminJs() {
1679
- }
1680
-
1681
- /**
1682
- * @deprecated 10.2
1683
- */
1684
- public function onWpEnqueueAdminCss() {
1685
- }
1686
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Controller;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
  use FernleafSystems\Wordpress\Plugin\Shield;
7
  use FernleafSystems\Wordpress\Services\Services;
8
  use FernleafSystems\Wordpress\Services\Utilities\Options\Transient;
12
  * @package FernleafSystems\Wordpress\Plugin\Shield\Controller
13
  * @property Config\ConfigVO $cfg
14
  * @property Shield\Controller\Assets\Urls $urls
15
+ * @property Shield\Controller\Assets\Paths $paths
16
  * @property bool $is_activating
17
  * @property bool $is_debug
18
  * @property bool $modules_loaded
21
  * @property bool $plugin_deactivating
22
  * @property bool $plugin_deleting
23
  * @property bool $plugin_reset
24
+ * @property bool $cache_dir_ready
25
  * @property false|string $file_forceoff
26
  * @property string $base_file
27
  * @property string $root_file
28
+ * @property bool $is_my_upgrade
29
  * @property bool $user_can_base_permissions
30
  * @property Shield\Modules\Events\Lib\EventsService $service_events
31
  * @property mixed[]|Shield\Modules\Base\ModCon[] $modules
32
  */
33
+ class Controller extends DynPropertiesClass {
 
 
 
 
34
 
35
  /**
36
  * @var \stdClass
145
  * @param string $key
146
  * @return mixed
147
  */
148
+ public function __get( string $key ) {
149
+ $val = parent::__get( $key );
150
 
151
  switch ( $key ) {
152
 
162
  }
163
  break;
164
 
165
+ case 'paths':
166
+ if ( !$val instanceof Shield\Controller\Assets\Paths ) {
167
+ $val = ( new Shield\Controller\Assets\Paths() )->setCon( $this );
168
+ $this->paths = $val;
169
+ }
170
+ break;
171
+
172
  case 'is_debug':
173
  if ( is_null( $val ) ) {
174
  $val = ( new Shield\Controller\Utilities\DebugMode() )
185
  return $val;
186
  }
187
 
188
+ /**
189
+ * @param $key
190
+ * @return mixed|null
191
+ * @deprecated 10.3
192
+ */
193
+ private function __adapterGet( $key ) {
194
+ return $this->getRawData()[ $key ] ?? null;
195
+ }
196
+
197
  /**
198
  * @throws \Exception
199
  */
314
  }
315
 
316
  /**
317
+ * @param string $cachePath
318
  * @return string|false
319
  */
320
+ public function getPluginCachePath( $cachePath = '' ) {
321
+ $path = false;
322
+ if ( $this->hasCacheDir() ) {
323
+ try {
324
+ // Never throws an exception if "hasCacheDir" == true
325
+ $path = $this->buildPluginCacheDir();
326
+ if ( !empty( $cachePath ) ) {
327
+ $path = path_join( $path, $cachePath );
328
+ }
329
+ }
330
+ catch ( \Exception $e ) {
331
+ }
332
  }
333
+ return $path;
334
+ }
335
+
336
+ public function hasCacheDir() :bool {
337
+ try {
338
+ $buildCacheDir = $this->buildPluginCacheDir();
339
+ }
340
+ catch ( \Exception $e ) {
341
+ $buildCacheDir = false;
342
+ }
343
+ return $buildCacheDir;
344
  }
345
 
346
  /**
347
+ * @return string
348
+ * @throws \Exception
349
  */
350
+ private function buildPluginCacheDir() :string {
351
+ $FS = Services::WpFs();
352
+
353
+ $cacheDirBasename = $this->cfg->paths[ 'cache' ];
354
+ if ( empty( $cacheDirBasename ) ) {
355
+ $this->cache_dir_ready = false;
356
+ throw new \Exception( 'No slug for cache dir' );
357
+ }
358
+
359
+ $cacheDir = path_join( WP_CONTENT_DIR, $cacheDirBasename );
360
+ if ( empty( $this->cache_dir_ready ) && $FS->mkdir( $cacheDir ) ) {
361
+ $htFile = path_join( $cacheDir, '.htaccess' );
362
+ $htContent = implode( "\n", [
363
+ "# BEGIN SHIELD",
364
+ "Options -Indexes",
365
+ "Order allow,deny",
366
+ "Deny from all",
367
+ '<FilesMatch "^.*\.(css|js)$">',
368
+ " Allow from all",
369
+ '</FilesMatch>',
370
+ "# END SHIELD"
371
+ ] );
372
+ if ( !$FS->exists( $htFile ) || ( md5_file( $htFile ) !== md5( $htContent ) ) ) {
373
+ $FS->putFileContent( $htFile, $htContent );
374
  }
375
+ $index = path_join( $cacheDir, 'index.php' );
376
+ $indexContent = "<?php\nhttp_response_code(404);";
377
+ if ( !$FS->exists( $index ) || ( md5_file( $index ) !== md5( $indexContent ) ) ) {
378
+ $FS->putFileContent( $index, $indexContent );
379
  }
380
+ $this->cache_dir_ready = true;
381
  }
382
+ return $cacheDir;
383
  }
384
 
385
  protected function doRegisterHooks() {
399
  add_filter( 'auto_update_plugin', [ $this, 'onWpAutoUpdate' ], 500, 2 );
400
  add_filter( 'set_site_transient_update_plugins', [ $this, 'setUpdateFirstDetectedAt' ] );
401
 
402
+ add_action( 'shutdown', [ $this, 'onWpShutdown' ], PHP_INT_MIN );
403
  add_action( 'wp_logout', [ $this, 'onWpLogout' ] );
404
 
405
  // GDPR
518
  ( new Admin\MainAdminMenu() )
519
  ->setCon( $this )
520
  ->execute();
521
+ ( new Utilities\CaptureMyUpgrade() )
522
+ ->setCon( $this )
523
+ ->execute();
524
  }
525
 
526
  protected function initCrons() {
547
  }
548
 
549
  /**
550
+ * @param string $action
551
  * @return array
552
  */
553
+ public function getNonceActionData( $action = '' ) {
554
  return [
555
  'action' => $this->prefix(), //wp ajax doesn't work without this.
556
+ 'exec' => $action,
557
+ 'exec_nonce' => wp_create_nonce( $action ),
558
  // 'rand' => wp_rand( 10000, 99999 )
559
  ];
560
  }
781
  foreach ( [ '16x16', '32x32', '128x128' ] as $dimension ) {
782
  $key = 'icon_url_'.$dimension;
783
  if ( !empty( $labels[ $key ] ) && !$oDP->isValidWebUrl( $labels[ $key ] ) ) {
784
+ $labels[ $key ] = $this->urls->forImage( $labels[ $key ] );
785
  }
786
  }
787
 
804
 
805
  protected function deleteFlags() {
806
  $FS = Services::WpFs();
807
+ if ( $FS->exists( $this->paths->forFlag( 'rebuild' ) ) ) {
808
+ $FS->deleteFile( $this->paths->forFlag( 'rebuild' ) );
809
  }
810
  if ( $this->getIsResetPlugin() ) {
811
+ $FS->deleteFile( $this->paths->forFlag( 'reset' ) );
812
  }
813
  }
814
 
877
  */
878
  public function getPluginSpec() {
879
  if ( isset( $this->cfg ) ) {
880
+ return $this->cfg->getRawData();
881
  }
882
  return $this->getPluginControllerOptions()->plugin_spec;
883
  }
887
  * @return string|null
888
  */
889
  public function getPluginSpec_Path( string $key ) {
890
+ return $this->cfg->paths[ $key ] ?? null;
 
 
 
 
891
  }
892
 
893
  /**
898
  return $this->cfg->properties[ $key ] ?? null;
899
  }
900
 
 
 
 
 
 
 
 
 
 
901
  public function getBasePermissions() :string {
902
  if ( isset( $this->cfg ) ) {
903
  return $this->cfg->properties[ 'base_permissions' ];
975
  return Services::WpGeneral()->getCurrentWpAdminPage() === $this->getPluginPrefix();
976
  }
977
 
 
 
 
 
 
 
 
 
978
  public function getIsResetPlugin() :bool {
979
  if ( !isset( $this->plugin_reset ) ) {
980
+ $this->plugin_reset = (bool)Services::WpFs()->isFile( $this->paths->forFlag( 'reset' ) );
981
  }
982
  return (bool)$this->plugin_reset;
983
  }
990
  return $this->getCfgProperty( 'slug_parent' );
991
  }
992
 
 
 
 
 
 
 
 
 
 
 
 
993
  public function getPluginSlug() :string {
994
  return $this->getCfgProperty( 'slug_plugin' );
995
  }
998
  return add_query_arg( [ 'ver' => $this->getVersion() ], plugins_url( $path, $this->getRootFile() ) );
999
  }
1000
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1001
  /**
1002
  * @deprecated 10.2
1003
  */
1006
  }
1007
 
1008
  /**
1009
+ * @deprecated 10.3
1010
  */
1011
  public function getPluginUrl_Js( string $asset ) :string {
1012
  return $this->urls->forJs( $asset );
1017
  }
1018
 
1019
  public function getPath_Assets( string $asset = '' ) :string {
1020
+ $base = path_join( $this->getRootDir(), $this->cfg->paths[ 'assets' ] );
1021
+ return empty( $asset ) ? $base : path_join( $base, ltrim( $asset, '/' ) );
1022
  }
1023
 
1024
+ /**
1025
+ * @param string $flag
1026
+ * @return string
1027
+ * @deprecated 10.3
1028
+ */
1029
  public function getPath_Flags( string $flag = '' ) :string {
1030
+ if ( isset( $this->paths ) ) {
1031
+ return $this->paths->forFlag( $flag );
1032
+ }
1033
  $base = path_join( $this->getRootDir(), $this->getPluginSpec_Path( 'flags' ) );
1034
  return empty( $flag ) ? $base : path_join( $base, $flag );
1035
  }
1076
  return $this->getPath_SourceFile( $this->getPluginSpec_Path( 'autoload' ) );
1077
  }
1078
 
1079
+ /**
1080
+ * @return string
1081
+ * @throws \Exception
1082
+ */
1083
  public function getPath_PluginCache() :string {
1084
+ $cacheSlug = $this->getPluginSpec_Path( 'cache' );
1085
+ if ( empty( $cacheSlug ) ) {
1086
+ throw new \Exception( 'Cache dir slug was empty' );
1087
+ }
1088
+ return path_join( WP_CONTENT_DIR, $cacheSlug );
1089
  }
1090
 
1091
+ /**
1092
+ * @param string $sourceFile
1093
+ * @return string
1094
+ * @deprecated 10.3
1095
+ */
1096
  public function getPath_SourceFile( string $sourceFile ) :string {
1097
+ if ( isset( $this->paths ) ) {
1098
+ return $this->paths->forSource( $sourceFile );
1099
+ }
1100
  $base = path_join( $this->getRootDir(), $this->getPluginSpec_Path( 'source' ) );
1101
  return empty( $sourceFile ) ? $base : path_join( $base, $sourceFile );
1102
  }
1105
  return path_join( $this->getRootDir(), $this->getPluginSpec_Path( 'templates' ) ).'/';
1106
  }
1107
 
1108
+ public function getPath_TemplatesFile( string $template ) :string {
1109
+ if ( isset( $this->paths ) ) {
1110
+ return $this->paths->forTemplate( $template );
1111
+ }
1112
+ return path_join( $this->getPath_Templates(), $template );
1113
  }
1114
 
1115
  private function getPathPluginSpec() :string {
1361
  * @return Shield\Modules\Base\ModCon|null|mixed
1362
  */
1363
  public function getModule( string $slug ) {
1364
+ $mod = $this->modules[ $slug ] ?? null;
1365
  if ( !$mod instanceof Shield\Modules\Base\ModCon ) {
1366
  try {
1367
  $mods = $this->loadCorePluginFeatureHandler()->getActivePluginFeatures();
1614
  */
1615
  private function buildPrivacyPolicyContent() {
1616
  try {
1617
+ if ( $this->getModule_SecAdmin()->isEnabledWhitelabel() ) {
1618
  $name = $this->getHumanName();
1619
  $href = $this->getLabels()[ 'PluginURI' ];
1620
  }
1653
  ->run();
1654
  }
1655
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1656
  }
src/lib/src/Controller/Utilities/CaptureMyUpgrade.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Controller\Utilities;
4
+
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
+ use FernleafSystems\Wordpress\Plugin\Shield;
7
+
8
+ class CaptureMyUpgrade {
9
+
10
+ use Shield\Modules\PluginControllerConsumer;
11
+ use ExecOnce;
12
+
13
+ protected function run() {
14
+ add_filter( 'upgrader_post_install', [ $this, 'captureMyInstall' ], 10, 2 );
15
+ add_action( 'upgrader_process_complete', [ $this, 'captureMyUpgrade' ], 10, 2 );
16
+ }
17
+
18
+ public function captureMyInstall( $true, $hooksExtra ) {
19
+ if ( !empty( $hooksExtra[ 'plugin' ] ) && $hooksExtra['plugin'] === $this->getCon()->base_file ) {
20
+ $this->getCon()->is_my_upgrade = true;
21
+ }
22
+ return $true;
23
+ }
24
+
25
+ public function captureMyUpgrade( $upgradeHandler, $data ) {
26
+ if ( ( $data[ 'action' ] ?? null === 'update' )
27
+ && ( $data[ 'type' ] ?? null === 'plugin' )
28
+ && is_array( $data[ 'plugins' ] ?? null ) ) {
29
+ foreach ( $data[ 'plugins' ] as $item ) {
30
+ if ( $item === $this->getCon()->root_file ) {
31
+ $this->getCon()->is_my_upgrade = true;
32
+ break;
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
src/lib/src/Controller/Utilities/DebugMode.php CHANGED
@@ -37,10 +37,10 @@ class DebugMode {
37
  public function isActiveViaModeFile() :bool {
38
  $con = $this->getCon();
39
  $FS = Services::WpFs();
40
- $correctPath = $con->getPath_Flags( 'mode.debug' );
41
 
42
  // We first look for the presence of the file (which may not be named in all lower-case)
43
- $foundFile = $FS->findFileInDir( 'mode.debug', $con->getPath_Flags(), false, false );
44
  if ( !empty( $foundFile )
45
  && $FS->isFile( $foundFile ) && !$FS->isFile( $correctPath )
46
  && !basename( $correctPath ) !== basename( $foundFile ) ) {
@@ -50,6 +50,6 @@ class DebugMode {
50
  }
51
 
52
  private function getPathToModeFile() :string {
53
- return $this->getCon()->getPath_Flags( 'mode.debug' );
54
  }
55
  }
37
  public function isActiveViaModeFile() :bool {
38
  $con = $this->getCon();
39
  $FS = Services::WpFs();
40
+ $correctPath = $con->paths->forFlag( 'mode.debug' );
41
 
42
  // We first look for the presence of the file (which may not be named in all lower-case)
43
+ $foundFile = $FS->findFileInDir( 'mode.debug', $con->paths->forFlag(), false, false );
44
  if ( !empty( $foundFile )
45
  && $FS->isFile( $foundFile ) && !$FS->isFile( $correctPath )
46
  && !basename( $correctPath ) !== basename( $foundFile ) ) {
50
  }
51
 
52
  private function getPathToModeFile() :string {
53
+ return $this->getCon()->paths->forFlag( 'mode.debug' );
54
  }
55
  }
src/lib/src/Crons/DailyCron.php CHANGED
@@ -19,18 +19,18 @@ class DailyCron extends BaseCron {
19
  }
20
 
21
  public function getFirstRunTimestamp() :int {
22
- $nHour = apply_filters( $this->getCon()->prefix( 'daily_cron_hour' ), 7 );
23
- if ( $nHour < 0 || $nHour > 23 ) {
24
- $nHour = 7;
25
  }
26
- $oCarb = Services::Request()
27
  ->carbon( true )
28
  ->minute( rand( 1, 59 ) )
29
  ->second( 0 );
30
- if ( $oCarb->hour >= $nHour ) {
31
- $oCarb->addDays( 1 );
32
  }
33
- return $oCarb->hour( $nHour )->timestamp;
34
  }
35
 
36
  /**
19
  }
20
 
21
  public function getFirstRunTimestamp() :int {
22
+ $hour = (int)apply_filters( 'shield/daily_cron_hour', 7 );
23
+ if ( $hour < 0 || $hour > 23 ) {
24
+ $hour = 7;
25
  }
26
+ $carbon = Services::Request()
27
  ->carbon( true )
28
  ->minute( rand( 1, 59 ) )
29
  ->second( 0 );
30
+ if ( $carbon->hour >= $hour ) {
31
+ $carbon->addDays( 1 );
32
  }
33
+ return $carbon->hour( $hour )->timestamp;
34
  }
35
 
36
  /**
src/lib/src/Databases/AdminNotes/Handler.php CHANGED
@@ -2,15 +2,6 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\AdminNotes;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
 
7
- class Handler extends Base\Handler {
8
-
9
- public function getCustomColumns() :array {
10
- return $this->getOptions()->getDef( 'db_notes_table_columns' );
11
- }
12
-
13
- protected function getDefaultTableName() :string {
14
- return $this->getOptions()->getDef( 'db_notes_name' );
15
- }
16
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\AdminNotes;
4
 
5
+ class Handler extends \FernleafSystems\Wordpress\Plugin\Shield\Databases\Base\Handler {
6
 
 
 
 
 
 
 
 
 
 
7
  }
src/lib/src/Databases/AdminNotes/Insert.php CHANGED
@@ -14,20 +14,16 @@ class Insert extends Base\Insert {
14
  protected function verifyInsertData() {
15
  parent::verifyInsertData();
16
 
17
- $aData = $this->getInsertData();
18
- if ( empty( $aData[ 'wp_username' ] ) ) {
19
- $sUser = Services::WpUsers()->getCurrentWpUsername();
20
- $aData[ 'wp_username' ] = empty( $sUser ) ? 'unknown' : $sUser;
21
  }
22
 
23
- return $this->setInsertData( $aData );
24
  }
25
 
26
- /**
27
- * @param string $sNote
28
- * @return bool
29
- */
30
- public function create( $sNote ) {
31
- return $this->setInsertData( [ 'note' => esc_sql( $sNote ) ] )->query() === 1;
32
  }
33
  }
14
  protected function verifyInsertData() {
15
  parent::verifyInsertData();
16
 
17
+ $data = $this->getInsertData();
18
+ if ( empty( $data[ 'wp_username' ] ) ) {
19
+ $username = Services::WpUsers()->getCurrentWpUsername();
20
+ $data[ 'wp_username' ] = empty( $username ) ? 'unknown' : $username;
21
  }
22
 
23
+ return $this->setInsertData( $data );
24
  }
25
 
26
+ public function create( string $note ) :bool {
27
+ return $this->setInsertData( [ 'note' => esc_sql( $note ) ] )->query() === 1;
 
 
 
 
28
  }
29
  }
src/lib/src/Databases/AuditTrail/EntryVO.php CHANGED
@@ -7,11 +7,10 @@ use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
7
  /**
8
  * Class EntryVO
9
  *
10
- * @property string $$ip
 
11
  * @property string $message
12
  * @property string $wp_username
13
- * @property string $rid
14
- * @property string $ip
15
  * @property string $event
16
  * @property string $context
17
  * @property string $category
7
  /**
8
  * Class EntryVO
9
  *
10
+ * @property int $rid
11
+ * @property string $ip
12
  * @property string $message
13
  * @property string $wp_username
 
 
14
  * @property string $event
15
  * @property string $context
16
  * @property string $category
src/lib/src/Databases/AuditTrail/Insert.php CHANGED
@@ -14,18 +14,18 @@ class Insert extends Base\Insert {
14
  protected function verifyInsertData() {
15
  parent::verifyInsertData();
16
 
17
- $aData = $this->getInsertData();
18
 
19
- if ( is_array( $aData[ 'message' ] ) ) {
20
- $aData[ 'message' ] = implode( ' ', $aData[ 'message' ] );
21
  }
22
- if ( isset( $aData[ 'data' ] ) && !is_string( $aData[ 'data' ] ) ) {
23
- $aData[ 'data' ] = '';
24
  }
25
- if ( empty( $aData[ 'ip' ] ) || !Services::IP()->isValidIp( $aData[ 'ip' ] ) ) {
26
- $aData[ 'ip' ] = '';
27
  }
28
 
29
- return $this->setInsertData( $aData );
30
  }
31
  }
14
  protected function verifyInsertData() {
15
  parent::verifyInsertData();
16
 
17
+ $data = $this->getInsertData();
18
 
19
+ if ( isset( $data[ 'message' ] ) && is_array( $data[ 'message' ] ) ) {
20
+ $data[ 'message' ] = implode( ' ', $data[ 'message' ] );
21
  }
22
+ if ( isset( $data[ 'data' ] ) && !is_string( $data[ 'data' ] ) ) {
23
+ $data[ 'data' ] = '';
24
  }
25
+ if ( empty( $data[ 'ip' ] ) || !Services::IP()->isValidIp( $data[ 'ip' ] ) ) {
26
+ $data[ 'ip' ] = '';
27
  }
28
 
29
+ return $this->setInsertData( $data );
30
  }
31
  }
src/lib/src/Databases/AuditTrail/Select.php CHANGED
@@ -3,11 +3,12 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\AuditTrail;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Tool\IpListSort;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class Select extends Base\Select {
10
 
 
 
11
  /**
12
  * @return string[]
13
  */
@@ -15,13 +16,6 @@ class Select extends Base\Select {
15
  return $this->getDistinct_FilterAndSort( 'event' );
16
  }
17
 
18
- /**
19
- * @return string[]
20
- */
21
- public function getDistinctIps() {
22
- return IpListSort::Sort( $this->getDistinctForColumn( 'ip' ) );
23
- }
24
-
25
  /**
26
  * @return string[]
27
  */
@@ -41,16 +35,24 @@ class Select extends Base\Select {
41
  }
42
 
43
  /**
44
- * @param string $sIp
45
  * @return $this
46
  */
47
- public function filterByIp( $sIp ) {
48
- if ( Services::IP()->isValidIp( $sIp ) ) {
49
- $this->addWhereEquals( 'ip', trim( $sIp ) );
50
  }
51
  return $this;
52
  }
53
 
 
 
 
 
 
 
 
 
54
  /**
55
  * @param string $sIp
56
  * @return $this
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\AuditTrail;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
 
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class Select extends Base\Select {
9
 
10
+ use Base\Traits\Select_IPTable;
11
+
12
  /**
13
  * @return string[]
14
  */
16
  return $this->getDistinct_FilterAndSort( 'event' );
17
  }
18
 
 
 
 
 
 
 
 
19
  /**
20
  * @return string[]
21
  */
35
  }
36
 
37
  /**
38
+ * @param string $ip
39
  * @return $this
40
  */
41
+ public function filterByIp( $ip ) {
42
+ if ( Services::IP()->isValidIp( $ip ) ) {
43
+ $this->addWhereEquals( 'ip', trim( $ip ) );
44
  }
45
  return $this;
46
  }
47
 
48
+ /**
49
+ * @param int $id
50
+ * @return $this
51
+ */
52
+ public function filterByRequestID( int $id ) {
53
+ return $this->addWhereEquals( 'rid', $id );
54
+ }
55
+
56
  /**
57
  * @param string $sIp
58
  * @return $this
src/lib/src/Databases/AuditTrail/Update.php CHANGED
@@ -1,21 +1,17 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\AuditTrail;
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 $oEntry
12
- * @param int $nIncrease
13
  * @return bool
14
  */
15
- public function updateCount( $oEntry, $nIncrease = 1 ) {
16
- return $this->updateEntry( $oEntry, [
17
- 'count' => $oEntry->count + $nIncrease,
18
- 'updated_at' => Services::Request()->ts()
19
  ] );
20
  }
21
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\AuditTrail;
4
 
5
+ class Update extends \FernleafSystems\Wordpress\Plugin\Shield\Databases\Base\Update {
 
 
 
6
 
7
  /**
8
+ * @param EntryVO $entry
9
+ * @param int $increase
10
  * @return bool
11
  */
12
+ public function updateCount( $entry, $increase = 1 ) :bool {
13
+ return $this->updateEntry( $entry, [
14
+ 'count' => $entry->count + $increase,
 
15
  ] );
16
  }
17
  }
src/lib/src/Databases/Base/BaseQuery.php CHANGED
@@ -53,13 +53,13 @@ abstract class BaseQuery {
53
  }
54
 
55
  /**
56
- * @param string $sColumn
57
  * @param string|array $mValue
58
- * @param string $sOperator
59
  * @return $this
60
  */
61
- public function addWhere( $sColumn, $mValue, $sOperator = '=' ) {
62
- if ( !$this->isValidComparisonOperator( $sOperator ) ) {
63
  return $this; // Exception?
64
  }
65
 
@@ -70,7 +70,7 @@ abstract class BaseQuery {
70
  else {
71
  $mValue = esc_sql( $mValue );
72
 
73
- if ( strcasecmp( $sOperator, 'LIKE' ) === 0 ) {
74
  $mValue = sprintf( '%%%s%%', $mValue );
75
  }
76
  if ( is_string( $mValue ) ) {
@@ -78,9 +78,9 @@ abstract class BaseQuery {
78
  }
79
  }
80
 
81
- $aWhere = $this->getWheres();
82
- $aWhere[] = sprintf( '`%s` %s %s', esc_sql( $sColumn ), $sOperator, $mValue );
83
- return $this->setWheres( $aWhere );
84
  }
85
 
86
  /**
@@ -311,10 +311,7 @@ abstract class BaseQuery {
311
  return max( (int)$this->nLimit, 0 );
312
  }
313
 
314
- /**
315
- * @return array
316
- */
317
- public function getWheres() {
318
  if ( !is_array( $this->aWheres ) ) {
319
  $this->aWheres = [];
320
  }
@@ -446,33 +443,33 @@ abstract class BaseQuery {
446
  }
447
 
448
  /**
449
- * @param array $aWheres
450
  * @return $this
451
  */
452
- public function setWheres( $aWheres ) {
453
- $this->aWheres = $aWheres;
454
  return $this;
455
  }
456
 
457
  /**
458
- * @param EntryVO $oVo
459
  * @return $this
460
  */
461
- public function setWheresFromVo( $oVo ) {
462
- foreach ( $oVo->getRawDataAsArray() as $sCol => $mVal ) {
463
- $this->addWhereEquals( $sCol, $mVal );
464
  }
465
  return $this;
466
  }
467
 
468
  /**
469
  * Very basic
470
- * @param string $sOp
471
  * @return bool
472
  */
473
- protected function isValidComparisonOperator( $sOp ) {
474
  return in_array(
475
- strtoupper( $sOp ),
476
  [ '=', '<', '>', '!=', '<>', '<=', '>=', '<=>', 'IN', 'LIKE', 'NOT LIKE' ]
477
  );
478
  }
53
  }
54
 
55
  /**
56
+ * @param string $column
57
  * @param string|array $mValue
58
+ * @param string $operator
59
  * @return $this
60
  */
61
+ public function addWhere( $column, $mValue, $operator = '=' ) {
62
+ if ( !$this->isValidComparisonOperator( $operator ) ) {
63
  return $this; // Exception?
64
  }
65
 
70
  else {
71
  $mValue = esc_sql( $mValue );
72
 
73
+ if ( strcasecmp( $operator, 'LIKE' ) === 0 ) {
74
  $mValue = sprintf( '%%%s%%', $mValue );
75
  }
76
  if ( is_string( $mValue ) ) {
78
  }
79
  }
80
 
81
+ $where = $this->getWheres();
82
+ $where[] = sprintf( '`%s` %s %s', esc_sql( $column ), $operator, $mValue );
83
+ return $this->setWheres( $where );
84
  }
85
 
86
  /**
311
  return max( (int)$this->nLimit, 0 );
312
  }
313
 
314
+ public function getWheres() :array {
 
 
 
315
  if ( !is_array( $this->aWheres ) ) {
316
  $this->aWheres = [];
317
  }
443
  }
444
 
445
  /**
446
+ * @param array $where
447
  * @return $this
448
  */
449
+ public function setWheres( $where ) {
450
+ $this->aWheres = $where;
451
  return $this;
452
  }
453
 
454
  /**
455
+ * @param EntryVO $VO
456
  * @return $this
457
  */
458
+ public function setWheresFromVo( $VO ) {
459
+ foreach ( $VO->getRawData() as $col => $mVal ) {
460
+ $this->addWhereEquals( $col, $mVal );
461
  }
462
  return $this;
463
  }
464
 
465
  /**
466
  * Very basic
467
+ * @param string $op
468
  * @return bool
469
  */
470
+ protected function isValidComparisonOperator( $op ) {
471
  return in_array(
472
+ strtoupper( $op ),
473
  [ '=', '<', '>', '!=', '<>', '<=', '>=', '<=>', 'IN', 'LIKE', 'NOT LIKE' ]
474
  );
475
  }
src/lib/src/Databases/Base/Delete.php CHANGED
@@ -32,14 +32,14 @@ class Delete extends BaseQuery {
32
 
33
  /**
34
  * NOTE: Does not reset() before query, so may be customized with where.
35
- * @param int $nMaxEntries
36
- * @param string $sSortColumn
37
  * @param bool $bOldestFirst
38
  * @return int
39
  * @throws \Exception
40
  */
41
- public function deleteExcess( $nMaxEntries, $sSortColumn = 'created_at', $bOldestFirst = true ) {
42
- if ( is_null( $nMaxEntries ) ) {
43
  throw new \Exception( 'Max Entries not specified for table excess delete.' );
44
  }
45
 
@@ -50,10 +50,10 @@ class Delete extends BaseQuery {
50
  ->getQuerySelector()
51
  ->setWheres( $this->getWheres() )
52
  ->count();
53
- $nToDelete = $nTotal - $nMaxEntries;
54
 
55
  if ( $nToDelete > 0 ) {
56
- $nEntriesDeleted = $this->setOrderBy( $sSortColumn, $bOldestFirst ? 'ASC' : 'DESC' )
57
  ->setLimit( $nToDelete )
58
  ->query();
59
  }
32
 
33
  /**
34
  * NOTE: Does not reset() before query, so may be customized with where.
35
+ * @param int $maxEntries
36
+ * @param string $orderByColumn
37
  * @param bool $bOldestFirst
38
  * @return int
39
  * @throws \Exception
40
  */
41
+ public function deleteExcess( $maxEntries, $orderByColumn = 'created_at', $bOldestFirst = true ) {
42
+ if ( is_null( $maxEntries ) ) {
43
  throw new \Exception( 'Max Entries not specified for table excess delete.' );
44
  }
45
 
50
  ->getQuerySelector()
51
  ->setWheres( $this->getWheres() )
52
  ->count();
53
+ $nToDelete = $nTotal - $maxEntries;
54
 
55
  if ( $nToDelete > 0 ) {
56
+ $nEntriesDeleted = $this->setOrderBy( $orderByColumn, $bOldestFirst ? 'ASC' : 'DESC' )
57
  ->setLimit( $nToDelete )
58
  ->query();
59
  }
src/lib/src/Databases/Base/EntryVO.php CHANGED
@@ -1,8 +1,8 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
 
7
  /**
8
  * Class BaseEntryVO
@@ -12,40 +12,32 @@ use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
12
  * @property int $created_at
13
  * @property int $deleted_at
14
  */
15
- class EntryVO {
16
 
17
- use StdClassAdapter {
18
- __get as __adapterGet;
19
- __set as __adapterSet;
20
  }
21
 
22
  /**
23
- * @param array $aRow
24
- */
25
- public function __construct( $aRow = null ) {
26
- $this->applyFromArray( $aRow );
27
- }
28
-
29
- /**
30
- * @param string $sProperty
31
  * @return mixed
32
  */
33
- public function __get( $sProperty ) {
34
 
35
- $mVal = $this->__adapterGet( $sProperty );
36
 
37
- switch ( $sProperty ) {
38
 
39
  case 'meta':
40
- if ( is_string( $mVal ) && !empty( $mVal ) ) {
41
- $mVal = base64_decode( $mVal );
42
- if ( !empty( $mVal ) ) {
43
- $mVal = @json_decode( $mVal, true );
44
  }
45
  }
46
 
47
- if ( !is_array( $mVal ) ) {
48
- $mVal = [];
49
  }
50
  break;
51
 
@@ -53,50 +45,46 @@ class EntryVO {
53
  break;
54
  }
55
 
56
- return $mVal;
 
 
 
 
57
  }
58
 
59
  /**
60
- * @param string $sProperty
61
- * @param mixed $mValue
62
- * @return $this|mixed
63
  */
64
- public function __set( $sProperty, $mValue ) {
65
 
66
- switch ( $sProperty ) {
67
 
68
  case 'meta':
69
- if ( !is_array( $mValue ) ) {
70
- $mValue = [];
71
  }
72
- $mValue = base64_encode( json_encode( $mValue ) );
73
  break;
74
 
75
  default:
76
  break;
77
  }
78
 
79
- return $this->__adapterSet( $sProperty, $mValue );
80
  }
81
 
82
- /**
83
- * @return int
84
- */
85
- public function getCreatedAt() {
86
  return (int)$this->created_at;
87
  }
88
 
89
- /**
90
- * @return int
91
- */
92
- public function getMeta() {
93
- return (int)$this->created_at;
94
  }
95
 
96
- /**
97
- * @return bool
98
- */
99
- public function isDeleted() {
100
  return $this->deleted_at > 0;
101
  }
102
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
 
7
  /**
8
  * Class BaseEntryVO
12
  * @property int $created_at
13
  * @property int $deleted_at
14
  */
15
+ class EntryVO extends DynPropertiesClass {
16
 
17
+ public function __construct( array $row = [] ) {
18
+ $this->applyFromArray( $row );
 
19
  }
20
 
21
  /**
22
+ * @param string $key
 
 
 
 
 
 
 
23
  * @return mixed
24
  */
25
+ public function __get( string $key ) {
26
 
27
+ $value = parent::__get( $key );
28
 
29
+ switch ( $key ) {
30
 
31
  case 'meta':
32
+ if ( is_string( $value ) && !empty( $value ) ) {
33
+ $value = base64_decode( $value );
34
+ if ( !empty( $value ) ) {
35
+ $value = @json_decode( $value, true );
36
  }
37
  }
38
 
39
+ if ( !is_array( $value ) ) {
40
+ $value = [];
41
  }
42
  break;
43
 
45
  break;
46
  }
47
 
48
+ if ( preg_match( '#^.*_at$#i', $key ) ) {
49
+ $value = (int)$value;
50
+ }
51
+
52
+ return $value;
53
  }
54
 
55
  /**
56
+ * @param string $key
57
+ * @param mixed $value
 
58
  */
59
+ public function __set( string $key, $value ) {
60
 
61
+ switch ( $key ) {
62
 
63
  case 'meta':
64
+ if ( !is_array( $value ) ) {
65
+ $value = [];
66
  }
67
+ $value = base64_encode( json_encode( $value ) );
68
  break;
69
 
70
  default:
71
  break;
72
  }
73
 
74
+ parent::__set( $key, $value );
75
  }
76
 
77
+ public function getCreatedAt() :int {
 
 
 
78
  return (int)$this->created_at;
79
  }
80
 
81
+ public function getHash() :string {
82
+ $data = $this->getRawData();
83
+ asort( $data );
84
+ return md5( serialize( $data ) );
 
85
  }
86
 
87
+ public function isDeleted() :bool {
 
 
 
88
  return $this->deleted_at > 0;
89
  }
90
  }
src/lib/src/Databases/Base/Handler.php CHANGED
@@ -2,6 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Common\AlignTableWithSchema;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Common\TableSchema;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
@@ -14,6 +15,7 @@ use FernleafSystems\Wordpress\Services\Services;
14
  abstract class Handler {
15
 
16
  use ModConsumer;
 
17
 
18
  /**
19
  * @var string
@@ -25,41 +27,111 @@ abstract class Handler {
25
  */
26
  private $bIsReady;
27
 
28
- public function __construct() {
 
 
 
 
 
 
 
 
 
 
 
29
  }
30
 
31
- public function autoCleanDb() {
 
 
 
 
32
  }
33
 
34
  /**
35
- * @param int $nAutoExpireDays
36
  * @return $this
 
37
  */
38
- public function tableCleanExpired( $nAutoExpireDays ) {
39
- $nAutoExpire = $nAutoExpireDays*DAY_IN_SECONDS;
40
- if ( $nAutoExpire > 0 ) {
41
- $this->deleteRowsOlderThan( Services::Request()->ts() - $nAutoExpire );
 
 
 
 
 
 
 
 
42
  }
43
  return $this;
44
  }
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  /**
47
- * @param int $nTimeStamp
48
  * @return bool
49
  */
50
- public function deleteRowsOlderThan( $nTimeStamp ) {
51
- return $this->isReady() && $this->getQueryDeleter()
52
- ->addWhereOlderThan( $nTimeStamp )
53
- ->query();
 
54
  }
55
 
56
  public function getTable() :string {
57
  return Services::WpDb()->getPrefix()
58
- .esc_sql( $this->getCon()->prefixOption( $this->getTableSlug() ) );
59
  }
60
 
61
  /**
62
  * @return string
 
63
  */
64
  protected function getTableSlug() {
65
  return empty( $this->sTable ) ? $this->getDefaultTableName() : $this->sTable;
@@ -69,19 +141,27 @@ abstract class Handler {
69
  * @return Insert|mixed
70
  */
71
  public function getQueryInserter() {
72
- $sClass = $this->getNamespace().'Insert';
73
  /** @var Insert $o */
74
- $o = new $sClass();
75
  return $o->setDbH( $this );
76
  }
77
 
 
 
 
 
 
 
 
 
78
  /**
79
  * @return Delete|mixed
80
  */
81
  public function getQueryDeleter() {
82
- $sClass = $this->getNamespace().'Delete';
83
  /** @var Delete $o */
84
- $o = new $sClass();
85
  return $o->setDbH( $this );
86
  }
87
 
@@ -89,9 +169,9 @@ abstract class Handler {
89
  * @return Select|mixed
90
  */
91
  public function getQuerySelector() {
92
- $sClass = $this->getNamespace().'Select';
93
  /** @var Select $o */
94
- $o = new $sClass();
95
  return $o->setDbH( $this )
96
  ->setResultsAsVo( true );
97
  }
@@ -100,9 +180,9 @@ abstract class Handler {
100
  * @return Update|mixed
101
  */
102
  public function getQueryUpdater() {
103
- $sClass = $this->getNamespace().'Update';
104
  /** @var Update $o */
105
- $o = new $sClass();
106
  return $o->setDbH( $this );
107
  }
108
 
@@ -110,10 +190,15 @@ abstract class Handler {
110
  * @return EntryVO|mixed
111
  */
112
  public function getVo() {
113
- $sClass = $this->getNamespace().'EntryVO';
114
- return new $sClass();
115
  }
116
 
 
 
 
 
 
117
  public function hasColumn( string $col ) :bool {
118
  return in_array( strtolower( $col ), $this->getTableSchema()->getColumnNames() );
119
  }
@@ -140,33 +225,13 @@ abstract class Handler {
140
  return $mResult !== false;
141
  }
142
 
143
- /**
144
- * @return bool
145
- */
146
- public function tableExists() {
147
  return Services::WpDb()->getIfTableExists( $this->getTable() );
148
  }
149
 
150
- /**
151
- * @return $this
152
- * @throws \Exception
153
- */
154
- public function tableInit() {
155
- if ( !$this->isReady() ) {
156
-
157
- $this->tableCreate();
158
-
159
- if ( !$this->isReady( true ) ) {
160
- $this->tableDelete();
161
- $this->tableCreate();
162
- }
163
- }
164
- return $this;
165
- }
166
-
167
- public function tableTrimExcess( int $nRowsLimit ) :self {
168
  try {
169
- $this->getQueryDeleter()->deleteExcess( $nRowsLimit );
170
  }
171
  catch ( \Exception $e ) {
172
  }
@@ -174,11 +239,11 @@ abstract class Handler {
174
  }
175
 
176
  /**
177
- * @param bool $bReTest
178
  * @return bool
179
  */
180
- public function isReady( $bReTest = false ) {
181
- if ( $bReTest ) {
182
  $this->reset();
183
  }
184
 
@@ -196,17 +261,13 @@ abstract class Handler {
196
  return $this->bIsReady;
197
  }
198
 
199
- public function setTable( string $sTable ) :self {
200
- $this->sTable = $sTable;
201
- return $this;
202
- }
203
-
204
  protected function getDefaultTableName() :string {
205
- throw new \Exception( 'No table name' );
206
  }
207
 
208
  /**
209
  * @return string[]
 
210
  */
211
  protected function getCustomColumns() :array {
212
  return [];
@@ -214,17 +275,22 @@ abstract class Handler {
214
 
215
  /**
216
  * @return string[]
 
217
  */
218
  protected function getTimestampColumns() :array {
219
  return [];
220
  }
221
 
222
  public function getTableSchema() :TableSchema {
223
- $sch = new TableSchema();
224
- $sch->table = $this->getTable();
225
- $sch->cols_custom = $this->getCustomColumns();
226
- $sch->cols_timestamps = $this->getTimestampColumns();
227
- return $sch;
 
 
 
 
228
  }
229
 
230
  private function getNamespace() :string {
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Common\AlignTableWithSchema;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Common\TableSchema;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
15
  abstract class Handler {
16
 
17
  use ModConsumer;
18
+ use ExecOnce;
19
 
20
  /**
21
  * @var string
27
  */
28
  private $bIsReady;
29
 
30
+ /**
31
+ * @var string
32
+ */
33
+ protected $slug;
34
+
35
+ /**
36
+ * @var TableSchema
37
+ */
38
+ protected $schema;
39
+
40
+ public function __construct( $slug = '' ) {
41
+ $this->slug = $slug;
42
  }
43
 
44
+ /**
45
+ * @throws \Exception
46
+ */
47
+ protected function run() {
48
+ $this->tableInit();
49
  }
50
 
51
  /**
 
52
  * @return $this
53
+ * @throws \Exception
54
  */
55
+ public function tableInit() {
56
+
57
+ $this->setupTableSchema();
58
+
59
+ if ( !$this->isReady() ) {
60
+
61
+ $this->tableCreate();
62
+
63
+ if ( !$this->isReady( true ) ) {
64
+ $this->tableDelete();
65
+ $this->tableCreate();
66
+ }
67
  }
68
  return $this;
69
  }
70
 
71
+ private function setupTableSchema() :TableSchema {
72
+ $this->schema = new TableSchema();
73
+
74
+ $spec = $this->getOptions()->getDef( 'db_table_'.$this->slug );
75
+
76
+ if ( empty( $spec ) ) {
77
+ $this->schema->slug = $this->slug;
78
+ $this->schema->primary_key = 'id';
79
+ $this->schema->cols_custom = $this->getCustomColumns();
80
+ $this->schema->cols_timestamps = $this->getTimestampColumns();
81
+ $this->schema->autoexpire = 0;
82
+ }
83
+ else {
84
+ $this->schema->applyFromArray( array_merge(
85
+ [
86
+ 'slug' => $this->slug,
87
+ 'primary_key' => 'id',
88
+ 'cols_custom' => [],
89
+ 'cols_timestamps' => [],
90
+ 'has_updated_at' => false,
91
+ 'col_older_than' => 'created_at',
92
+ 'autoexpire' => 0,
93
+ 'has_ip_col' => false,
94
+ ],
95
+ $spec
96
+ ) );
97
+ }
98
+
99
+ $this->schema->table = $this->getTable();
100
+ return $this->schema;
101
+ }
102
+
103
+ public function autoCleanDb() {
104
+ }
105
+
106
+ public function tableCleanExpired( int $autoExpireDays ) {
107
+ if ( $autoExpireDays > 0 ) {
108
+ $this->deleteRowsOlderThan( Services::Request()->ts() - $autoExpireDays*DAY_IN_SECONDS );
109
+ }
110
+ }
111
+
112
+ protected function getColumnForOlderThanComparison() :string {
113
+ return 'created_at';
114
+ }
115
+
116
  /**
117
+ * @param int $timestamp
118
  * @return bool
119
  */
120
+ public function deleteRowsOlderThan( $timestamp ) :bool {
121
+ return $this->isReady() &&
122
+ $this->getQueryDeleter()
123
+ ->addWhereOlderThan( $timestamp, $this->getTableSchema()->col_older_than ?? 'created_at' )
124
+ ->query();
125
  }
126
 
127
  public function getTable() :string {
128
  return Services::WpDb()->getPrefix()
129
+ .esc_sql( $this->getCon()->prefixOption( $this->getDefaultTableName() ) );
130
  }
131
 
132
  /**
133
  * @return string
134
+ * @deprecated 10.3
135
  */
136
  protected function getTableSlug() {
137
  return empty( $this->sTable ) ? $this->getDefaultTableName() : $this->sTable;
141
  * @return Insert|mixed
142
  */
143
  public function getQueryInserter() {
144
+ $class = $this->getNamespace().'Insert';
145
  /** @var Insert $o */
146
+ $o = new $class();
147
  return $o->setDbH( $this );
148
  }
149
 
150
+ /**
151
+ * @return Iterator
152
+ */
153
+ public function getIterator() {
154
+ $o = new Iterator();
155
+ return $o->setDbHandler( $this );
156
+ }
157
+
158
  /**
159
  * @return Delete|mixed
160
  */
161
  public function getQueryDeleter() {
162
+ $class = $this->getNamespace().'Delete';
163
  /** @var Delete $o */
164
+ $o = new $class();
165
  return $o->setDbH( $this );
166
  }
167
 
169
  * @return Select|mixed
170
  */
171
  public function getQuerySelector() {
172
+ $class = $this->getNamespace().'Select';
173
  /** @var Select $o */
174
+ $o = new $class();
175
  return $o->setDbH( $this )
176
  ->setResultsAsVo( true );
177
  }
180
  * @return Update|mixed
181
  */
182
  public function getQueryUpdater() {
183
+ $class = $this->getNamespace().'Update';
184
  /** @var Update $o */
185
+ $o = new $class();
186
  return $o->setDbH( $this );
187
  }
188
 
190
  * @return EntryVO|mixed
191
  */
192
  public function getVo() {
193
+ $class = $this->getNamespace().'EntryVO';
194
+ return new $class();
195
  }
196
 
197
+ /**
198
+ * @param string $col
199
+ * @return bool
200
+ * @deprecated 10.3 - moved to schema
201
+ */
202
  public function hasColumn( string $col ) :bool {
203
  return in_array( strtolower( $col ), $this->getTableSchema()->getColumnNames() );
204
  }
225
  return $mResult !== false;
226
  }
227
 
228
+ public function tableExists() :bool {
 
 
 
229
  return Services::WpDb()->getIfTableExists( $this->getTable() );
230
  }
231
 
232
+ public function tableTrimExcess( int $rowsLimit ) :self {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  try {
234
+ $this->getQueryDeleter()->deleteExcess( $rowsLimit );
235
  }
236
  catch ( \Exception $e ) {
237
  }
239
  }
240
 
241
  /**
242
+ * @param bool $reTest
243
  * @return bool
244
  */
245
+ public function isReady( bool $reTest = false ) {
246
+ if ( $reTest ) {
247
  $this->reset();
248
  }
249
 
261
  return $this->bIsReady;
262
  }
263
 
 
 
 
 
 
264
  protected function getDefaultTableName() :string {
265
+ return $this->getTableSchema()->slug;
266
  }
267
 
268
  /**
269
  * @return string[]
270
+ * @deprecated 10.3
271
  */
272
  protected function getCustomColumns() :array {
273
  return [];
275
 
276
  /**
277
  * @return string[]
278
+ * @deprecated 10.3
279
  */
280
  protected function getTimestampColumns() :array {
281
  return [];
282
  }
283
 
284
  public function getTableSchema() :TableSchema {
285
+ if ( empty( $this->schema ) ) { // TODO: Delete empty test after 10.3
286
+ $sch = new TableSchema();
287
+ $sch->table = $this->getTable();
288
+ $sch->col_older_than = 'created_at';
289
+ $sch->cols_custom = $this->getCustomColumns();
290
+ $sch->cols_timestamps = $this->getTimestampColumns();
291
+ return $sch;
292
+ }
293
+ return $this->schema;
294
  }
295
 
296
  private function getNamespace() :string {
src/lib/src/Databases/Base/Insert.php CHANGED
@@ -12,20 +12,20 @@ class Insert extends BaseQuery {
12
  protected $aInsertData;
13
 
14
  public function getInsertData() :array {
15
- $dbh = $this->getDbH();
16
- $cols = $dbh->getTableSchema()->getColumnNames();
17
  return array_intersect_key(
18
  is_array( $this->aInsertData ) ? $this->aInsertData : [],
19
- array_flip( $cols )
20
  );
21
  }
22
 
23
  /**
24
- * @param EntryVO $oEntry
25
  * @return bool
26
  */
27
- public function insert( $oEntry ) :bool {
28
- return $this->setInsertData( $oEntry->getRawDataAsArray() )->query() === 1;
 
 
29
  }
30
 
31
  /**
@@ -34,12 +34,8 @@ class Insert extends BaseQuery {
34
  * @return $this
35
  */
36
  protected function setInsertData( $data ) {
37
- if ( !is_array( $data ) ) {
38
- $data = [];
39
- }
40
-
41
  $this->aInsertData = array_intersect_key(
42
- $data,
43
  array_flip( $this->getDbH()->getTableSchema()->getColumnNames() )
44
  );
45
  return $this;
@@ -50,20 +46,11 @@ class Insert extends BaseQuery {
50
  * @throws \Exception
51
  */
52
  protected function verifyInsertData() {
53
- $aData = $this->getInsertData();
54
-
55
- if ( !is_array( $aData ) ) {
56
- $aData = [];
57
  }
58
- $aData = array_merge(
59
- [ 'created_at' => Services::Request()->ts(), ],
60
- $aData
61
- );
62
- if ( !isset( $aData[ 'updated_at' ] ) && $this->getDbH()->hasColumn( 'updated_at' ) ) {
63
- $aData[ 'updated_at' ] = Services::Request()->ts();
64
- }
65
-
66
- return $this->setInsertData( $aData );
67
  }
68
 
69
  /**
12
  protected $aInsertData;
13
 
14
  public function getInsertData() :array {
 
 
15
  return array_intersect_key(
16
  is_array( $this->aInsertData ) ? $this->aInsertData : [],
17
+ array_flip( $this->getDbH()->getTableSchema()->getColumnNames() )
18
  );
19
  }
20
 
21
  /**
22
+ * @param EntryVO $entry
23
  * @return bool
24
  */
25
+ public function insert( $entry ) :bool {
26
+ // @deprecated 10.3- get rid of casting after moving filelockerVO to normal VO
27
+ $data = (array)$entry->getRawData();
28
+ return $this->setInsertData( $data )->query() === 1;
29
  }
30
 
31
  /**
34
  * @return $this
35
  */
36
  protected function setInsertData( $data ) {
 
 
 
 
37
  $this->aInsertData = array_intersect_key(
38
+ is_array( $data ) ? $data : [],
39
  array_flip( $this->getDbH()->getTableSchema()->getColumnNames() )
40
  );
41
  return $this;
46
  * @throws \Exception
47
  */
48
  protected function verifyInsertData() {
49
+ $baseData = [ 'created_at' => Services::Request()->ts() ];
50
+ if ( $this->getDbH()->hasColumn( 'updated_at' ) ) {
51
+ $baseData[ 'updated_at' ] = Services::Request()->ts();
 
52
  }
53
+ return $this->setInsertData( array_merge( $baseData, $this->getInsertData() ) );
 
 
 
 
 
 
 
 
54
  }
55
 
56
  /**
src/lib/src/Databases/Base/Iterator.php ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
4
+
5
+ use Elliotchance\Iterator\AbstractPagedIterator;
6
+
7
+ class Iterator extends AbstractPagedIterator {
8
+
9
+ const PAGE_LIMIT = 50;
10
+ use HandlerConsumer;
11
+
12
+ /**
13
+ * @var Select|mixed
14
+ */
15
+ private $selector;
16
+
17
+ /**
18
+ * @var int
19
+ */
20
+ private $totalSize;
21
+
22
+ /**
23
+ * @var array
24
+ */
25
+ private $customFilters = [];
26
+
27
+ /**
28
+ * @return EntryVO|mixed|null
29
+ */
30
+ public function current() {
31
+ return parent::current();
32
+ }
33
+
34
+ public function getCustomQueryFilters() :array {
35
+ return is_array( $this->customFilters ) ? $this->customFilters : [];
36
+ }
37
+
38
+ protected function getDefaultQueryFilters() :array {
39
+ return [
40
+ 'orderby' => 'id',
41
+ 'order' => 'ASC',
42
+ ];
43
+ }
44
+
45
+ protected function getFinalQueryFilters() :array {
46
+ return array_merge( $this->getDefaultQueryFilters(), $this->getCustomQueryFilters() );
47
+ }
48
+
49
+ /**
50
+ * @param int $nPage - always starts at 0
51
+ * @return array
52
+ */
53
+ public function getPage( $nPage ) {
54
+ $aParams = $this->getFinalQueryFilters();
55
+
56
+ $this->getSelector()
57
+ ->setResultsAsVo( true )
58
+ ->setPage( $nPage + 1 ) // Pages start at 1, not zero.
59
+ ->setLimit( $this->getPageSize() )
60
+ ->setOrderBy( $aParams[ 'orderby' ], $aParams[ 'order' ] );
61
+
62
+ return $this->runQuery();
63
+ }
64
+
65
+ /**
66
+ * @return int
67
+ */
68
+ public function getPageSize() {
69
+ return static::PAGE_LIMIT;
70
+ }
71
+
72
+ /**
73
+ * @return Select|mixed
74
+ */
75
+ public function getSelector() {
76
+ if ( empty( $this->selector ) ) {
77
+ $this->selector = $this->getDbHandler()->getQuerySelector();
78
+ }
79
+ return $this->selector;
80
+ }
81
+
82
+ /**
83
+ * @return int
84
+ */
85
+ public function getTotalSize() {
86
+ if ( !isset( $this->totalSize ) ) {
87
+ $this->totalSize = $this->runQueryCount();
88
+ }
89
+ return $this->totalSize;
90
+ }
91
+
92
+ /**
93
+ * @return EntryVO[]|mixed[]
94
+ */
95
+ protected function runQuery() {
96
+ return ( clone $this->getSelector() )->query();
97
+ }
98
+
99
+ protected function runQueryCount() :int {
100
+ return (int)( clone $this->getSelector() )->count();
101
+ }
102
+
103
+ /**
104
+ * @param Select|mixed $selector
105
+ * @return $this
106
+ */
107
+ public function setSelector( $selector ) {
108
+ $this->selector = $selector;
109
+ return $this;
110
+ }
111
+ }
src/lib/src/Databases/Base/Traits/Select_IPTable.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Base\Traits;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Tool\IpListSort;
6
+
7
+ trait Select_IPTable {
8
+
9
+ /**
10
+ * @return string[]
11
+ */
12
+ public function getDistinctIps() :array {
13
+ $ips = $this->getDistinctForColumn( 'ip' );
14
+ if ( $this->getDbH()->getTableSchema()->is_ip_binary ) {
15
+ $ips = array_map(
16
+ function ( $binaryIP ) {
17
+ return inet_ntop( $binaryIP );
18
+ },
19
+ $ips
20
+ );
21
+ }
22
+ return IpListSort::Sort( $ips );
23
+ }
24
+ }
src/lib/src/Databases/Base/Update.php CHANGED
@@ -26,11 +26,11 @@ class Update extends Insert {
26
  }
27
 
28
  /**
29
- * @param array $aSetData
30
  * @return $this
31
  */
32
- public function setUpdateData( $aSetData ) {
33
- return $this->setInsertData( $aSetData );
34
  }
35
 
36
  /**
@@ -52,44 +52,52 @@ class Update extends Insert {
52
  }
53
 
54
  /**
55
- * @param EntryVO $oEntry
56
- * @param array $aUpdateData
57
  * @return bool
58
  */
59
- public function updateEntry( $oEntry, $aUpdateData = [] ) {
60
  $success = false;
61
 
62
- if ( $oEntry instanceof EntryVO ) {
63
- if ( empty( $aUpdateData ) ) {
64
- $aUpdateData = $oEntry->getRawDataAsArray();
 
 
 
 
 
 
 
65
  }
66
- $success = $this->updateById( $oEntry->id, $aUpdateData );
67
- // TODO: run through update data and determine if anything actually needs updating
68
- if ( $success ) {
69
- foreach ( $aUpdateData as $col => $mVal ) {
70
- $oEntry->{$col} = $mVal;
 
 
71
  }
72
  }
73
  }
 
74
  return $success;
75
  }
76
 
77
  /**
78
- * @param int $nId
79
- * @param array $aUpdateData
80
  * @return bool true is success or no update necessary
81
  */
82
- public function updateById( $nId, $aUpdateData = [] ) {
83
- $bSuccess = true;
84
-
85
- if ( !empty( $aUpdateData ) ) {
86
- $mResult = $this
87
- ->setUpdateId( $nId )
88
- ->setUpdateData( $aUpdateData )
89
- ->query();
90
- $bSuccess = $mResult === 1;
91
  }
92
- return $bSuccess;
93
  }
94
 
95
  /**
26
  }
27
 
28
  /**
29
+ * @param array $data
30
  * @return $this
31
  */
32
+ public function setUpdateData( $data ) {
33
+ return $this->setInsertData( $data );
34
  }
35
 
36
  /**
52
  }
53
 
54
  /**
55
+ * @param EntryVO $entry
56
+ * @param array $updateData
57
  * @return bool
58
  */
59
+ public function updateEntry( $entry, $updateData = [] ) :bool {
60
  $success = false;
61
 
62
+ if ( $entry instanceof EntryVO ) {
63
+
64
+ foreach ( (array)$entry->getRawData() as $key => $value ) {
65
+ if ( isset( $updateData[ $key ] ) && $updateData[ $key ] === $value ) {
66
+ unset( $updateData[ $key ] );
67
+ }
68
+ }
69
+
70
+ if ( empty( $updateData ) ) {
71
+ $success = true;
72
  }
73
+ else {
74
+ if ( $this->getDbH()->hasColumn( 'updated_at' ) && !isset( $updateData[ 'updated_at' ] ) ) {
75
+ $updateData[ 'updated_at' ] = Services::Request()->ts();
76
+ }
77
+ if ( $this->updateById( $entry->id, $updateData ) ) {
78
+ $entry->applyFromArray( array_merge( (array)$entry->getRawData(), $updateData ) );
79
+ $success = true;
80
  }
81
  }
82
  }
83
+
84
  return $success;
85
  }
86
 
87
  /**
88
+ * @param int $id
89
+ * @param array $updateData
90
  * @return bool true is success or no update necessary
91
  */
92
+ public function updateById( $id, $updateData = [] ) {
93
+ $success = true;
94
+
95
+ if ( !empty( $updateData ) ) {
96
+ $success = $this->setUpdateId( $id )
97
+ ->setUpdateData( $updateData )
98
+ ->query() === 1;
 
 
99
  }
100
+ return $success;
101
  }
102
 
103
  /**
src/lib/src/Databases/BotSignals/Common.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\BotSignals;
4
+
5
+ trait Common {
6
+
7
+ /**
8
+ * Will test whether the Binary IP can be converted back before applying filter.
9
+ * @param mixed $binaryIp - IP has already been converted using inet_pton
10
+ * @return $this
11
+ */
12
+ public function filterByIP( $binaryIp ) {
13
+ if ( inet_ntop( $binaryIp ) !== false ) {
14
+ $this->addWhereEquals( 'ip', $binaryIp );
15
+ }
16
+ return $this;
17
+ }
18
+
19
+ public function filterByIPHuman( string $ip ) {
20
+ return $this->filterByIP( inet_pton( $ip ) );
21
+ }
22
+
23
+ /**
24
+ * Will test whether the Binary IP can be converted back before applying filter.
25
+ * @param mixed $bBinaryIp - IP has already been converted using inet_pton
26
+ * @return $this
27
+ */
28
+ public function filterByNotIp( $bBinaryIp ) {
29
+ if ( inet_ntop( $bBinaryIp ) !== false ) {
30
+ $this->addWhere( 'ip', $bBinaryIp, '!=' );
31
+ }
32
+ return $this;
33
+ }
34
+ }
src/lib/src/Databases/BotSignals/Delete.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\BotSignals;
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/BotSignals/EntryVO.php ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\BotSignals;
4
+
5
+ /**
6
+ * Class EntryVO
7
+ * @property string $ip
8
+ * @property int $notbot_at
9
+ * @property int $frontpage_at
10
+ * @property int $bt404_at
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
17
+ * @property int $btxml_at
18
+ * @property int $cooldown_at
19
+ * @property int $auth_at
20
+ * @property int $offense_at
21
+ * @property int $blocked_at
22
+ * @property int $unblocked_at
23
+ * @property int $bypass_at
24
+ * @property int $humanspam_at
25
+ * @property int $markspam_at
26
+ * @property int $unmarkspam_at
27
+ * @property int $captchapass_at
28
+ * @property int $captchafail_at
29
+ * @property int $ratelimit_at
30
+ * @property int $updated_at
31
+ */
32
+ class EntryVO extends \FernleafSystems\Wordpress\Plugin\Shield\Databases\Base\EntryVO {
33
+
34
+ /**
35
+ * @inheritDoc
36
+ */
37
+ public function __get( string $key ) {
38
+ switch ( $key ) {
39
+
40
+ case 'ip':
41
+ $value = inet_ntop( parent::__get( $key ) );
42
+ break;
43
+
44
+ default:
45
+ $value = parent::__get( $key );
46
+ break;
47
+ }
48
+ return $value;
49
+ }
50
+
51
+ /**
52
+ * @inheritDoc
53
+ */
54
+ public function __set( string $key, $value ) {
55
+ switch ( $key ) {
56
+
57
+ case 'ip':
58
+ $value = inet_pton( $value );
59
+ break;
60
+
61
+ default:
62
+ break;
63
+ }
64
+
65
+ parent::__set( $key, $value );
66
+ }
67
+ }
src/lib/src/Databases/BotSignals/Handler.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\BotSignals;
4
+
5
+ class Handler extends \FernleafSystems\Wordpress\Plugin\Shield\Databases\Base\Handler {
6
+
7
+ public function autoCleanDb() {
8
+ $this->tableCleanExpired( (int)$this->getTableSchema()->autoexpire );
9
+ }
10
+ }
src/lib/src/Databases/BotSignals/Insert.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\BotSignals;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
+
7
+ class Insert extends Base\Insert {
8
+
9
+ }
src/lib/src/Databases/BotSignals/Select.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\BotSignals;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
+
7
+ class Select extends Base\Select {
8
+
9
+ use Common;
10
+ use Base\Traits\Select_IPTable;
11
+
12
+ /**
13
+ * @param string $ip
14
+ * @return EntryVO
15
+ */
16
+ public function byIp( string $ip ) {
17
+ return $this->filterByIP( inet_pton( $ip ) )
18
+ ->setResultsAsVo( true )
19
+ ->first();
20
+ }
21
+ }
src/lib/src/Databases/BotSignals/Update.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\BotSignals;
4
+
5
+ class Update extends \FernleafSystems\Wordpress\Plugin\Shield\Databases\Base\Update {
6
+
7
+ }
src/lib/src/Databases/ChangeTracking/EntryVO.php CHANGED
@@ -6,7 +6,6 @@ use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
 
7
  /**
8
  * Class EntryVO
9
- *
10
  * @property string ip
11
  * @property array data
12
  * @property array meta
@@ -14,14 +13,14 @@ use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
14
  class EntryVO extends Base\EntryVO {
15
 
16
  /**
17
- * @param string $sProperty
18
  * @return mixed
19
  */
20
- public function __get( $sProperty ) {
21
 
22
- $mVal = parent::__get( $sProperty );
23
 
24
- switch ( $sProperty ) {
25
 
26
  case 'data':
27
  $mVal = json_decode( \WP_Http_Encoding::decompress( $mVal ), true );
@@ -35,22 +34,20 @@ class EntryVO extends Base\EntryVO {
35
  }
36
 
37
  /**
38
- * @param string $sProperty
39
- * @param mixed $mValue
40
- * @return $this|mixed
41
  */
42
- public function __set( $sProperty, $mValue ) {
43
 
44
- switch ( $sProperty ) {
45
 
46
  case 'data':
47
- $mValue = \WP_Http_Encoding::compress( json_encode( $mValue ) );
48
  break;
49
 
50
  default:
51
  break;
52
  }
53
 
54
- return parent::__set( $sProperty, $mValue );
55
  }
56
  }
6
 
7
  /**
8
  * Class EntryVO
 
9
  * @property string ip
10
  * @property array data
11
  * @property array meta
13
  class EntryVO extends Base\EntryVO {
14
 
15
  /**
16
+ * @param string $key
17
  * @return mixed
18
  */
19
+ public function __get(string $key ) {
20
 
21
+ $mVal = parent::__get( $key );
22
 
23
+ switch ( $key ) {
24
 
25
  case 'data':
26
  $mVal = json_decode( \WP_Http_Encoding::decompress( $mVal ), true );
34
  }
35
 
36
  /**
37
+ * @inheritDoc
 
 
38
  */
39
+ public function __set( string $key, $value ) {
40
 
41
+ switch ( $key ) {
42
 
43
  case 'data':
44
+ $value = \WP_Http_Encoding::compress( json_encode( $value ) );
45
  break;
46
 
47
  default:
48
  break;
49
  }
50
 
51
+ parent::__set( $key, $value );
52
  }
53
  }
src/lib/src/Databases/Common/TableSchema.php CHANGED
@@ -2,22 +2,42 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Common;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  /**
9
- * Class TableBuilder
10
- * @package FernleafSystems\Wordpress\Plugin\Shield\Databases\Base
 
11
  * @property string $table
12
  * @property string $primary_key
13
  * @property string[] $cols_ids
14
  * @property string[] $cols_custom
15
  * @property string[] $cols_timestamps
 
 
 
 
 
16
  */
17
- class TableSchema {
18
 
19
  const PRIMARY_KEY = 'id';
20
- use StdClassAdapter;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  public function buildCreate() :string {
23
  $cols = [];
@@ -50,7 +70,7 @@ class TableSchema {
50
  return array_merge(
51
  $this->getColumn_ID(),
52
  $this->cols_custom ?? [],
53
- $this->getColumnns_Timestamps()
54
  );
55
  }
56
 
@@ -66,17 +86,56 @@ class TableSchema {
66
  /**
67
  * @return string[]
68
  */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  protected function getColumnns_Timestamps() :array {
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  return array_map(
71
  function ( $comment ) {
72
  return $this->getTimestampColDef( $comment );
73
  },
74
  array_merge(
75
  $this->cols_timestamps ?? [],
76
- [
77
- 'created_at' => 'Created At',
78
- 'deleted_at' => 'Soft Deleted At',
79
- ]
80
  )
81
  );
82
  }
@@ -92,4 +151,8 @@ class TableSchema {
92
  protected function getPrimaryKeyColumnName() :string {
93
  return $this->primary_key ?? static::PRIMARY_KEY;
94
  }
95
- }
 
 
 
 
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Common;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  /**
9
+ * Class TableSchema
10
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Databases\Common
11
+ * @property string $slug
12
  * @property string $table
13
  * @property string $primary_key
14
  * @property string[] $cols_ids
15
  * @property string[] $cols_custom
16
  * @property string[] $cols_timestamps
17
+ * @property string $col_older_than
18
+ * @property bool $has_updated_at
19
+ * @property int $autoexpire
20
+ * @property bool $has_ip_col
21
+ * @property bool $is_ip_binary
22
  */
23
+ class TableSchema extends DynPropertiesClass {
24
 
25
  const PRIMARY_KEY = 'id';
26
+
27
+ public function __get( $key ) {
28
+ switch ( $key ) {
29
+ case 'has_ip_col':
30
+ $val = array_key_exists( 'ip', $this->enumerateColumns() );
31
+ break;
32
+ case 'is_ip_binary':
33
+ $val = $this->has_ip_col && ( stripos( $this->cols_custom[ 'ip' ], 'varbinary' ) !== false );
34
+ break;
35
+ default:
36
+ $val = parent::__get( $key );
37
+ break;
38
+ }
39
+ return $val;
40
+ }
41
 
42
  public function buildCreate() :string {
43
  $cols = [];
70
  return array_merge(
71
  $this->getColumn_ID(),
72
  $this->cols_custom ?? [],
73
+ method_exists( $this, 'getColumns_Timestamps' ) ? $this->getColumns_Timestamps() : $this->getColumnns_Timestamps()
74
  );
75
  }
76
 
86
  /**
87
  * @return string[]
88
  */
89
+ protected function getColumns_Timestamps() :array {
90
+
91
+ $standardTsCols = [
92
+ 'created_at' => 'Created At',
93
+ 'deleted_at' => 'Soft Deleted At',
94
+ ];
95
+
96
+ if ( $this->has_updated_at && !array_key_exists( 'updated_at', $this->cols_timestamps ) ) {
97
+ $standardTsCols = array_merge(
98
+ [ 'updated_at' => 'Updated At', ],
99
+ $standardTsCols
100
+ );
101
+ }
102
+
103
+ return array_map(
104
+ function ( $comment ) {
105
+ return $this->getTimestampColDef( $comment );
106
+ },
107
+ array_merge(
108
+ $this->cols_timestamps ?? [],
109
+ $standardTsCols
110
+ )
111
+ );
112
+ }
113
+
114
+ /**
115
+ * @return string[]
116
+ * @deprecated 10.3
117
+ */
118
  protected function getColumnns_Timestamps() :array {
119
+
120
+ $standardTsCols = [
121
+ 'created_at' => 'Created At',
122
+ 'deleted_at' => 'Soft Deleted At',
123
+ ];
124
+
125
+ if ( $this->has_updated_at && !array_key_exists( 'updated_at', $this->cols_timestamps ) ) {
126
+ $standardTsCols = array_merge(
127
+ [ 'updated_at' => 'Updated At', ],
128
+ $standardTsCols
129
+ );
130
+ }
131
+
132
  return array_map(
133
  function ( $comment ) {
134
  return $this->getTimestampColDef( $comment );
135
  },
136
  array_merge(
137
  $this->cols_timestamps ?? [],
138
+ $standardTsCols
 
 
 
139
  )
140
  );
141
  }
151
  protected function getPrimaryKeyColumnName() :string {
152
  return $this->primary_key ?? static::PRIMARY_KEY;
153
  }
154
+
155
+ public function hasColumn( string $col ) :bool {
156
+ return in_array( strtolower( $col ), $this->getColumnNames() );
157
+ }
158
+ }
src/lib/src/Databases/Events/Select.php CHANGED
@@ -17,11 +17,11 @@ class Select extends Base\Select {
17
  }
18
 
19
  /**
20
- * @param string[] $aEvents
21
  * @return int
22
  */
23
- public function sumEvents( $aEvents ) {
24
- return (int)$this->filterByEvents( $aEvents )
25
  ->setColumnsToSelect( [ 'count' ] )
26
  ->sum();
27
  }
17
  }
18
 
19
  /**
20
+ * @param string[] $events
21
  * @return int
22
  */
23
+ public function sumEvents( array $events ) :int {
24
+ return (int)$this->filterByEvents( $events )
25
  ->setColumnsToSelect( [ 'count' ] )
26
  ->sum();
27
  }
src/lib/src/Databases/Events/Update.php CHANGED
@@ -1,9 +1,7 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Events;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
-
7
- class Update extends Base\Update {
8
 
9
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Events;
4
 
5
+ class Update extends \FernleafSystems\Wordpress\Plugin\Shield\Databases\Base\Update {
 
 
6
 
7
  }
src/lib/src/Databases/FileLocker/EntryVO.php CHANGED
@@ -19,44 +19,35 @@ use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
19
  class EntryVO extends Base\EntryVO {
20
 
21
  /**
22
- * @param string $sProperty
23
- * @return mixed
24
  */
25
- public function __get( $sProperty ) {
26
-
27
- $mValue = parent::__get( $sProperty );
28
-
29
- switch ( $sProperty ) {
30
-
31
  case 'content':
32
  case 'file':
33
- $mValue = base64_decode( $mValue );
34
  break;
35
 
36
  default:
37
  break;
38
  }
39
- return $mValue;
40
  }
41
 
42
  /**
43
- * @param string $sProperty
44
- * @param mixed $mValue
45
- * @return $this
46
  */
47
- public function __set( $sProperty, $mValue ) {
48
-
49
- switch ( $sProperty ) {
50
-
51
  case 'content':
52
  case 'file':
53
- $mValue = base64_encode( $mValue );
54
  break;
55
 
56
  default:
57
  break;
58
  }
59
-
60
- return parent::__set( $sProperty, $mValue );
61
  }
62
  }
19
  class EntryVO extends Base\EntryVO {
20
 
21
  /**
22
+ * @inheritDoc
 
23
  */
24
+ public function __get( string $key ) {
25
+ $value = parent::__get( $key );
26
+ switch ( $key ) {
 
 
 
27
  case 'content':
28
  case 'file':
29
+ $value = base64_decode( $value );
30
  break;
31
 
32
  default:
33
  break;
34
  }
35
+ return $value;
36
  }
37
 
38
  /**
39
+ * @inheritDoc
 
 
40
  */
41
+ public function __set( string $key, $value ) {
42
+ switch ( $key ) {
 
 
43
  case 'content':
44
  case 'file':
45
+ $value = base64_encode( $value);
46
  break;
47
 
48
  default:
49
  break;
50
  }
51
+ parent::__set( $key, $value );
 
52
  }
53
  }
src/lib/src/Databases/FileLocker/Insert.php CHANGED
@@ -1,9 +1,7 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\FileLocker;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
-
7
- class Insert extends Base\Insert {
8
 
9
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\FileLocker;
4
 
5
+ class Insert extends \FernleafSystems\Wordpress\Plugin\Shield\Databases\Base\Insert {
 
 
6
 
7
  }
src/lib/src/Databases/FileLocker/Update.php CHANGED
@@ -8,47 +8,46 @@ use FernleafSystems\Wordpress\Services\Services;
8
  class Update extends Base\Update {
9
 
10
  /**
11
- * @param EntryVO $oEntry
12
  * @return bool
13
  */
14
- public function markNotified( EntryVO $oEntry ) {
15
- return $this->updateEntry( $oEntry, [
16
  'notified_at' => Services::Request()->ts()
17
  ] );
18
  }
19
 
20
  /**
21
- * @param EntryVO $oEntry
22
  * @return bool
23
  */
24
- public function markProblem( EntryVO $oEntry ) {
25
- return $this->updateEntry( $oEntry, [
26
  'detected_at' => Services::Request()->ts(),
27
  'notified_at' => 0
28
  ] );
29
  }
30
 
31
  /**
32
- * @param EntryVO $oEntry
33
  * @return bool
34
  */
35
- public function markReverted( EntryVO $oEntry ) {
36
- return $this->updateEntry( $oEntry, [
37
  'reverted_at' => Services::Request()->ts()
38
  ] );
39
  }
40
 
41
  /**
42
- * @param EntryVO $oEntry
43
- * @param string $sHash
44
  * @return bool
45
  */
46
- public function updateCurrentHash( EntryVO $oEntry, $sHash = '' ) {
47
- return $this->updateEntry( $oEntry, [
48
- 'hash_current' => $sHash,
49
- 'detected_at' => empty( $sHash ) ? 0 : Services::Request()->ts(),
50
  'notified_at' => 0,
51
- 'updated_at' => Services::Request()->ts(),
52
  ] );
53
  }
54
  }
8
  class Update extends Base\Update {
9
 
10
  /**
11
+ * @param EntryVO $entry
12
  * @return bool
13
  */
14
+ public function markNotified( EntryVO $entry ) {
15
+ return $this->updateEntry( $entry, [
16
  'notified_at' => Services::Request()->ts()
17
  ] );
18
  }
19
 
20
  /**
21
+ * @param EntryVO $entry
22
  * @return bool
23
  */
24
+ public function markProblem( EntryVO $entry ) {
25
+ return $this->updateEntry( $entry, [
26
  'detected_at' => Services::Request()->ts(),
27
  'notified_at' => 0
28
  ] );
29
  }
30
 
31
  /**
32
+ * @param EntryVO $entry
33
  * @return bool
34
  */
35
+ public function markReverted( EntryVO $entry ) {
36
+ return $this->updateEntry( $entry, [
37
  'reverted_at' => Services::Request()->ts()
38
  ] );
39
  }
40
 
41
  /**
42
+ * @param EntryVO $entry
43
+ * @param string $hash
44
  * @return bool
45
  */
46
+ public function updateCurrentHash( EntryVO $entry, $hash = '' ) {
47
+ return $this->updateEntry( $entry, [
48
+ 'hash_current' => $hash,
49
+ 'detected_at' => empty( $hash ) ? 0 : Services::Request()->ts(),
50
  'notified_at' => 0,
 
51
  ] );
52
  }
53
  }
src/lib/src/Databases/GeoIp/EntryVO.php CHANGED
@@ -53,39 +53,36 @@ class EntryVO extends Base\EntryVO {
53
  }
54
 
55
  /**
56
- * @param string $sProperty
57
  * @return mixed
58
  */
59
- public function __get( $sProperty ) {
60
- switch ( $sProperty ) {
61
-
62
  case 'ip':
63
- $mVal = inet_ntop( parent::__get( $sProperty ) );
64
  break;
65
 
66
  default:
67
- $mVal = parent::__get( $sProperty );
68
  }
69
- return $mVal;
70
  }
71
 
72
  /**
73
- * @param string $sProperty
74
- * @param mixed $mValue
75
- * @return $this
76
  */
77
- public function __set( $sProperty, $mValue ) {
78
 
79
- switch ( $sProperty ) {
80
 
81
  case 'ip':
82
- $mValue = inet_pton( $mValue );
83
  break;
84
 
85
  default:
86
  break;
87
  }
88
 
89
- return parent::__set( $sProperty, $mValue );
90
  }
91
  }
53
  }
54
 
55
  /**
56
+ * @param string $key
57
  * @return mixed
58
  */
59
+ public function __get( string $key ) {
60
+ switch ( $key ) {
 
61
  case 'ip':
62
+ $value = inet_ntop( parent::__get( $key ) );
63
  break;
64
 
65
  default:
66
+ $value = parent::__get( $key );
67
  }
68
+ return $value;
69
  }
70
 
71
  /**
72
+ * @inheritDoc
 
 
73
  */
74
+ public function __set( string $key, $value ) {
75
 
76
+ switch ( $key ) {
77
 
78
  case 'ip':
79
+ $value = inet_pton( $value );
80
  break;
81
 
82
  default:
83
  break;
84
  }
85
 
86
+ parent::__set( $key, $value );
87
  }
88
  }
src/lib/src/Databases/GeoIp/Handler.php CHANGED
@@ -2,19 +2,9 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\GeoIp;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
-
7
- class Handler extends Base\Handler {
8
 
9
  public function autoCleanDb() {
10
- $this->tableCleanExpired( $this->getOptions()->getDef( 'db_autoexpire_geoip' ) );
11
- }
12
-
13
- public function getCustomColumns() :array {
14
- return $this->getOptions()->getDef( 'geoip_table_columns' );
15
- }
16
-
17
- protected function getDefaultTableName() :string {
18
- return $this->getOptions()->getDef( 'geoip_table_name' );
19
  }
20
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\GeoIp;
4
 
5
+ class Handler extends \FernleafSystems\Wordpress\Plugin\Shield\Databases\Base\Handler {
 
 
6
 
7
  public function autoCleanDb() {
8
+ $this->tableCleanExpired( (int)$this->getTableSchema()->autoexpire );
 
 
 
 
 
 
 
 
9
  }
10
  }
src/lib/src/Databases/GeoIp/Select.php CHANGED
@@ -8,6 +8,7 @@ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Tool\IpListSort;
8
  class Select extends Base\Select {
9
 
10
  use BaseGeoIp;
 
11
 
12
  /**
13
  * @param string $sIp
@@ -18,16 +19,4 @@ class Select extends Base\Select {
18
  ->setResultsAsVo( true )
19
  ->first();
20
  }
21
-
22
- /**
23
- * @return string[]
24
- */
25
- public function getDistinctIps() {
26
- return IpListSort::Sort( array_map(
27
- function ( $sIp ) {
28
- return inet_ntop( $sIp );
29
- },
30
- $this->getDistinctForColumn( 'ip' )
31
- ) );
32
- }
33
  }
8
  class Select extends Base\Select {
9
 
10
  use BaseGeoIp;
11
+ use Base\Traits\Select_IPTable;
12
 
13
  /**
14
  * @param string $sIp
19
  ->setResultsAsVo( true )
20
  ->first();
21
  }
 
 
 
 
 
 
 
 
 
 
 
 
22
  }
src/lib/src/Databases/IPs/EntryVO.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\IPs;
4
 
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\IPs;
4
 
src/lib/src/Databases/IPs/Handler.php CHANGED
@@ -20,24 +20,36 @@ class Handler extends Base\Handler {
20
  }
21
 
22
  /**
23
- * @param int $nTimeStamp
24
  * @return bool
25
  */
26
- public function deleteRowsOlderThan( $nTimeStamp ) {
27
  return $this->getQueryDeleter()
28
- ->addWhereOlderThan( $nTimeStamp, 'last_access_at' )
29
  ->addWhere( 'list', ModCon::LIST_MANUAL_WHITE, '!=' )
30
  ->query();
31
  }
32
 
 
 
 
 
33
  protected function getCustomColumns() :array {
34
  return $this->getOptions()->getDef( 'ip_list_table_columns' );
35
  }
36
 
 
 
 
 
37
  protected function getDefaultTableName() :string {
38
  return $this->getOptions()->getDef( 'ip_lists_table_name' );
39
  }
40
 
 
 
 
 
41
  protected function getTimestampColumns() :array {
42
  return $this->getOptions()->getDef( 'ip_list_table_timestamp_columns' );
43
  }
20
  }
21
 
22
  /**
23
+ * @param int $timestamp
24
  * @return bool
25
  */
26
+ public function deleteRowsOlderThan( $timestamp ) :bool {
27
  return $this->getQueryDeleter()
28
+ ->addWhereOlderThan( $timestamp, 'last_access_at' )
29
  ->addWhere( 'list', ModCon::LIST_MANUAL_WHITE, '!=' )
30
  ->query();
31
  }
32
 
33
+ /**
34
+ * @return string[]
35
+ * @deprecated 10.3
36
+ */
37
  protected function getCustomColumns() :array {
38
  return $this->getOptions()->getDef( 'ip_list_table_columns' );
39
  }
40
 
41
+ /**
42
+ * @return string[]
43
+ * @deprecated 10.3
44
+ */
45
  protected function getDefaultTableName() :string {
46
  return $this->getOptions()->getDef( 'ip_lists_table_name' );
47
  }
48
 
49
+ /**
50
+ * @return string[]
51
+ * @deprecated 10.3
52
+ */
53
  protected function getTimestampColumns() :array {
54
  return $this->getOptions()->getDef( 'ip_list_table_timestamp_columns' );
55
  }
src/lib/src/Databases/IPs/Insert.php CHANGED
@@ -13,19 +13,19 @@ class Insert extends Base\Insert {
13
  */
14
  protected function verifyInsertData() {
15
  parent::verifyInsertData();
16
- $aData = $this->getInsertData();
17
 
18
- if ( !Services::IP()->isValidIpOrRange( $aData[ 'ip' ] ) ) {
19
  throw new \Exception( 'IP address provided is not valid' );
20
  }
21
- if ( empty( $aData[ 'list' ] ) ) {
22
  throw new \Exception( 'An IP address must be assigned to a list' );
23
  }
24
 
25
- if ( strpos( $aData[ 'ip' ], '/' ) !== false ) {
26
- $aData[ 'is_range' ] = true;
27
  }
28
 
29
- return $this->setInsertData( $aData );
30
  }
31
  }
13
  */
14
  protected function verifyInsertData() {
15
  parent::verifyInsertData();
16
+ $data = $this->getInsertData();
17
 
18
+ if ( !Services::IP()->isValidIpOrRange( $data[ 'ip' ] ) ) {
19
  throw new \Exception( 'IP address provided is not valid' );
20
  }
21
+ if ( empty( $data[ 'list' ] ) ) {
22
  throw new \Exception( 'An IP address must be assigned to a list' );
23
  }
24
 
25
+ if ( strpos( $data[ 'ip' ], '/' ) !== false ) {
26
+ $data[ 'is_range' ] = true;
27
  }
28
 
29
+ return $this->setInsertData( $data );
30
  }
31
  }
src/lib/src/Databases/IPs/Update.php CHANGED
@@ -18,43 +18,40 @@ class Update extends Base\Update {
18
  }
19
 
20
  /**
21
- * @param EntryVO $oIp
22
- * @param int $nTransCount
23
  * @return bool
24
  */
25
- public function updateTransgressions( $oIp, $nTransCount ) {
26
- return $this->updateEntry(
27
- $oIp,
28
- [
29
- 'transgressions' => max( 0, $nTransCount ),
30
- 'last_access_at' => Services::Request()->ts()
31
- ]
32
- );
33
  }
34
 
35
  /**
36
- * @param EntryVO $oIp
37
- * @param string $sLabel
38
  * @return bool
39
  */
40
- public function updateLabel( $oIp, $sLabel ) {
41
- return $this->updateEntry( $oIp, [ 'label' => trim( $sLabel ) ] );
42
  }
43
 
44
  /**
45
  * Also updates last access at
46
- * @param EntryVO $oIp
47
  * @return bool
48
  */
49
- public function updateLastAccessAt( $oIp ) {
50
- return $this->updateEntry( $oIp, [ 'last_access_at' => Services::Request()->ts() ] );
51
  }
52
 
53
  /**
54
- * @param EntryVO $oIp
55
  * @return bool
56
  */
57
- public function setBlocked( $oIp ) {
58
- return $this->updateEntry( $oIp, [ 'blocked_at' => Services::Request()->ts() ] );
59
  }
60
  }
18
  }
19
 
20
  /**
21
+ * @param EntryVO $IP
22
+ * @param int $offenseCount
23
  * @return bool
24
  */
25
+ public function updateTransgressions( $IP, $offenseCount ) {
26
+ return $this->updateEntry( $IP, [
27
+ 'transgressions' => max( 0, $offenseCount ),
28
+ 'last_access_at' => Services::Request()->ts()
29
+ ] );
 
 
 
30
  }
31
 
32
  /**
33
+ * @param EntryVO $IP
34
+ * @param string $label
35
  * @return bool
36
  */
37
+ public function updateLabel( $IP, $label ) {
38
+ return $this->updateEntry( $IP, [ 'label' => trim( $label ) ] );
39
  }
40
 
41
  /**
42
  * Also updates last access at
43
+ * @param EntryVO $IP
44
  * @return bool
45
  */
46
+ public function updateLastAccessAt( $IP ) {
47
+ return $this->updateEntry( $IP, [ 'last_access_at' => Services::Request()->ts() ] );
48
  }
49
 
50
  /**
51
+ * @param EntryVO $IP
52
  * @return bool
53
  */
54
+ public function setBlocked( $IP ) {
55
+ return $this->updateEntry( $IP, [ 'blocked_at' => Services::Request()->ts() ] );
56
  }
57
  }
src/lib/src/Databases/Reports/Handler.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Reports;
4
 
@@ -10,18 +10,6 @@ class Handler extends Base\Handler {
10
  const TYPE_INFO = 'nfo';
11
 
12
  public function autoCleanDb() {
13
- $this->tableCleanExpired( 30 );
14
- }
15
-
16
- protected function getCustomColumns() :array {
17
- return $this->getOptions()->getDef( 'reports_table_columns' );
18
- }
19
-
20
- protected function getDefaultTableName() :string {
21
- return $this->getOptions()->getDef( 'reports_table_name' );
22
- }
23
-
24
- protected function getTimestampColumns() :array {
25
- return $this->getOptions()->getDef( 'reports_table_timestamp_columns' );
26
  }
27
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Reports;
4
 
10
  const TYPE_INFO = 'nfo';
11
 
12
  public function autoCleanDb() {
13
+ $this->tableCleanExpired( $this->getTableSchema()->autoexpire );
 
 
 
 
 
 
 
 
 
 
 
 
14
  }
15
  }
src/lib/src/Databases/ScanQueue/EntryVO.php CHANGED
@@ -15,56 +15,53 @@ use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
15
  class EntryVO extends Base\EntryVO {
16
 
17
  /**
18
- * @param string $sProperty
19
- * @return mixed
20
  */
21
- public function __get( $sProperty ) {
22
 
23
- $mVal = parent::__get( $sProperty );
24
 
25
- switch ( $sProperty ) {
26
 
27
  case 'items':
28
  case 'results':
29
- if ( is_string( $mVal ) && !empty( $mVal ) ) {
30
- $mVal = base64_decode( $mVal );
31
- if ( !empty( $mVal ) ) {
32
- $mVal = @json_decode( $mVal, true );
33
  }
34
  }
35
 
36
- if ( !is_array( $mVal ) ) {
37
- $mVal = [];
38
  }
39
  break;
40
 
41
  default:
42
  break;
43
  }
44
- return $mVal;
45
  }
46
 
47
  /**
48
- * @param string $sProperty
49
- * @param mixed $mValue
50
- * @return $this
51
  */
52
- public function __set( $sProperty, $mValue ) {
53
 
54
- switch ( $sProperty ) {
55
 
56
  case 'items':
57
  case 'results':
58
- if ( !is_array( $mValue ) ) {
59
- $mValue = [];
60
  }
61
- $mValue = base64_encode( json_encode( $mValue ) );
62
  break;
63
 
64
  default:
65
  break;
66
  }
67
 
68
- return parent::__set( $sProperty, $mValue );
69
  }
70
  }
15
  class EntryVO extends Base\EntryVO {
16
 
17
  /**
18
+ * @inheritDoc
 
19
  */
20
+ public function __get( string $key ) {
21
 
22
+ $value = parent::__get( $key );
23
 
24
+ switch ( $key ) {
25
 
26
  case 'items':
27
  case 'results':
28
+ if ( is_string( $value ) && !empty( $value ) ) {
29
+ $value = base64_decode( $value );
30
+ if ( !empty( $value ) ) {
31
+ $value = @json_decode( $value, true );
32
  }
33
  }
34
 
35
+ if ( !is_array( $value ) ) {
36
+ $value = [];
37
  }
38
  break;
39
 
40
  default:
41
  break;
42
  }
43
+ return $value;
44
  }
45
 
46
  /**
47
+ * @inheritDoc
 
 
48
  */
49
+ public function __set( string $key, $value ) {
50
 
51
+ switch ( $key ) {
52
 
53
  case 'items':
54
  case 'results':
55
+ if ( !is_array( $value ) ) {
56
+ $value = [];
57
  }
58
+ $value = base64_encode( json_encode( $value ) );
59
  break;
60
 
61
  default:
62
  break;
63
  }
64
 
65
+ parent::__set( $key, $value );
66
  }
67
  }
src/lib/src/Databases/ScanQueue/Handler.php CHANGED
@@ -2,19 +2,6 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\ScanQueue;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
 
7
- class Handler extends Base\Handler {
8
-
9
- public function getCustomColumns() :array {
10
- return $this->getOptions()->getDef( 'table_columns_scanqueue' );
11
- }
12
-
13
- protected function getDefaultTableName() :string {
14
- return $this->getOptions()->getDef( 'table_name_scanqueue' );
15
- }
16
-
17
- protected function getTimestampColumns() :array {
18
- return $this->getOptions()->getDef( 'scanqueue_table_timestamp_columns' );
19
- }
20
  }
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/Update.php CHANGED
@@ -8,27 +8,27 @@ use FernleafSystems\Wordpress\Services\Services;
8
  class Update extends Base\Update {
9
 
10
  /**
11
- * @param EntryVO $oEntry
12
  * @return bool
13
  */
14
- public function storeResults( $oEntry ) {
15
- return isset( $oEntry->results ) &&
16
- $this->updateEntry( $oEntry, [ 'results' => gzcompress( $oEntry->getRawDataAsArray()[ 'results' ] ) ] );
17
  }
18
 
19
  /**
20
- * @param EntryVO $oEntry
21
  * @return bool
22
  */
23
- public function setFinished( $oEntry ) {
24
- return $this->updateEntry( $oEntry, [ 'finished_at' => Services::Request()->ts() ] );
25
  }
26
 
27
  /**
28
- * @param EntryVO $oEntry
29
  * @return bool
30
  */
31
- public function setStarted( $oEntry ) {
32
- return $this->updateEntry( $oEntry, [ 'started_at' => Services::Request()->ts() ] );
33
  }
34
  }
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/Handler.php CHANGED
@@ -2,19 +2,6 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Scanner;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
 
7
- class Handler extends Base\Handler {
8
-
9
- public function getCustomColumns() :array {
10
- return $this->getOptions()->getDef( 'table_columns_scanner' );
11
- }
12
-
13
- protected function getDefaultTableName() :string {
14
- return $this->getOptions()->getDef( 'table_name_scanner' );
15
- }
16
-
17
- protected function getTimestampColumns() :array {
18
- return $this->getOptions()->getDef( 'scanresults_table_timestamp_columns' );
19
- }
20
  }
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/Update.php CHANGED
@@ -46,34 +46,34 @@ class Update extends Base\Update {
46
  }
47
 
48
  /**
49
- * @param EntryVO $oEntry
50
  * @return bool
51
  */
52
- public function setIgnored( $oEntry ) {
53
- return $this->updateEntry( $oEntry, [ 'ignored_at' => Services::Request()->ts() ] );
54
  }
55
 
56
  /**
57
- * @param EntryVO $oEntry
58
  * @return bool
59
  */
60
- public function setNotified( $oEntry ) {
61
- return $this->updateEntry( $oEntry, [ 'notified_at' => Services::Request()->ts() ] );
62
  }
63
 
64
  /**
65
- * @param EntryVO $oEntry
66
  * @return bool
67
  */
68
- public function setNotIgnored( $oEntry ) {
69
- return $this->updateEntry( $oEntry, [ 'ignored_at' => 0 ] );
70
  }
71
 
72
  /**
73
- * @param EntryVO $oEntry
74
  * @return bool
75
  */
76
- public function setNotNotified( $oEntry ) {
77
- return $this->updateEntry( $oEntry, [ 'notified_at' => 0 ] );
78
  }
79
  }
46
  }
47
 
48
  /**
49
+ * @param EntryVO $entry
50
  * @return bool
51
  */
52
+ public function setIgnored( $entry ) {
53
+ return $this->updateEntry( $entry, [ 'ignored_at' => Services::Request()->ts() ] );
54
  }
55
 
56
  /**
57
+ * @param EntryVO $entry
58
  * @return bool
59
  */
60
+ public function setNotified( $entry ) {
61
+ return $this->updateEntry( $entry, [ 'notified_at' => Services::Request()->ts() ] );
62
  }
63
 
64
  /**
65
+ * @param EntryVO $entry
66
  * @return bool
67
  */
68
+ public function setNotIgnored( $entry ) {
69
+ return $this->updateEntry( $entry, [ 'ignored_at' => 0 ] );
70
  }
71
 
72
  /**
73
+ * @param EntryVO $entry
74
  * @return bool
75
  */
76
+ public function setNotNotified( $entry ) {
77
+ return $this->updateEntry( $entry, [ 'notified_at' => 0 ] );
78
  }
79
  }
src/lib/src/Databases/Session/Insert.php CHANGED
@@ -8,12 +8,11 @@ use FernleafSystems\Wordpress\Services\Services;
8
  class Insert extends Base\Insert {
9
 
10
  public function create( string $ID, string $username ) :bool {
11
- $aData = [
12
- 'session_id' => $ID,
13
- 'wp_username' => $username,
14
- 'ip' => Services::IP()->getRequestIp()
15
- ];
16
- return $this->setInsertData( $aData )->query() === 1;
17
  }
18
 
19
  /**
@@ -23,22 +22,22 @@ class Insert extends Base\Insert {
23
  protected function verifyInsertData() {
24
  parent::verifyInsertData();
25
 
26
- $aData = $this->getInsertData();
27
- if ( empty( $aData[ 'session_id' ] ) ) {
28
  throw new \Exception( 'Session ID not provided.' );
29
  }
30
- if ( empty( $aData[ 'wp_username' ] ) ) {
31
  throw new \Exception( 'WP Username not provided' );
32
  }
33
 
34
- $oIP = Services::IP();
35
- if ( empty( $aData[ 'ip' ] ) || !$oIP->isValidIp( $aData[ 'ip' ] ) ) {
36
- $sReqIP = $oIP->getRequestIp();
37
- $aData[ 'ip' ] = $oIP->isValidIp( $sReqIP ) ? $sReqIP : '';
38
  }
39
 
40
  $req = Services::Request();
41
- $aData = array_merge(
42
  [
43
  'browser' => md5( $req->getUserAgent() ),
44
  'logged_in_at' => $req->ts(),
@@ -47,9 +46,7 @@ class Insert extends Base\Insert {
47
  'login_intent_expires_at' => 0,
48
  'secadmin_at' => 0,
49
  ],
50
- $aData
51
- );
52
-
53
- return $this->setInsertData( $aData );
54
  }
55
  }
8
  class Insert extends Base\Insert {
9
 
10
  public function create( string $ID, string $username ) :bool {
11
+ return $this->setInsertData( [
12
+ 'session_id' => $ID,
13
+ 'wp_username' => $username,
14
+ 'ip' => Services::IP()->getRequestIp()
15
+ ] )->query() === 1;
 
16
  }
17
 
18
  /**
22
  protected function verifyInsertData() {
23
  parent::verifyInsertData();
24
 
25
+ $data = $this->getInsertData();
26
+ if ( empty( $data[ 'session_id' ] ) ) {
27
  throw new \Exception( 'Session ID not provided.' );
28
  }
29
+ if ( empty( $data[ 'wp_username' ] ) ) {
30
  throw new \Exception( 'WP Username not provided' );
31
  }
32
 
33
+ $srvIP = Services::IP();
34
+ if ( empty( $data[ 'ip' ] ) || !$srvIP->isValidIp( $data[ 'ip' ] ) ) {
35
+ $reqIP = $srvIP->getRequestIp();
36
+ $data[ 'ip' ] = $srvIP->isValidIp( $reqIP ) ? $reqIP: '';
37
  }
38
 
39
  $req = Services::Request();
40
+ return $this->setInsertData( array_merge(
41
  [
42
  'browser' => md5( $req->getUserAgent() ),
43
  'logged_in_at' => $req->ts(),
46
  'login_intent_expires_at' => 0,
47
  'secadmin_at' => 0,
48
  ],
49
+ $data
50
+ ) );
 
 
51
  }
52
  }
src/lib/src/Databases/Session/Select.php CHANGED
@@ -8,12 +8,7 @@ use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class Select extends Base\Select {
10
 
11
- /**
12
- * @return string[]
13
- */
14
- public function getDistinctIps() :array {
15
- return IpListSort::Sort( $this->getDistinctForColumn( 'ip' ) );
16
- }
17
 
18
  /**
19
  * @return string[]
8
 
9
  class Select extends Base\Select {
10
 
11
+ use Base\Traits\Select_IPTable;
 
 
 
 
 
12
 
13
  /**
14
  * @return string[]
src/lib/src/Databases/Session/Update.php CHANGED
@@ -52,10 +52,10 @@ class Update extends Base\Update {
52
 
53
  /**
54
  * @param EntryVO $session
55
- * @param array $aUpdateData
56
  * @return bool
57
  */
58
- public function updateSession( $session, $aUpdateData = [] ) {
59
- return parent::updateEntry( $session, $aUpdateData );
60
  }
61
  }
52
 
53
  /**
54
  * @param EntryVO $session
55
+ * @param array $updateData
56
  * @return bool
57
  */
58
+ public function updateSession( $session, $updateData = [] ) {
59
+ return parent::updateEntry( $session, $updateData );
60
  }
61
  }
src/lib/src/Databases/Tally/Delete.php DELETED
@@ -1,9 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Tally;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
-
7
- class Delete extends Base\Delete {
8
-
9
- }
 
 
 
 
 
 
 
 
 
src/lib/src/Databases/Tally/EntryVO.php DELETED
@@ -1,16 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Tally;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
-
7
- /**
8
- * Class EntryVO
9
- * @property string stat_key
10
- * @property string parent_stat_key
11
- * @property int tally
12
- * @property int modified_at
13
- */
14
- class EntryVO extends Base\EntryVO {
15
-
16
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Databases/Tally/Handler.php DELETED
@@ -1,32 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Tally;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Statistics\Options;
7
-
8
- class Handler extends Base\Handler {
9
-
10
- public function getColumns() :array {
11
- return $this->getOptions()->getDef( 'statistics_table_columns' );
12
- }
13
-
14
- protected function getDefaultTableName() :string {
15
- /** @var Options $opts */
16
- $opts = $this->getOptions();
17
- return $opts->getDbTable_Tallys();
18
- }
19
-
20
- protected function getDefaultCreateTableSql() :string {
21
- return "CREATE TABLE %s (
22
- id int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
23
- stat_key varchar(100) NOT NULL DEFAULT 0,
24
- parent_stat_key varchar(100) NOT NULL DEFAULT '',
25
- tally int(11) UNSIGNED NOT NULL DEFAULT 0,
26
- created_at int(15) UNSIGNED NOT NULL DEFAULT 0,
27
- modified_at int(15) UNSIGNED NOT NULL DEFAULT 0,
28
- deleted_at int(15) UNSIGNED NOT NULL DEFAULT 0,
29
- PRIMARY KEY (id)
30
- ) %s;";
31
- }
32
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Databases/Tally/Insert.php DELETED
@@ -1,45 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Tally;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
- use FernleafSystems\Wordpress\Services\Services;
7
-
8
- class Insert extends Base\Insert {
9
-
10
- /**
11
- * @param EntryVO $oEntry
12
- * @return bool
13
- */
14
- public function insert( $oEntry ) :bool {
15
- $bSuccess = false;
16
- if ( preg_match( '#[a-z]+\.[a-z]+#i', $oEntry->stat_key )
17
- && is_numeric( $oEntry->tally ) && $oEntry->tally > 0 ) {
18
- $bSuccess = parent::insert( $oEntry );
19
- }
20
- return $bSuccess;
21
- }
22
-
23
- /**
24
- * @param string sStatKey
25
- * @param string $sParent
26
- * @param int $nTally
27
- * @return bool
28
- */
29
- public function create( $sStatKey, $nTally, $sParent = '' ) {
30
- if ( !preg_match( '#[a-z]{1,}\.[a-z]{1,}#i', $sStatKey ) || empty( $nTally )
31
- || !is_numeric( $nTally ) || $nTally < 0 ) {
32
- return false;
33
- }
34
-
35
- $nTimeStamp = Services::Request()->ts();
36
- $aData = [
37
- 'stat_key' => $sStatKey,
38
- 'parent_stat_key' => $sParent,
39
- 'tally' => $nTally,
40
- 'modified_at' => $nTimeStamp,
41
- 'created_at' => $nTimeStamp,
42
- ];
43
- return $this->setInsertData( $aData )->query() === 1;
44
- }
45
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Databases/Tally/Select.php DELETED
@@ -1,39 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Tally;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
-
7
- class Select extends Base\Select {
8
-
9
- /**
10
- * @param string $sKey
11
- * @return $this
12
- */
13
- public function filterByParentStatKey( $sKey ) {
14
- return $this->addWhereEquals( 'parent_stat_key', $sKey );
15
- }
16
-
17
- /**
18
- * @param string $sKey
19
- * @return $this
20
- */
21
- public function filterByStatKey( $sKey ) {
22
- return $this->addWhereEquals( 'stat_key', $sKey );
23
- }
24
-
25
- /**
26
- * @param string $sStatKey
27
- * @param string $sParentStatKey
28
- * @return EntryVO|\stdClass|null
29
- */
30
- public function retrieveStat( $sStatKey, $sParentStatKey = '' ) {
31
- if ( !empty( $sParentStatKey ) ) {
32
- $this->filterByParentStatKey( $sParentStatKey );
33
- }
34
- $oR = $this->filterByStatKey( $sStatKey )
35
- ->setOrderBy( 'created_at', 'DESC' )
36
- ->first();
37
- return $oR;
38
- }
39
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Databases/Tally/Update.php DELETED
@@ -1,17 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Tally;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
-
7
- class Update extends Base\Update {
8
-
9
- /**
10
- * @param EntryVO $oStat
11
- * @param int $nAdditional
12
- * @return bool
13
- */
14
- public function incrementTally( $oStat, $nAdditional ) {
15
- return $this->updateEntry( $oStat, [ 'tally' => $oStat->tally + $nAdditional, ] );
16
- }
17
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Databases/Traffic/EntryVO.php CHANGED
@@ -18,39 +18,38 @@ use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
18
  class EntryVO extends Base\EntryVO {
19
 
20
  /**
21
- * @param string $sProperty
22
  * @return mixed
23
  */
24
- public function __get( $sProperty ) {
25
- switch ( $sProperty ) {
26
 
27
  case 'ip':
28
- $mVal = inet_ntop( parent::__get( $sProperty ) );
29
  break;
30
 
31
  default:
32
- $mVal = parent::__get( $sProperty );
 
33
  }
34
- return $mVal;
35
  }
36
 
37
  /**
38
- * @param string $sProperty
39
- * @param mixed $mValue
40
- * @return $this
41
  */
42
- public function __set( $sProperty, $mValue ) {
43
 
44
- switch ( $sProperty ) {
45
 
46
  case 'ip':
47
- $mValue = inet_pton( $mValue );
48
  break;
49
 
50
  default:
51
  break;
52
  }
53
 
54
- return parent::__set( $sProperty, $mValue );
55
  }
56
  }
18
  class EntryVO extends Base\EntryVO {
19
 
20
  /**
21
+ * @param string $key
22
  * @return mixed
23
  */
24
+ public function __get( string $key ) {
25
+ switch ( $key ) {
26
 
27
  case 'ip':
28
+ $value = inet_ntop( parent::__get( $key ) );
29
  break;
30
 
31
  default:
32
+ $value = parent::__get( $key );
33
+ break;
34
  }
35
+ return $value;
36
  }
37
 
38
  /**
39
+ * @inheritDoc
 
 
40
  */
41
+ public function __set( string $key, $value ) {
42
 
43
+ switch ( $key ) {
44
 
45
  case 'ip':
46
+ $value = inet_pton( $value );
47
  break;
48
 
49
  default:
50
  break;
51
  }
52
 
53
+ parent::__set( $key, $value );
54
  }
55
  }
src/lib/src/Databases/Traffic/Insert.php CHANGED
@@ -12,12 +12,12 @@ class Insert extends Base\Insert {
12
  */
13
  protected function verifyInsertData() {
14
  parent::verifyInsertData();
15
- $aData = $this->getInsertData();
16
 
17
- if ( empty( $aData[ 'ip' ] ) ) {
 
18
  throw new \Exception( 'IP address provided is not valid' );
19
  }
20
 
21
- return $this->setInsertData( $aData );
22
  }
23
  }
12
  */
13
  protected function verifyInsertData() {
14
  parent::verifyInsertData();
 
15
 
16
+ $data = $this->getInsertData();
17
+ if ( empty( $data[ 'ip' ] ) ) {
18
  throw new \Exception( 'IP address provided is not valid' );
19
  }
20
 
21
+ return $this->setInsertData( $data );
22
  }
23
  }
src/lib/src/Databases/Traffic/Select.php CHANGED
@@ -9,18 +9,7 @@ use FernleafSystems\Wordpress\Services\Services;
9
  class Select extends Base\Select {
10
 
11
  use BaseTraffic;
12
-
13
- /**
14
- * @return string[]
15
- */
16
- public function getDistinctIps() {
17
- return IpListSort::Sort( array_map(
18
- function ( $sIpBinary ) {
19
- return inet_ntop( $sIpBinary );
20
- },
21
- $this->getDistinctForColumn( 'ip' )
22
- ) );
23
- }
24
 
25
  /**
26
  * @return string[]
9
  class Select extends Base\Select {
10
 
11
  use BaseTraffic;
12
+ use Base\Traits\Select_IPTable;
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  /**
15
  * @return string[]
src/lib/src/Modules/AuditTrail/AjaxHandler.php CHANGED
@@ -28,41 +28,27 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
28
  protected function ajaxExec_AddParamToFirewallWhitelist() :array {
29
  /** @var ModCon $mod */
30
  $mod = $this->getMod();
31
- $bSuccess = false;
32
 
33
- $nId = Services::Request()->post( 'rid' );
34
- if ( empty( $nId ) || !is_numeric( $nId ) || $nId < 1 ) {
35
- $sMessage = __( 'Invalid audit entry selected for this action', 'wp-simple-firewall' );
36
  }
37
  else {
38
- /** @var Shield\Databases\AuditTrail\EntryVO $oEntry */
39
- $oEntry = $mod->getDbHandler_AuditTrail()
40
- ->getQuerySelector()
41
- ->byId( $nId );
42
-
43
- if ( empty( $oEntry ) ) {
44
- $sMessage = __( 'Audit entry could not be loaded.', 'wp-simple-firewall' );
45
  }
46
- else {
47
- $aData = $oEntry->meta;
48
- $sParam = isset( $aData[ 'param' ] ) ? $aData[ 'param' ] : '';
49
- $sUri = isset( $aData[ 'uri' ] ) ? $aData[ 'uri' ] : '*';
50
- if ( empty( $sParam ) ) {
51
- $sMessage = __( 'Parameter associated with this audit entry could not be found.', 'wp-simple-firewall' );
52
- }
53
- else {
54
- /** @var Shield\Modules\Firewall\ModCon $oModFire */
55
- $oModFire = $this->getCon()->getModule( 'firewall' );
56
- $oModFire->addParamToWhitelist( $sParam, $sUri );
57
- $sMessage = sprintf( __( 'Parameter "%s" whitelisted successfully', 'wp-simple-firewall' ), $sParam );
58
- $bSuccess = true;
59
- }
60
  }
61
  }
62
 
63
  return [
64
- 'success' => $bSuccess,
65
- 'message' => $sMessage
66
  ];
67
  }
68
 
28
  protected function ajaxExec_AddParamToFirewallWhitelist() :array {
29
  /** @var ModCon $mod */
30
  $mod = $this->getMod();
31
+ $success = false;
32
 
33
+ $entryID = Services::Request()->post( 'rid' );
34
+ if ( empty( $entryID ) || !is_numeric( $entryID ) || $entryID < 1 ) {
35
+ $msg = __( 'Invalid audit entry selected for this action', 'wp-simple-firewall' );
36
  }
37
  else {
38
+ try {
39
+ $msg = ( new Lib\Utility\AutoWhitelistParamFromAuditEntry() )
40
+ ->setMod( $mod )
41
+ ->run( (int)$entryID );
42
+ $success = true;
 
 
43
  }
44
+ catch ( \Exception $e ) {
45
+ $msg = $e->getMessage();
 
 
 
 
 
 
 
 
 
 
 
 
46
  }
47
  }
48
 
49
  return [
50
+ 'success' => $success,
51
+ 'message' => $msg
52
  ];
53
  }
54
 
src/lib/src/Modules/AuditTrail/Lib/AuditMessageBuilder.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\Lib;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases\AuditTrail\EntryVO;
6
+
7
+ class AuditMessageBuilder {
8
+
9
+ public static function Build( EntryVO $entry, array $msgStructure ) :string {
10
+
11
+ $substitutions = $entry->meta;
12
+ $rawString = implode( "\n", $msgStructure );
13
+
14
+ // In-case we're working with an older audit message without as much data substitutions
15
+ $missingCount = substr_count( $rawString, '%s' ) - count( $substitutions );
16
+
17
+ if ( $missingCount > 0 ) {
18
+ $substitutions = array_merge(
19
+ $substitutions,
20
+ array_fill( 0, $missingCount, 'data missing for older audit logs' )
21
+ );
22
+ }
23
+ return stripslashes( sanitize_textarea_field( vsprintf( $rawString, $substitutions ) ) );
24
+ }
25
+ }
src/lib/src/Modules/AuditTrail/Lib/Ops/Commit.php CHANGED
@@ -15,54 +15,51 @@ class Commit {
15
  */
16
  public function commitAudits( $aEvents ) {
17
  if ( is_array( $aEvents ) ) {
18
- foreach ( $aEvents as $oEntry ) {
19
- if ( $oEntry instanceof AuditTrail\EntryVO ) {
20
- $this->commitAudit( $oEntry );
21
  }
22
  }
23
  }
24
  }
25
 
26
  /**
27
- * @param AuditTrail\EntryVO $oEntry
28
  */
29
- public function commitAudit( $oEntry ) {
30
- $oWp = Services::WpGeneral();
31
- $oWpUsers = Services::WpUsers();
32
 
33
- if ( empty( $oEntry->ip ) ) {
34
- $oEntry->ip = Services::IP()->getRequestIp();
35
  }
36
- if ( empty( $oEntry->message ) ) {
37
- $oEntry->message = '';
38
- }
39
- if ( empty( $oEntry->wp_username ) ) {
40
- if ( $oWpUsers->isUserLoggedIn() ) {
41
- $sUser = $oWpUsers->getCurrentWpUsername();
42
  }
43
- elseif ( $oWp->isCron() ) {
44
  $sUser = 'WP Cron';
45
  }
46
- elseif ( $oWp->isWpCli() ) {
47
  $sUser = 'WP CLI';
48
  }
49
  else {
50
  $sUser = '-';
51
  }
52
- $oEntry->wp_username = $sUser;
53
  }
54
 
55
  $oLatest = null;
56
- $bCanCount = in_array( $oEntry->event, $this->getCanCountEvents() );
57
  if ( $bCanCount ) {
58
  /** @var AuditTrail\Select $oSel */
59
  $oSel = $this->getDbHandler()->getQuerySelector();
60
- $oLatest = $oSel->filterByEvent( $oEntry->event )
61
- ->filterByIp( $oEntry->ip )
62
  ->filterByCreatedAt( Services::Request()->carbon()->subDay()->timestamp, '>' )
63
  ->first();
64
  $bCanCount = ( $oLatest instanceof AuditTrail\EntryVO )
65
- && ( $oLatest->event === $oEntry->event && $oLatest->ip === $oEntry->ip );
66
  }
67
 
68
  if ( $bCanCount ) {
@@ -73,7 +70,7 @@ class Commit {
73
  else {
74
  /** @var AuditTrail\Insert $oQI */
75
  $oQI = $this->getDbHandler()->getQueryInserter();
76
- $oQI->insert( $oEntry );
77
  }
78
  }
79
 
15
  */
16
  public function commitAudits( $aEvents ) {
17
  if ( is_array( $aEvents ) ) {
18
+ foreach ( $aEvents as $entry ) {
19
+ if ( $entry instanceof AuditTrail\EntryVO ) {
20
+ $this->commitAudit( $entry );
21
  }
22
  }
23
  }
24
  }
25
 
26
  /**
27
+ * @param AuditTrail\EntryVO $entry
28
  */
29
+ public function commitAudit( $entry ) {
30
+ $WP = Services::WpGeneral();
31
+ $WPU = Services::WpUsers();
32
 
33
+ if ( empty( $entry->ip ) ) {
34
+ $entry->ip = Services::IP()->getRequestIp();
35
  }
36
+ if ( empty( $entry->wp_username ) ) {
37
+ if ( $WPU->isUserLoggedIn() ) {
38
+ $sUser = $WPU->getCurrentWpUsername();
 
 
 
39
  }
40
+ elseif ( $WP->isCron() ) {
41
  $sUser = 'WP Cron';
42
  }
43
+ elseif ( $WP->isWpCli() ) {
44
  $sUser = 'WP CLI';
45
  }
46
  else {
47
  $sUser = '-';
48
  }
49
+ $entry->wp_username = $sUser;
50
  }
51
 
52
  $oLatest = null;
53
+ $bCanCount = in_array( $entry->event, $this->getCanCountEvents() );
54
  if ( $bCanCount ) {
55
  /** @var AuditTrail\Select $oSel */
56
  $oSel = $this->getDbHandler()->getQuerySelector();
57
+ $oLatest = $oSel->filterByEvent( $entry->event )
58
+ ->filterByIp( $entry->ip )
59
  ->filterByCreatedAt( Services::Request()->carbon()->subDay()->timestamp, '>' )
60
  ->first();
61
  $bCanCount = ( $oLatest instanceof AuditTrail\EntryVO )
62
+ && ( $oLatest->event === $entry->event && $oLatest->ip === $entry->ip );
63
  }
64
 
65
  if ( $bCanCount ) {
70
  else {
71
  /** @var AuditTrail\Insert $oQI */
72
  $oQI = $this->getDbHandler()->getQueryInserter();
73
+ $oQI->insert( $entry );
74
  }
75
  }
76
 
src/lib/src/Modules/AuditTrail/Lib/Utility/AutoWhitelistParamFromAuditEntry.php ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\Lib\Utility;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases\AuditTrail\EntryVO;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases\AuditTrail\Select;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\ModCon;
9
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
10
+
11
+ class AutoWhitelistParamFromAuditEntry {
12
+
13
+ use ModConsumer;
14
+
15
+ /**
16
+ * @param int $entryID
17
+ * @return string
18
+ * @throws \Exception
19
+ */
20
+ public function run( int $entryID ) :string {
21
+ /** @var ModCon $mod */
22
+ $mod = $this->getMod();
23
+
24
+ /** @var Select $selector */
25
+ $selector = $mod->getDbHandler_AuditTrail()->getQuerySelector();
26
+
27
+ /** @var EntryVO $entry */
28
+ $entry = $selector->byId( $entryID );
29
+ if ( !$entry instanceof EntryVO ) {
30
+ throw new \Exception( __( 'Audit entry could not be loaded.', 'wp-simple-firewall' ) );
31
+ }
32
+
33
+ $uri = '';
34
+ foreach ( $selector->filterByRequestID( (int)$entry->rid )->all() as $entry ) {
35
+ $param = $this->extractParameter( $entry );
36
+ if ( !empty( $param ) ) {
37
+ $uri = $entry->meta[ 'uri' ] ?? '*';
38
+ break;
39
+ }
40
+ }
41
+
42
+ if ( empty( $param ) ) {
43
+ throw new \Exception( __( 'Parameter associated with this audit entry could not be found.', 'wp-simple-firewall' ) );
44
+ }
45
+
46
+ /** @var Shield\Modules\Firewall\ModCon $modFW */
47
+ $modFW = $this->getCon()->modules[ 'firewall' ];
48
+ $modFW->addParamToWhitelist( $param, $uri );
49
+ return sprintf( __( 'Parameter "%s" whitelisted successfully', 'wp-simple-firewall' ), $param );
50
+ }
51
+
52
+ private function extractParameter( EntryVO $entry ) :string {
53
+ return $entry->meta[ 'param' ] ?? '';
54
+ }
55
+ }
src/lib/src/Modules/AuditTrail/ModCon.php CHANGED
@@ -4,12 +4,24 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class ModCon extends BaseShield\ModCon {
10
 
11
  public function getDbHandler_AuditTrail() :Shield\Databases\AuditTrail\Handler {
12
- return $this->getDbH( 'audit' );
 
 
 
 
 
 
 
 
 
 
 
13
  }
14
 
15
  /**
@@ -62,12 +74,12 @@ class ModCon extends BaseShield\ModCon {
62
  $oFinder->filterByUsername( $oUser->user_login );
63
 
64
  $WP = Services::WpGeneral();
65
- /** @var Shield\Databases\AuditTrail\EntryVO $oEntry */
66
- foreach ( $oFinder->query() as $oEntry ) {
67
  $aExportItem[ 'data' ][] = [
68
- $sTimeStamp = $WP->getTimeStringForDisplay( $oEntry->getCreatedAt() ),
69
  'name' => sprintf( '[%s] Audit Trail Entry', $sTimeStamp ),
70
- 'value' => sprintf( '[IP:%s] %s', $oEntry->ip, $oEntry->message )
71
  ];
72
  }
73
 
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Tool\DbTableExport;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
10
  class ModCon extends BaseShield\ModCon {
11
 
12
  public function getDbHandler_AuditTrail() :Shield\Databases\AuditTrail\Handler {
13
+ $new = $this->getDbH( 'audit_trail' );
14
+ return empty( $new ) ? $this->getDbH( 'audit' ) : $new;
15
+ }
16
+
17
+ protected function handleFileDownload( string $downloadID ) {
18
+ switch ( $downloadID ) {
19
+ case 'db_audit':
20
+ ( new DbTableExport() )
21
+ ->setDbHandler( $this->getDbHandler_AuditTrail() )
22
+ ->toCSV();
23
+ break;
24
+ }
25
  }
26
 
27
  /**
74
  $oFinder->filterByUsername( $oUser->user_login );
75
 
76
  $WP = Services::WpGeneral();
77
+ /** @var Shield\Databases\AuditTrail\EntryVO $entry */
78
+ foreach ( $oFinder->query() as $entry ) {
79
  $aExportItem[ 'data' ][] = [
80
+ $sTimeStamp = $WP->getTimeStringForDisplay( $entry->getCreatedAt() ),
81
  'name' => sprintf( '[%s] Audit Trail Entry', $sTimeStamp ),
82
+ 'value' => sprintf( '[IP:%s] %s', $entry->ip, $entry->message )
83
  ];
84
  }
85
 
src/lib/src/Modules/Base/AjaxHandler.php CHANGED
@@ -18,7 +18,7 @@ abstract class AjaxHandler {
18
  add_filter( $this->getCon()->prefix( 'ajaxNonAuthAction' ), [ $this, 'handleAjaxNonAuth' ], 10, 2 );
19
  }
20
 
21
- public function handleAjaxAuth( array $ajaxResponse, string $ajaxAction ) {
22
  if ( !empty( $ajaxAction ) && ( empty( $ajaxResponse ) || !is_array( $ajaxResponse ) ) ) {
23
  $ajaxResponse = $this->normaliseAjaxResponse( $this->processAjaxAction( $ajaxAction ) );
24
  }
@@ -27,7 +27,7 @@ abstract class AjaxHandler {
27
 
28
  public function handleAjaxNonAuth( array $ajaxResponse, string $ajaxAction ) :array {
29
  if ( !empty( $ajaxAction ) && ( empty( $ajaxResponse ) || !is_array( $ajaxResponse ) ) ) {
30
- $ajaxResponse = $this->normaliseAjaxResponse( $this->processAjaxAction( $ajaxAction ) );
31
  }
32
  return $ajaxResponse;
33
  }
@@ -71,6 +71,10 @@ abstract class AjaxHandler {
71
  return [];
72
  }
73
 
 
 
 
 
74
  /**
75
  * We check for empty since if it's empty, there's nothing to normalize. It's a filter,
76
  * so if we send something back non-empty, it'll be treated like a "handled" response and
@@ -78,7 +82,7 @@ abstract class AjaxHandler {
78
  * @param array $ajaxResponse
79
  * @return array
80
  */
81
- protected function normaliseAjaxResponse( array $ajaxResponse ) {
82
  if ( !empty( $ajaxResponse ) ) {
83
  $ajaxResponse = array_merge(
84
  [
18
  add_filter( $this->getCon()->prefix( 'ajaxNonAuthAction' ), [ $this, 'handleAjaxNonAuth' ], 10, 2 );
19
  }
20
 
21
+ public function handleAjaxAuth( array $ajaxResponse, string $ajaxAction ) :array {
22
  if ( !empty( $ajaxAction ) && ( empty( $ajaxResponse ) || !is_array( $ajaxResponse ) ) ) {
23
  $ajaxResponse = $this->normaliseAjaxResponse( $this->processAjaxAction( $ajaxAction ) );
24
  }
27
 
28
  public function handleAjaxNonAuth( array $ajaxResponse, string $ajaxAction ) :array {
29
  if ( !empty( $ajaxAction ) && ( empty( $ajaxResponse ) || !is_array( $ajaxResponse ) ) ) {
30
+ $ajaxResponse = $this->normaliseAjaxResponse( $this->processNonAuthAjaxAction( $ajaxAction ) );
31
  }
32
  return $ajaxResponse;
33
  }
71
  return [];
72
  }
73
 
74
+ protected function processNonAuthAjaxAction( string $action ) :array {
75
+ return [];
76
+ }
77
+
78
  /**
79
  * We check for empty since if it's empty, there's nothing to normalize. It's a filter,
80
  * so if we send something back non-empty, it'll be treated like a "handled" response and
82
  * @param array $ajaxResponse
83
  * @return array
84
  */
85
+ protected function normaliseAjaxResponse( array $ajaxResponse ) :array {
86
  if ( !empty( $ajaxResponse ) ) {
87
  $ajaxResponse = array_merge(
88
  [
src/lib/src/Modules/Base/BaseProcessor.php DELETED
@@ -1,183 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules;
6
- use FernleafSystems\Wordpress\Services\Services;
7
-
8
- /**
9
- * Class BaseProcessor
10
- * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Base
11
- * @deprecated 10.2
12
- */
13
- class BaseProcessor {
14
-
15
- use Modules\ModConsumer;
16
-
17
- /**
18
- * @var BaseProcessor[]
19
- */
20
- protected $aSubPros;
21
-
22
- /**
23
- * @var bool
24
- */
25
- private $bLoginCaptured;
26
-
27
- /**
28
- * @var bool
29
- */
30
- private $bHasExecuted;
31
-
32
- /**
33
- * @param ModCon $mod
34
- */
35
- public function __construct( $mod ) {
36
- $this->setMod( $mod );
37
-
38
- add_action( 'init', [ $this, 'onWpInit' ], 9 );
39
- add_action( 'wp_loaded', [ $this, 'onWpLoaded' ] );
40
- { // Capture Logins
41
- add_action( 'wp_login', [ $this, 'onWpLogin' ], 10, 2 );
42
- if ( !Services::WpUsers()->isProfilePage() ) { // This can be fired during profile update.
43
- add_action( 'set_logged_in_cookie', [ $this, 'onWpSetLoggedInCookie' ], 5, 4 );
44
- }
45
- }
46
- add_action( $mod->prefix( 'plugin_shutdown' ), [ $this, 'onModuleShutdown' ] );
47
- add_action( $mod->prefix( 'daily_cron' ), [ $this, 'runDailyCron' ] );
48
- add_action( $mod->prefix( 'hourly_cron' ), [ $this, 'runHourlyCron' ] );
49
- add_action( $mod->prefix( 'deactivate_plugin' ), [ $this, 'deactivatePlugin' ] );
50
-
51
- /**
52
- * 2019-04-19:
53
- * wp_service_worker: added to prevent infinite page reloads triggered by an error with the PWA plugin.
54
- * It seems that using wp_localize_script() on a request with wp_service_worker=1 causes the worker
55
- * reload the page. Why exactly this happens hasn't been investigated, so we just skip any FRONTend
56
- * enqueues that might call wp_localize_script() for these requests.
57
- */
58
- if ( Services::Request()->query( 'wp_service_worker', 0 ) != 1 ) {
59
- add_action( 'wp_enqueue_scripts', [ $this, 'onWpEnqueueJs' ] );
60
- add_action( 'login_enqueue_scripts', [ $this, 'onWpEnqueueJs' ] );
61
- }
62
-
63
- $this->bHasExecuted = false;
64
- $this->init();
65
- }
66
-
67
- public function onWpInit() {
68
- }
69
-
70
- public function onWpLoaded() {
71
- }
72
-
73
- public function onWpEnqueueJs() {
74
- }
75
-
76
- /**
77
- * @param string $sUsername
78
- * @param \WP_User $user
79
- */
80
- public function onWpLogin( $sUsername, $user ) {
81
- }
82
-
83
- /**
84
- * @param string $sCookie
85
- * @param int $nExpire
86
- * @param int $nExpiration
87
- * @param int $nUserId
88
- */
89
- public function onWpSetLoggedInCookie( $sCookie, $nExpire, $nExpiration, $nUserId ) {
90
- }
91
-
92
- /**
93
- * @return bool
94
- */
95
- protected function isLoginCaptured() {
96
- return (bool)$this->bLoginCaptured;
97
- }
98
-
99
- public function runDailyCron() {
100
- }
101
-
102
- public function runHourlyCron() {
103
- }
104
-
105
- /**
106
- * @return $this
107
- */
108
- protected function setLoginCaptured() {
109
- $this->bLoginCaptured = true;
110
- return $this;
111
- }
112
-
113
- public function onModuleShutdown() {
114
- }
115
-
116
- public function init() {
117
- }
118
-
119
- /**
120
- * @return bool
121
- */
122
- public function isReadyToRun() {
123
- return true;
124
- }
125
-
126
- /**
127
- * @return $this
128
- */
129
- public function execute() {
130
- if ( !$this->bHasExecuted ) {
131
- $this->run();
132
- $this->bHasExecuted;
133
- }
134
- return $this;
135
- }
136
-
137
- /**
138
- * Override to set what this processor does when it's "run"
139
- */
140
- public function run() {
141
- }
142
-
143
- /**
144
- * We don't handle locale derivatives (yet)
145
- * @return string
146
- */
147
- protected function getGoogleRecaptchaLocale() {
148
- return Services::WpGeneral()->getLocale( '-' );
149
- }
150
-
151
- /**
152
- * @param string $key
153
- * @return BaseProcessor|mixed|null
154
- */
155
- protected function getSubPro( string $key ) {
156
- $aProcessors = $this->getSubProcessors();
157
- if ( !isset( $aProcessors[ $key ] ) ) {
158
- $aMap = $this->getSubProMap();
159
- if ( !isset( $aMap[ $key ] ) ) {
160
- error_log( 'Sub processor key not set: '.$key );
161
- }
162
- $aProcessors[ $key ] = new $aMap[ $key ]( $this->getMod() );
163
- }
164
- return $aProcessors[ $key ];
165
- }
166
-
167
- protected function getSubProMap() :array {
168
- return [];
169
- }
170
-
171
- public function deactivatePlugin() {
172
- }
173
-
174
- /**
175
- * @return BaseProcessor[]
176
- */
177
- protected function getSubProcessors() {
178
- if ( !isset( $this->aSubPros ) ) {
179
- $this->aSubPros = [];
180
- }
181
- return $this->aSubPros;
182
- }
183
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Base/ModCon.php CHANGED
@@ -31,7 +31,7 @@ abstract class ModCon {
31
  protected $bImportExportWhitelistNotify = false;
32
 
33
  /**
34
- * @var Shield\Modules\Base\BaseProcessor
35
  */
36
  private $oProcessor;
37
 
@@ -146,8 +146,8 @@ abstract class ModCon {
146
  */
147
  protected function getDbHandlers( $bInitAll = false ) {
148
  if ( $bInitAll ) {
149
- foreach ( $this->getAllDbClasses() as $sDbSlug => $sDbClass ) {
150
- $this->getDbH( $sDbSlug );
151
  }
152
  }
153
  return is_array( $this->aDbHandlers ) ? $this->aDbHandlers : [];
@@ -171,9 +171,15 @@ abstract class ModCon {
171
  $aDbClasses = $this->getAllDbClasses();
172
  if ( isset( $aDbClasses[ $dbhKey ] ) ) {
173
  /** @var Shield\Databases\Base\Handler $dbh */
174
- $dbh = new $aDbClasses[ $dbhKey ]();
175
  try {
176
- $dbh->setMod( $this )->tableInit();
 
 
 
 
 
 
177
  }
178
  catch ( \Exception $e ) {
179
  }
@@ -410,7 +416,7 @@ abstract class ModCon {
410
  }
411
 
412
  /**
413
- * @return Shield\Modules\Base\BaseProcessor|\FernleafSystems\Utilities\Logic\OneTimeExecute|mixed
414
  */
415
  public function getProcessor() {
416
  return $this->loadProcessor();
@@ -424,14 +430,10 @@ abstract class ModCon {
424
  );
425
  }
426
 
427
- /**
428
- * @param string $sAction
429
- * @return string
430
- */
431
- public function buildAdminActionNonceUrl( $sAction ) {
432
- $aActionNonce = $this->getNonceActionData( $sAction );
433
- $aActionNonce[ 'ts' ] = Services::Request()->ts();
434
- return add_query_arg( $aActionNonce, $this->getUrl_AdminPage() );
435
  }
436
 
437
  protected function getModActionParams( string $action ) :array {
@@ -442,8 +444,8 @@ abstract class ModCon {
442
  'mod_slug' => $this->getModSlug(),
443
  'ts' => Services::Request()->ts(),
444
  'exec_nonce' => substr(
445
- hash_hmac( 'md5', $action.Services::Request()->ts(), $con->getSiteInstallationId() )
446
- , 0, 6 )
447
  ];
448
  }
449
 
@@ -452,28 +454,28 @@ abstract class ModCon {
452
  * @throws \Exception
453
  */
454
  protected function verifyModActionRequest() :bool {
455
- $bValid = false;
456
 
457
  $con = $this->getCon();
458
  $req = Services::Request();
459
 
460
- $sExec = $req->request( 'exec' );
461
- if ( !empty( $sExec ) && $req->request( 'action' ) == $con->prefix() ) {
462
 
463
 
464
- if ( wp_verify_nonce( $req->request( 'exec_nonce' ), $sExec ) && $con->getMeetsBasePermissions() ) {
465
- $bValid = true;
466
  }
467
  else {
468
- $bValid = $req->request( 'exec_nonce' ) ===
469
- substr( hash_hmac( 'md5', $sExec.$req->request( 'ts' ), $con->getSiteInstallationId() ), 0, 6 );
470
  }
471
- if ( !$bValid ) {
472
  throw new \Exception( 'Invalid request' );
473
  }
474
  }
475
 
476
- return $bValid;
477
  }
478
 
479
  public function getUrl_DirectLinkToOption( string $key ) :string {
@@ -572,46 +574,48 @@ abstract class ModCon {
572
  }
573
 
574
  /**
575
- * @param array $aItems
576
  * @return array
577
  */
578
- public function supplySubMenuItem( $aItems ) {
579
-
580
- $sTitle = $this->getOptions()->getFeatureProperty( 'menu_title' );
581
- $sTitle = empty( $sTitle ) ? $this->getMainFeatureName() : __( $sTitle, 'wp-simple-firewall' );
582
 
583
- if ( !empty( $sTitle ) ) {
 
584
 
585
- $sHumanName = $this->getCon()->getHumanName();
 
 
586
 
587
- $bMenuHighlighted = $this->getOptions()->getFeatureProperty( 'highlight_menu_item' );
588
- if ( $bMenuHighlighted ) {
589
- $sTitle = sprintf( '<span class="icwp_highlighted">%s</span>', $sTitle );
590
  }
591
 
592
- $sMenuPageTitle = $sTitle.' - '.$sHumanName;
593
- $aItems[ $sMenuPageTitle ] = [
594
- $sTitle,
595
  $this->getModSlug(),
596
  [ $this, 'displayModuleAdminPage' ],
597
  $this->getIfShowModuleMenuItem()
598
  ];
599
 
600
- $aAdditionalItems = $this->getOptions()->getAdditionalMenuItems();
601
- if ( !empty( $aAdditionalItems ) && is_array( $aAdditionalItems ) ) {
602
 
603
- foreach ( $aAdditionalItems as $aMenuItem ) {
604
- $sMenuPageTitle = $sHumanName.' - '.$aMenuItem[ 'title' ];
605
- $aItems[ $sMenuPageTitle ] = [
606
- __( $aMenuItem[ 'title' ], 'wp-simple-firewall' ),
607
- $this->prefix( $aMenuItem[ 'slug' ] ),
608
- [ $this, $aMenuItem[ 'callback' ] ],
 
 
 
 
609
  true
610
  ];
611
  }
612
  }
613
  }
614
- return $aItems;
615
  }
616
 
617
  /**
@@ -620,13 +624,13 @@ abstract class ModCon {
620
  * This can of course be extended for any other types of redirect.
621
  */
622
  public function handleAutoPageRedirects() {
623
- $aConf = $this->getOptions()->getRawData_FullFeatureConfig();
624
- if ( !empty( $aConf[ 'custom_redirects' ] ) && $this->getCon()->isValidAdminArea() ) {
625
- foreach ( $aConf[ 'custom_redirects' ] as $aRedirect ) {
626
- if ( Services::Request()->query( 'page' ) == $this->prefix( $aRedirect[ 'source_mod_page' ] ) ) {
627
  Services::Response()->redirect(
628
- $this->getCon()->getModule( $aRedirect[ 'target_mod_page' ] )->getUrl_AdminPage(),
629
- $aRedirect[ 'query_args' ],
630
  true,
631
  false
632
  );
@@ -635,13 +639,6 @@ abstract class ModCon {
635
  }
636
  }
637
 
638
- /**
639
- * @return array
640
- */
641
- protected function getAdditionalMenuItem() {
642
- return [];
643
- }
644
-
645
  /**
646
  * TODO: not the place for this method.
647
  * @return array[]
@@ -922,6 +919,28 @@ abstract class ModCon {
922
  }
923
 
924
  protected function handleModAction( string $action ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
925
  }
926
 
927
  /**
@@ -1065,7 +1084,7 @@ abstract class ModCon {
1065
  }
1066
 
1067
  protected function isWizardPage() :bool {
1068
- return ( $this->getCon()->getShieldAction() == 'wizard' && $this->isThisModulePage() );
1069
  }
1070
 
1071
  /**
@@ -1087,13 +1106,13 @@ abstract class ModCon {
1087
 
1088
  /**
1089
  * Override this to customize anything with the display of the page
1090
- * @param array $aData
1091
  * @return string
1092
  */
1093
- protected function renderModulePage( array $aData = [] ) :string {
1094
  return $this->renderTemplate(
1095
  'index.php',
1096
- Services::DataManipulation()->mergeArraysRecursive( $this->getUIHandler()->getBaseDisplayData(), $aData )
1097
  );
1098
  }
1099
 
@@ -1129,27 +1148,27 @@ abstract class ModCon {
1129
  }
1130
 
1131
  /**
1132
- * @param string $sWizardSlug
1133
  * @return string
1134
  * @uses nonce
1135
  */
1136
- public function getUrl_Wizard( $sWizardSlug ) {
1137
- $aDef = $this->getWizardDefinition( $sWizardSlug );
1138
- if ( empty( $aDef[ 'min_user_permissions' ] ) ) { // i.e. no login/minimum perms
1139
- $sUrl = Services::WpGeneral()->getHomeUrl();
1140
  }
1141
  else {
1142
- $sUrl = Services::WpGeneral()->getAdminUrl( 'admin.php' );
1143
  }
1144
 
1145
  return add_query_arg(
1146
  [
1147
  'page' => $this->getModSlug(),
1148
  'shield_action' => 'wizard',
1149
- 'wizard' => $sWizardSlug,
1150
- 'nonwizard' => wp_create_nonce( 'wizard'.$sWizardSlug )
1151
  ],
1152
- $sUrl
1153
  );
1154
  }
1155
 
@@ -1161,44 +1180,31 @@ abstract class ModCon {
1161
  }
1162
 
1163
  /**
1164
- * @param string $sWizardSlug
1165
  * @return array
1166
  */
1167
- public function getWizardDefinition( $sWizardSlug ) {
1168
- $aDef = null;
1169
- if ( $this->hasWizardDefinition( $sWizardSlug ) ) {
1170
- $aW = $this->getWizardDefinitions();
1171
- $aDef = $aW[ $sWizardSlug ];
1172
  }
1173
- return $aDef;
1174
  }
1175
 
1176
- /**
1177
- * @return array
1178
- */
1179
- public function getWizardDefinitions() {
1180
- $aW = $this->getDef( 'wizards' );
1181
- return is_array( $aW ) ? $aW : [];
1182
  }
1183
 
1184
  public function hasWizard() :bool {
1185
  return count( $this->getWizardDefinitions() ) > 0;
1186
  }
1187
 
1188
- /**
1189
- * @param string $sWizardSlug
1190
- * @return bool
1191
- */
1192
- public function hasWizardDefinition( $sWizardSlug ) {
1193
- $aW = $this->getWizardDefinitions();
1194
- return !empty( $aW[ $sWizardSlug ] );
1195
  }
1196
 
1197
- /**
1198
- * @return bool
1199
- */
1200
- public function getIsShowMarketing() {
1201
- return apply_filters( $this->prefix( 'show_marketing' ), !$this->isPremium() );
1202
  }
1203
 
1204
  /**
31
  protected $bImportExportWhitelistNotify = false;
32
 
33
  /**
34
+ * @var Shield\Modules\Base\Processor
35
  */
36
  private $oProcessor;
37
 
146
  */
147
  protected function getDbHandlers( $bInitAll = false ) {
148
  if ( $bInitAll ) {
149
+ foreach ( $this->getAllDbClasses() as $dbSlug => $dbClass ) {
150
+ $this->getDbH( $dbSlug );
151
  }
152
  }
153
  return is_array( $this->aDbHandlers ) ? $this->aDbHandlers : [];
171
  $aDbClasses = $this->getAllDbClasses();
172
  if ( isset( $aDbClasses[ $dbhKey ] ) ) {
173
  /** @var Shield\Databases\Base\Handler $dbh */
174
+ $dbh = new $aDbClasses[ $dbhKey ]( $dbhKey );
175
  try {
176
+ // TODO remove 10.3: method_exists + table init
177
+ if ( method_exists( $dbh, 'execute' ) ) {
178
+ $dbh->setMod( $this )->execute();
179
+ }
180
+ else {
181
+ $dbh->setMod( $this )->tableInit();
182
+ }
183
  }
184
  catch ( \Exception $e ) {
185
  }
416
  }
417
 
418
  /**
419
+ * @return Shield\Modules\Base\Processor|\FernleafSystems\Utilities\Logic\ExecOnce|mixed
420
  */
421
  public function getProcessor() {
422
  return $this->loadProcessor();
430
  );
431
  }
432
 
433
+ public function buildAdminActionNonceUrl( string $action ) :string {
434
+ $nonce = $this->getNonceActionData( $action );
435
+ $nonce[ 'ts' ] = Services::Request()->ts();
436
+ return add_query_arg( $nonce, $this->getUrl_AdminPage() );
 
 
 
 
437
  }
438
 
439
  protected function getModActionParams( string $action ) :array {
444
  'mod_slug' => $this->getModSlug(),
445
  'ts' => Services::Request()->ts(),
446
  'exec_nonce' => substr(
447
+ hash_hmac( 'md5', $action.Services::Request()->ts(), $con->getSiteInstallationId() ), 0, 6
448
+ )
449
  ];
450
  }
451
 
454
  * @throws \Exception
455
  */
456
  protected function verifyModActionRequest() :bool {
457
+ $valid = false;
458
 
459
  $con = $this->getCon();
460
  $req = Services::Request();
461
 
462
+ $exec = $req->request( 'exec' );
463
+ if ( !empty( $exec ) && $req->request( 'action' ) == $con->prefix() ) {
464
 
465
 
466
+ if ( wp_verify_nonce( $req->request( 'exec_nonce' ), $exec ) && $con->getMeetsBasePermissions() ) {
467
+ $valid = true;
468
  }
469
  else {
470
+ $valid = $req->request( 'exec_nonce' ) ===
471
+ substr( hash_hmac( 'md5', $exec.$req->request( 'ts' ), $con->getSiteInstallationId() ), 0, 6 );
472
  }
473
+ if ( !$valid ) {
474
  throw new \Exception( 'Invalid request' );
475
  }
476
  }
477
 
478
+ return $valid;
479
  }
480
 
481
  public function getUrl_DirectLinkToOption( string $key ) :string {
574
  }
575
 
576
  /**
577
+ * @param array $items
578
  * @return array
579
  */
580
+ public function supplySubMenuItem( $items ) {
 
 
 
581
 
582
+ $title = $this->getOptions()->getFeatureProperty( 'menu_title' );
583
+ $title = empty( $title ) ? $this->getMainFeatureName() : __( $title, 'wp-simple-firewall' );
584
 
585
+ if ( !empty( $title ) ) {
586
+ $highlightedTemplate = '<span class="icwp_highlighted">%s</span>';
587
+ $humanName = $this->getCon()->getHumanName();
588
 
589
+ if ( $this->getOptions()->getFeatureProperty( 'highlight_menu_item' ) ) {
590
+ $title = sprintf( $highlightedTemplate, $title );
 
591
  }
592
 
593
+ $menuPageTitle = $title.' - '.$humanName;
594
+ $items[ $menuPageTitle ] = [
595
+ $title,
596
  $this->getModSlug(),
597
  [ $this, 'displayModuleAdminPage' ],
598
  $this->getIfShowModuleMenuItem()
599
  ];
600
 
601
+ foreach ( $this->getOptions()->getAdditionalMenuItems() as $menuItem ) {
 
602
 
603
+ // special case: don't show go pro if you're pro.
604
+ if ( $menuItem[ 'slug' ] !== 'pro-redirect' || !$this->isPremium() ) {
605
+
606
+ $title = __( $menuItem[ 'title' ], 'wp-simple-firewall' );
607
+ $menuPageTitle = $humanName.' - '.$title;
608
+ $isHighlighted = $menuItem[ 'highlight' ] ?? false;
609
+ $items[ $menuPageTitle ] = [
610
+ $isHighlighted ? sprintf( $highlightedTemplate, $title ) : $title,
611
+ $this->prefix( $menuItem[ 'slug' ] ),
612
+ [ $this, $menuItem[ 'callback' ] ?? '' ],
613
  true
614
  ];
615
  }
616
  }
617
  }
618
+ return $items;
619
  }
620
 
621
  /**
624
  * This can of course be extended for any other types of redirect.
625
  */
626
  public function handleAutoPageRedirects() {
627
+ $cfg = $this->getOptions()->getRawData_FullFeatureConfig();
628
+ if ( !empty( $cfg[ 'custom_redirects' ] ) && $this->getCon()->isValidAdminArea() ) {
629
+ foreach ( $cfg[ 'custom_redirects' ] as $redirect ) {
630
+ if ( Services::Request()->query( 'page' ) == $this->prefix( $redirect[ 'source_mod_page' ] ) ) {
631
  Services::Response()->redirect(
632
+ $this->getCon()->getModule( $redirect[ 'target_mod_page' ] )->getUrl_AdminPage(),
633
+ $redirect[ 'query_args' ],
634
  true,
635
  false
636
  );
639
  }
640
  }
641
 
 
 
 
 
 
 
 
642
  /**
643
  * TODO: not the place for this method.
644
  * @return array[]
919
  }
920
 
921
  protected function handleModAction( string $action ) {
922
+ switch ( $action ) {
923
+ case 'file_download':
924
+ $id = Services::Request()->query( 'download_id', '' );
925
+ if ( !empty( $id ) ) {
926
+ header( 'Set-Cookie: fileDownload=true; path=/' );
927
+ $this->handleFileDownload( $id );
928
+ }
929
+ break;
930
+ default:
931
+ break;
932
+ }
933
+ }
934
+
935
+ protected function handleFileDownload( string $downloadID ) {
936
+ }
937
+
938
+ public function createFileDownloadLink( string $downloadID, array $additionalParams = [] ) :string {
939
+ $additionalParams[ 'download_id' ] = $downloadID;
940
+ return add_query_arg(
941
+ array_merge( $this->getNonceActionData( 'file_download' ), $additionalParams ),
942
+ $this->getUrl_AdminPage()
943
+ );
944
  }
945
 
946
  /**
1084
  }
1085
 
1086
  protected function isWizardPage() :bool {
1087
+ return $this->getCon()->getShieldAction() == 'wizard' && $this->isThisModulePage();
1088
  }
1089
 
1090
  /**
1106
 
1107
  /**
1108
  * Override this to customize anything with the display of the page
1109
+ * @param array $data
1110
  * @return string
1111
  */
1112
+ protected function renderModulePage( array $data = [] ) :string {
1113
  return $this->renderTemplate(
1114
  'index.php',
1115
+ Services::DataManipulation()->mergeArraysRecursive( $this->getUIHandler()->getBaseDisplayData(), $data )
1116
  );
1117
  }
1118
 
1148
  }
1149
 
1150
  /**
1151
+ * @param string $wizardSlug
1152
  * @return string
1153
  * @uses nonce
1154
  */
1155
+ public function getUrl_Wizard( string $wizardSlug ) :string {
1156
+ $def = $this->getWizardDefinition( $wizardSlug );
1157
+ if ( empty( $def[ 'min_user_permissions' ] ) ) { // i.e. no login/minimum perms
1158
+ $url = Services::WpGeneral()->getHomeUrl();
1159
  }
1160
  else {
1161
+ $url = Services::WpGeneral()->getAdminUrl( 'admin.php' );
1162
  }
1163
 
1164
  return add_query_arg(
1165
  [
1166
  'page' => $this->getModSlug(),
1167
  'shield_action' => 'wizard',
1168
+ 'wizard' => $wizardSlug,
1169
+ 'nonwizard' => wp_create_nonce( 'wizard'.$wizardSlug )
1170
  ],
1171
+ $url
1172
  );
1173
  }
1174
 
1180
  }
1181
 
1182
  /**
1183
+ * @param string $wizardSlug
1184
  * @return array
1185
  */
1186
+ public function getWizardDefinition( string $wizardSlug ) {
1187
+ $def = null;
1188
+ if ( $this->hasWizardDefinition( $wizardSlug ) ) {
1189
+ $def = $this->getWizardDefinitions()[ $wizardSlug ];
 
1190
  }
1191
+ return $def;
1192
  }
1193
 
1194
+ public function getWizardDefinitions() :array {
1195
+ return is_array( $this->getDef( 'wizards' ) ) ? $this->getDef( 'wizards' ) : [];
 
 
 
 
1196
  }
1197
 
1198
  public function hasWizard() :bool {
1199
  return count( $this->getWizardDefinitions() ) > 0;
1200
  }
1201
 
1202
+ public function hasWizardDefinition( string $wizardSlug ) :bool {
1203
+ return !empty( $this->getWizardDefinitions()[ $wizardSlug ] );
 
 
 
 
 
1204
  }
1205
 
1206
+ public function getIsShowMarketing() :bool {
1207
+ return (bool)apply_filters( $this->prefix( 'show_marketing' ), !$this->isPremium() );
 
 
 
1208
  }
1209
 
1210
  /**
src/lib/src/Modules/Base/Options.php CHANGED
@@ -179,9 +179,6 @@ class Options {
179
  return ( isset( $raw[ 'properties' ] ) && isset( $raw[ 'properties' ][ $sProperty ] ) ) ? $raw[ 'properties' ][ $sProperty ] : null;
180
  }
181
 
182
- /**
183
- * @return array
184
- */
185
  public function getWpCliCfg() :array {
186
  $cfg = $this->getRawData_FullFeatureConfig();
187
  return array_merge(
@@ -428,7 +425,7 @@ class Options {
428
  }
429
 
430
  public function getAdditionalMenuItems() :array {
431
- return $this->getRawData_MenuItems();
432
  }
433
 
434
  public function getNeedSave() :bool {
@@ -588,9 +585,11 @@ class Options {
588
  return $raw[ 'requirements' ] ?? [];
589
  }
590
 
 
 
 
591
  protected function getRawData_MenuItems() :array {
592
- $raw = $this->getRawData_FullFeatureConfig();
593
- return $raw[ 'menu_items' ] ?? [];
594
  }
595
 
596
  public function getRawData_SingleOption( string $key ) :array {
179
  return ( isset( $raw[ 'properties' ] ) && isset( $raw[ 'properties' ][ $sProperty ] ) ) ? $raw[ 'properties' ][ $sProperty ] : null;
180
  }
181
 
 
 
 
182
  public function getWpCliCfg() :array {
183
  $cfg = $this->getRawData_FullFeatureConfig();
184
  return array_merge(
425
  }
426
 
427
  public function getAdditionalMenuItems() :array {
428
+ return $this->getRawData_FullFeatureConfig()[ 'menu_items' ] ?? [];
429
  }
430
 
431
  public function getNeedSave() :bool {
585
  return $raw[ 'requirements' ] ?? [];
586
  }
587
 
588
+ /**
589
+ * @deprecated 11.0
590
+ */
591
  protected function getRawData_MenuItems() :array {
592
+ return $this->getRawData_FullFeatureConfig()[ 'menu_items' ] ?? [];
 
593
  }
594
 
595
  public function getRawData_SingleOption( string $key ) :array {
src/lib/src/Modules/Base/Processor.php CHANGED
@@ -2,14 +2,14 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield;
7
 
8
  abstract class Processor {
9
 
10
  use Shield\Crons\PluginCronsConsumer;
11
  use Shield\Modules\ModConsumer;
12
- use OneTimeExecute;
13
 
14
  /**
15
  * @param ModCon $mod
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield;
7
 
8
  abstract class Processor {
9
 
10
  use Shield\Crons\PluginCronsConsumer;
11
  use Shield\Modules\ModConsumer;
12
+ use ExecOnce;
13
 
14
  /**
15
  * @param ModCon $mod
src/lib/src/Modules/Base/UI.php CHANGED
@@ -171,8 +171,8 @@ class UI {
171
  $con = $this->getCon();
172
  $urlBuilder = $con->urls;
173
 
174
- /** @var Shield\Modules\Plugin\Options $oPluginOptions */
175
- $oPluginOptions = $con->getModule_Plugin()->getOptions();
176
 
177
  return [
178
  'sPluginName' => $con->getHumanName(),
@@ -217,16 +217,16 @@ class UI {
217
  ],
218
  'strings' => $mod->getStrings()->getDisplayStrings(),
219
  'flags' => [
220
- 'access_restricted' => !$mod->canDisplayOptionsForm(),
221
- 'show_ads' => $mod->getIsShowMarketing(),
222
- 'wrap_page_content' => true,
223
- 'show_standard_options' => true,
224
- 'show_content_help' => true,
225
- 'show_alt_content' => false,
226
- 'has_wizard' => $mod->hasWizard(),
227
- 'is_premium' => $con->isPremiumActive(),
228
- 'show_transfer_switch' => $con->isPremiumActive(),
229
- 'is_wpcli' => $oPluginOptions->isEnabledWpcli()
230
  ],
231
  'hrefs' => [
232
  'go_pro' => 'https://shsec.io/shieldgoprofeature',
@@ -235,18 +235,17 @@ class UI {
235
  'wizard_landing' => $mod->getUrl_WizardLanding(),
236
 
237
  'form_action' => Services::Request()->getUri(),
238
- 'css_bootstrap' => $urlBuilder->forCss( 'bootstrap4.min' ),
239
  'css_pages' => $urlBuilder->forCss( 'pages' ),
240
  'css_steps' => $urlBuilder->forCss( 'jquery.steps' ),
241
  'css_fancybox' => $urlBuilder->forCss( 'jquery.fancybox.min' ),
242
  'css_globalplugin' => $urlBuilder->forCss( 'global-plugin' ),
243
  'css_wizard' => $urlBuilder->forCss( 'wizard' ),
244
  'js_jquery' => Services::Includes()->getUrl_Jquery(),
245
- 'js_bootstrap' => $urlBuilder->forJs( 'bootstrap4.bundle.min' ),
246
  'js_fancybox' => $urlBuilder->forJs( 'jquery.fancybox.min' ),
247
  'js_globalplugin' => $urlBuilder->forJs( 'global-plugin' ),
248
  'js_steps' => $urlBuilder->forJs( 'jquery.steps.min' ),
249
- 'js_wizard' => $urlBuilder->forJs( 'wizard' ),
250
  ],
251
  'imgs' => [
252
  'favicon' => $urlBuilder->forImage( 'pluginlogo_24x24.png' ),
171
  $con = $this->getCon();
172
  $urlBuilder = $con->urls;
173
 
174
+ /** @var Shield\Modules\Plugin\Options $pluginOptions */
175
+ $pluginOptions = $con->getModule_Plugin()->getOptions();
176
 
177
  return [
178
  'sPluginName' => $con->getHumanName(),
217
  ],
218
  'strings' => $mod->getStrings()->getDisplayStrings(),
219
  'flags' => [
220
+ 'access_restricted' => !$mod->canDisplayOptionsForm(),
221
+ 'show_ads' => $mod->getIsShowMarketing(),
222
+ 'wrap_page_content' => true,
223
+ 'show_standard_options' => true,
224
+ 'show_content_help' => true,
225
+ 'show_alt_content' => false,
226
+ 'has_wizard' => $mod->hasWizard(),
227
+ 'is_premium' => $con->isPremiumActive(),
228
+ 'show_transfer_switch' => $con->isPremiumActive(),
229
+ 'is_wpcli' => $pluginOptions->isEnabledWpcli(),
230
  ],
231
  'hrefs' => [
232
  'go_pro' => 'https://shsec.io/shieldgoprofeature',
235
  'wizard_landing' => $mod->getUrl_WizardLanding(),
236
 
237
  'form_action' => Services::Request()->getUri(),
238
+ 'css_bootstrap' => $urlBuilder->forCss( 'bootstrap' ),
239
  'css_pages' => $urlBuilder->forCss( 'pages' ),
240
  'css_steps' => $urlBuilder->forCss( 'jquery.steps' ),
241
  'css_fancybox' => $urlBuilder->forCss( 'jquery.fancybox.min' ),
242
  'css_globalplugin' => $urlBuilder->forCss( 'global-plugin' ),
243
  'css_wizard' => $urlBuilder->forCss( 'wizard' ),
244
  'js_jquery' => Services::Includes()->getUrl_Jquery(),
245
+ 'js_bootstrap' => $urlBuilder->forJs( 'bootstrap' ),
246
  'js_fancybox' => $urlBuilder->forJs( 'jquery.fancybox.min' ),
247
  'js_globalplugin' => $urlBuilder->forJs( 'global-plugin' ),
248
  'js_steps' => $urlBuilder->forJs( 'jquery.steps.min' ),
 
249
  ],
250
  'imgs' => [
251
  'favicon' => $urlBuilder->forImage( 'pluginlogo_24x24.png' ),
src/lib/src/Modules/Base/Upgrade.php CHANGED
@@ -2,12 +2,13 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
 
7
  class Upgrade {
8
 
9
  use ModConsumer;
10
- use \FernleafSystems\Utilities\Logic\OneTimeExecute;
11
 
12
  protected function run() {
13
  $this->upgradeModule();
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
 
8
  class Upgrade {
9
 
10
  use ModConsumer;
11
+ use ExecOnce;
12
 
13
  protected function run() {
14
  $this->upgradeModule();
src/lib/src/Modules/Base/WpCli.php CHANGED
@@ -2,18 +2,19 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\WpCli\ModuleStandard;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
 
8
  class WpCli {
9
 
10
  use ModConsumer;
11
- use \FernleafSystems\Utilities\Logic\OneTimeExecute;
12
 
13
  protected function run() {
14
  try {
15
- foreach ( $this->getAllCmdHandlers() as $oHandler ) {
16
- $oHandler->setMod( $this->getMod() )->execute();
17
  }
18
  }
19
  catch ( \Exception $e ) {
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\WpCli\ModuleStandard;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
 
9
  class WpCli {
10
 
11
  use ModConsumer;
12
+ use ExecOnce;
13
 
14
  protected function run() {
15
  try {
16
+ foreach ( $this->getAllCmdHandlers() as $handler ) {
17
+ $handler->setMod( $this->getMod() )->execute();
18
  }
19
  }
20
  catch ( \Exception $e ) {
src/lib/src/Modules/Base/WpCli/BaseWpCliCmd.php CHANGED
@@ -9,7 +9,15 @@ use FernleafSystems\Wordpress\Services\Services;
9
  abstract class BaseWpCliCmd {
10
 
11
  use ModConsumer;
12
- use \FernleafSystems\Utilities\Logic\OneTimeExecute;
 
 
 
 
 
 
 
 
13
 
14
  /**
15
  * @throws \Exception
@@ -25,58 +33,29 @@ abstract class BaseWpCliCmd {
25
  }
26
  }
27
 
28
- /**
29
- * @param array $aParts
30
- * @return string
31
- */
32
- protected function buildCmd( array $aParts ) {
33
  return implode( ' ',
34
- array_filter( array_merge( $this->getBaseCmdParts(), $aParts ) )
35
  );
36
  }
37
 
38
- /**
39
- * @return bool
40
- */
41
- protected function canRun() {
42
- /** @var Options $oOpts */
43
- $oOpts = $this->getCon()
44
- ->getModule_Plugin()
45
- ->getOptions();
46
- return $this->getOptions()->getWpCliCfg()[ 'enabled' ]
47
- && $oOpts->isEnabledWpcli();
48
- }
49
-
50
  /**
51
  * @return string[]
52
  */
53
- protected function getBaseCmdParts() {
54
  return [ 'shield', $this->getBaseCmdKey() ];
55
  }
56
 
57
- /**
58
- * @return string
59
- */
60
- protected function getBaseCmdKey() {
61
- $sRoot = $this->getOptions()->getWpCliCfg()[ 'root' ];
62
- return empty( $sRoot ) ? $this->getMod()->getModSlug( false ) : $sRoot;
63
  }
64
 
65
- /**
66
- * @param array $aArgs
67
- * @return array
68
- */
69
- protected function mergeCommonCmdArgs( array $aArgs ) {
70
- return array_merge(
71
- $this->getCommonCmdArgs(),
72
- $aArgs
73
- );
74
  }
75
 
76
- /**
77
- * @return array
78
- */
79
- protected function getCommonCmdArgs() {
80
  return [
81
  'before_invoke' => function () {
82
  $this->beforeInvokeCmd();
@@ -95,36 +74,32 @@ abstract class BaseWpCliCmd {
95
  }
96
 
97
  /**
98
- * @param array $aA
99
  * @return \WP_User
100
  * @throws \WP_CLI\ExitException
101
  */
102
- protected function loadUserFromArgs( array $aA ) {
103
  $oWpUsers = Services::WpUsers();
104
 
105
- $oU = null;
106
- if ( isset( $aA[ 'uid' ] ) ) {
107
- $oU = $oWpUsers->getUserById( $aA[ 'uid' ] );
108
  }
109
- elseif ( isset( $aA[ 'email' ] ) ) {
110
- $oU = $oWpUsers->getUserByEmail( $aA[ 'email' ] );
111
  }
112
- elseif ( isset( $aA[ 'username' ] ) ) {
113
- $oU = $oWpUsers->getUserByUsername( $aA[ 'username' ] );
114
  }
115
 
116
- if ( !$oU instanceof \WP_User || $oU->ID < 1 ) {
117
  \WP_CLI::error( "Couldn't find that user." );
118
  }
119
 
120
- return $oU;
121
  }
122
 
123
- /**
124
- * @param array $aA
125
- * @return bool
126
- */
127
- protected function isForceFlag( array $aA ) {
128
- return (bool)\WP_CLI\Utils\get_flag_value( $aA, 'force', false );
129
  }
130
  }
9
  abstract class BaseWpCliCmd {
10
 
11
  use ModConsumer;
12
+ use \FernleafSystems\Utilities\Logic\ExecOnce;
13
+
14
+ protected function canRun() :bool {
15
+ /** @var Options $pluginModOpts */
16
+ $pluginModOpts = $this->getCon()
17
+ ->getModule_Plugin()
18
+ ->getOptions();
19
+ return $this->getOptions()->getWpCliCfg()[ 'enabled' ] && $pluginModOpts->isEnabledWpcli();
20
+ }
21
 
22
  /**
23
  * @throws \Exception
33
  }
34
  }
35
 
36
+ protected function buildCmd( array $parts ) :string {
 
 
 
 
37
  return implode( ' ',
38
+ array_filter( array_merge( $this->getBaseCmdParts(), $parts ) )
39
  );
40
  }
41
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  /**
43
  * @return string[]
44
  */
45
+ protected function getBaseCmdParts() :array {
46
  return [ 'shield', $this->getBaseCmdKey() ];
47
  }
48
 
49
+ protected function getBaseCmdKey() :string {
50
+ $root = $this->getOptions()->getWpCliCfg()[ 'root' ];
51
+ return empty( $root ) ? $this->getMod()->getModSlug( false ) : $root;
 
 
 
52
  }
53
 
54
+ protected function mergeCommonCmdArgs( array $args ) :array {
55
+ return array_merge( $this->getCommonCmdArgs(), $args );
 
 
 
 
 
 
 
56
  }
57
 
58
+ protected function getCommonCmdArgs() :array {
 
 
 
59
  return [
60
  'before_invoke' => function () {
61
  $this->beforeInvokeCmd();
74
  }
75
 
76
  /**
77
+ * @param array $args
78
  * @return \WP_User
79
  * @throws \WP_CLI\ExitException
80
  */
81
+ protected function loadUserFromArgs( array $args ) :\WP_User {
82
  $oWpUsers = Services::WpUsers();
83
 
84
+ $user = null;
85
+ if ( isset( $args[ 'uid' ] ) ) {
86
+ $user = $oWpUsers->getUserById( $args[ 'uid' ] );
87
  }
88
+ elseif ( isset( $args[ 'email' ] ) ) {
89
+ $user = $oWpUsers->getUserByEmail( $args[ 'email' ] );
90
  }
91
+ elseif ( isset( $args[ 'username' ] ) ) {
92
+ $user = $oWpUsers->getUserByUsername( $args[ 'username' ] );
93
  }
94
 
95
+ if ( !$user instanceof \WP_User || $user->ID < 1 ) {
96
  \WP_CLI::error( "Couldn't find that user." );
97
  }
98
 
99
+ return $user;
100
  }
101
 
102
+ protected function isForceFlag( array $args ) :bool {
103
+ return (bool)\WP_CLI\Utils\get_flag_value( $args, 'force', false );
 
 
 
 
104
  }
105
  }
src/lib/src/Modules/Base/WpCli/ModuleStandard.php CHANGED
@@ -92,10 +92,10 @@ class ModuleStandard extends BaseWpCliCmd {
92
  ] ) );
93
  }
94
 
95
- public function cmdModAction( $null, $aA ) {
96
  $oMod = $this->getMod();
97
 
98
- switch ( $aA[ 'action' ] ) {
99
 
100
  case 'status':
101
  $oMod->isModOptEnabled() ?
@@ -121,13 +121,13 @@ class ModuleStandard extends BaseWpCliCmd {
121
 
122
  /**
123
  * @param array $null
124
- * @param array $aA
125
  */
126
- public function cmdOptGet( array $null, array $aA ) {
127
  $oOpts = $this->getOptions();
128
 
129
- $mVal = $oOpts->getOpt( $aA[ 'key' ], $null );
130
- $aOpt = $oOpts->getRawData_SingleOption( $aA[ 'key' ] );
131
  if ( !is_numeric( $mVal ) && empty( $mVal ) ) {
132
  \WP_CLI::log( __( 'No value set.', 'wp-simple-firewall' ) );
133
  }
@@ -151,20 +151,20 @@ class ModuleStandard extends BaseWpCliCmd {
151
 
152
  /**
153
  * @param array $null
154
- * @param array $aA
155
  */
156
- public function cmdOptSet( array $null, array $aA ) {
157
- $this->getOptions()->setOpt( $aA[ 'key' ], $aA[ 'value' ] );
158
  \WP_CLI::success( 'Option updated.' );
159
  }
160
 
161
- public function cmdOptList( array $null, array $aA ) {
162
  $oOpts = $this->getOptions();
163
  $oStrings = $this->getMod()->getStrings();
164
- $aOpts = [];
165
  foreach ( $oOpts->getOptionsForWpCli() as $sKey ) {
166
  try {
167
- $aOpts[] = [
168
  'key' => $sKey,
169
  'name' => $oStrings->getOptionStrings( $sKey )[ 'name' ],
170
  'type' => $oOpts->getOptionType( $sKey ),
@@ -176,11 +176,11 @@ class ModuleStandard extends BaseWpCliCmd {
176
  }
177
  }
178
 
179
- if ( empty( $aOpts ) ) {
180
  \WP_CLI::log( "This module doesn't have any configurable options." );
181
  }
182
  else {
183
- if ( !\WP_CLI\Utils\get_flag_value( $aA, 'full', false ) ) {
184
  $aKeys = [
185
  'key',
186
  'name',
@@ -188,12 +188,12 @@ class ModuleStandard extends BaseWpCliCmd {
188
  ];
189
  }
190
  else {
191
- $aKeys = array_keys( $aOpts[ 0 ] );
192
  }
193
 
194
  \WP_CLI\Utils\format_items(
195
- $aA[ 'format' ],
196
- $aOpts,
197
  $aKeys
198
  );
199
  }
92
  ] ) );
93
  }
94
 
95
+ public function cmdModAction( $null, $args ) {
96
  $oMod = $this->getMod();
97
 
98
+ switch ( $args[ 'action' ] ) {
99
 
100
  case 'status':
101
  $oMod->isModOptEnabled() ?
121
 
122
  /**
123
  * @param array $null
124
+ * @param array $args
125
  */
126
+ public function cmdOptGet( array $null, array $args ) {
127
  $oOpts = $this->getOptions();
128
 
129
+ $mVal = $oOpts->getOpt( $args[ 'key' ], $null );
130
+ $aOpt = $oOpts->getRawData_SingleOption( $args[ 'key' ] );
131
  if ( !is_numeric( $mVal ) && empty( $mVal ) ) {
132
  \WP_CLI::log( __( 'No value set.', 'wp-simple-firewall' ) );
133
  }
151
 
152
  /**
153
  * @param array $null
154
+ * @param array $args
155
  */
156
+ public function cmdOptSet( array $null, array $args ) {
157
+ $this->getOptions()->setOpt( $args[ 'key' ], $args[ 'value' ] );
158
  \WP_CLI::success( 'Option updated.' );
159
  }
160
 
161
+ public function cmdOptList( array $null, array $args ) {
162
  $oOpts = $this->getOptions();
163
  $oStrings = $this->getMod()->getStrings();
164
+ $opts = [];
165
  foreach ( $oOpts->getOptionsForWpCli() as $sKey ) {
166
  try {
167
+ $opts[] = [
168
  'key' => $sKey,
169
  'name' => $oStrings->getOptionStrings( $sKey )[ 'name' ],
170
  'type' => $oOpts->getOptionType( $sKey ),
176
  }
177
  }
178
 
179
+ if ( empty( $opts ) ) {
180
  \WP_CLI::log( "This module doesn't have any configurable options." );
181
  }
182
  else {
183
+ if ( !\WP_CLI\Utils\get_flag_value( $args, 'full', false ) ) {
184
  $aKeys = [
185
  'key',
186
  'name',
188
  ];
189
  }
190
  else {
191
+ $aKeys = array_keys( $opts[ 0 ] );
192
  }
193
 
194
  \WP_CLI\Utils\format_items(
195
+ $args[ 'format' ],
196
+ $opts,
197
  $aKeys
198
  );
199
  }
src/lib/src/Modules/BaseShield/ModCon.php CHANGED
@@ -5,9 +5,9 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
 
8
  use FernleafSystems\Wordpress\Services\Services;
9
  use FernleafSystems\Wordpress\Services\Utilities;
10
- use FernleafSystems\Wordpress\Services\Utilities\Net\IpIdentify;
11
 
12
  class ModCon extends Base\ModCon {
13
 
@@ -21,20 +21,13 @@ class ModCon extends Base\ModCon {
21
  */
22
  private static $bVisitorIsWhitelisted;
23
 
24
- /**
25
- * @return bool
26
- * @deprecated 10.2
27
- */
28
  public function canCacheDirWrite() :bool {
29
  return ( new Shield\Modules\Plugin\Lib\TestCacheDirWrite() )
30
  ->setMod( $this->getCon()->getModule_Plugin() )
31
  ->canWrite();
32
  }
33
 
34
- /**
35
- * @return Shield\Databases\Session\Handler
36
- */
37
- public function getDbHandler_Sessions() {
38
  return $this->getCon()
39
  ->getModule_Sessions()
40
  ->getDbHandler_Sessions();
@@ -50,11 +43,8 @@ class ModCon extends Base\ModCon {
50
  ->getCurrent();
51
  }
52
 
53
- /**
54
- * @return bool
55
- */
56
- public function hasValidRequestIP() {
57
- return Services::IP()->isValidIp( Services::IP()->getRequestIp() );
58
  }
59
 
60
  public function onWpInit() {
@@ -97,11 +87,14 @@ class ModCon extends Base\ModCon {
97
  return $cfg;
98
  }
99
 
 
 
 
100
  public function getSecAdminLoginAjaxData() :array {
101
  // We set a custom mod_slug so that this module handles the ajax request
102
- $dat = $this->getAjaxActionData( 'sec_admin_login' );
103
- $dat[ 'mod_slug' ] = $this->prefix( 'admin_access_restriction' );
104
- return $dat;
105
  }
106
 
107
  protected function getSecAdminCheckAjaxData() :array {
@@ -156,10 +149,7 @@ class ModCon extends Base\ModCon {
156
  return $this->renderTemplate( '/wpadmin_pages/security_admin/index.twig', $aData, true );
157
  }
158
 
159
- /**
160
- * @return bool
161
- */
162
- public function getIfSupport3rdParty() {
163
  return $this->isPremium();
164
  }
165
 
@@ -177,42 +167,48 @@ class ModCon extends Base\ModCon {
177
 
178
  public function isVisitorWhitelisted() :bool {
179
  if ( !isset( self::$bVisitorIsWhitelisted ) ) {
180
- try {
181
- $ipID = ( new IpIdentify(
182
- (string)Services::IP()->getRequestIp(),
183
- (string)Services::Request()->getUserAgent()
184
- ) )->run();
185
- $ipID = key( $ipID );
 
186
  }
187
- catch ( \Exception $e ) {
188
- $ipID = IpIdentify::UNKNOWN;
 
 
 
 
 
 
 
 
189
  }
190
-
191
- self::$bVisitorIsWhitelisted =
192
- in_array( $ipID, [ IpIdentify::ICONTROLWP, IpIdentify::MANAGEWP ] )
193
- || ( new Shield\Modules\IPs\Lib\Ops\LookupIpOnList() )
194
- ->setDbHandler( $this->getCon()->getModule_IPs()->getDbHandler_IPs() )
195
- ->setIP( Services::IP()->getRequestIp() )
196
- ->setListTypeWhite()
197
- ->lookup()
198
- instanceof Shield\Databases\IPs\EntryVO;
199
  }
200
  return self::$bVisitorIsWhitelisted;
201
  }
202
 
203
  public function isVerifiedBot() :bool {
204
  if ( !isset( self::$bIsVerifiedBot ) ) {
205
- $srvIP = Services::IP();
206
- self::$bIsVerifiedBot = !$srvIP->isLoopback() &&
207
- !in_array( $srvIP->getIpDetector()->getIPIdentity(), [
208
- IpIdentify::UNKNOWN,
209
- IpIdentify::THIS_SERVER,
210
- IpIdentify::VISITOR,
211
  ] );
212
  }
213
  return self::$bIsVerifiedBot;
214
  }
215
 
 
 
 
 
 
 
216
  public function isXmlrpcBypass() :bool {
217
  return $this->getCon()
218
  ->getModule_Plugin()
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin;
9
  use FernleafSystems\Wordpress\Services\Services;
10
  use FernleafSystems\Wordpress\Services\Utilities;
 
11
 
12
  class ModCon extends Base\ModCon {
13
 
21
  */
22
  private static $bVisitorIsWhitelisted;
23
 
 
 
 
 
24
  public function canCacheDirWrite() :bool {
25
  return ( new Shield\Modules\Plugin\Lib\TestCacheDirWrite() )
26
  ->setMod( $this->getCon()->getModule_Plugin() )
27
  ->canWrite();
28
  }
29
 
30
+ public function getDbHandler_Sessions() :Shield\Databases\Session\Handler {
 
 
 
31
  return $this->getCon()
32
  ->getModule_Sessions()
33
  ->getDbHandler_Sessions();
43
  ->getCurrent();
44
  }
45
 
46
+ public function hasValidRequestIP() :bool {
47
+ return !empty( Services::IP()->isValidIp( Services::IP()->getRequestIp() ) );
 
 
 
48
  }
49
 
50
  public function onWpInit() {
87
  return $cfg;
88
  }
89
 
90
+ /**
91
+ * @deprecated 11.0
92
+ */
93
  public function getSecAdminLoginAjaxData() :array {
94
  // We set a custom mod_slug so that this module handles the ajax request
95
+ $data = $this->getAjaxActionData( 'sec_admin_login' );
96
+ $data[ 'mod_slug' ] = $this->prefix( 'admin_access_restriction' );
97
+ return $data;
98
  }
99
 
100
  protected function getSecAdminCheckAjaxData() :array {
149
  return $this->renderTemplate( '/wpadmin_pages/security_admin/index.twig', $aData, true );
150
  }
151
 
152
+ public function getIfSupport3rdParty() :bool {
 
 
 
153
  return $this->isPremium();
154
  }
155
 
167
 
168
  public function isVisitorWhitelisted() :bool {
169
  if ( !isset( self::$bVisitorIsWhitelisted ) ) {
170
+
171
+ $ipID = Services::IP()->getIpDetector()->getIPIdentity();
172
+
173
+ $untrustedProviders = apply_filters( 'shield/untrusted_service_providers', [] );
174
+
175
+ if ( is_array( $untrustedProviders ) && in_array( $ipID, $untrustedProviders ) ) {
176
+ self::$bVisitorIsWhitelisted = false;
177
  }
178
+ elseif ( in_array( $ipID, Services::ServiceProviders()->getWpSiteManagementProviders() ) ) {
179
+ self::$bVisitorIsWhitelisted = true; // iControlWP / ManageWP
180
+ }
181
+ else {
182
+ self::$bVisitorIsWhitelisted =
183
+ ( new Shield\Modules\IPs\Lib\Ops\LookupIpOnList() )
184
+ ->setDbHandler( $this->getCon()->getModule_IPs()->getDbHandler_IPs() )
185
+ ->setIP( Services::IP()->getRequestIp() )
186
+ ->setListTypeWhite()
187
+ ->lookup() instanceof Shield\Databases\IPs\EntryVO;
188
  }
 
 
 
 
 
 
 
 
 
189
  }
190
  return self::$bVisitorIsWhitelisted;
191
  }
192
 
193
  public function isVerifiedBot() :bool {
194
  if ( !isset( self::$bIsVerifiedBot ) ) {
195
+ $ipID = Services::IP()->getIpDetector()->getIPIdentity();
196
+ self::$bIsVerifiedBot = !Services::IP()->isLoopback() &&
197
+ !in_array( $ipID, [
198
+ Utilities\Net\IpID::UNKNOWN,
199
+ Utilities\Net\IpID::THIS_SERVER,
200
+ Utilities\Net\IpID::VISITOR,
201
  ] );
202
  }
203
  return self::$bIsVerifiedBot;
204
  }
205
 
206
+ public function isEnabledWhitelabel() :bool {
207
+ /** @var SecurityAdmin\Options $opts */
208
+ $opts = $this->getCon()->getModule_SecAdmin()->getOptions();
209
+ return $opts->isEnabledWhitelabel();
210
+ }
211
+
212
  public function isXmlrpcBypass() :bool {
213
  return $this->getCon()
214
  ->getModule_Plugin()
src/lib/src/Modules/BaseShield/ShieldProcessor.php DELETED
@@ -1,14 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
-
7
- /**
8
- * Class ShieldProcessor
9
- * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield
10
- * @deprecated 10.2
11
- */
12
- class ShieldProcessor extends Base\BaseProcessor {
13
-
14
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/BaseShield/UI.php CHANGED
@@ -36,15 +36,16 @@ class UI extends Base\UI {
36
  'scripts' => []
37
  ],
38
  'ajax' => [
39
- 'sec_admin_login' => $mod->getSecAdminLoginAjaxData(),
40
  ],
41
  'flags' => [
42
- 'has_session' => $con->getModule_Sessions()
43
- ->getSessionCon()
44
- ->hasSession()
 
45
  ],
46
  'hrefs' => [
47
- 'aar_forget_key' => $con->getModule_SecAdmin()->isWlEnabled() ?
48
  $this->getCon()->getLabels()[ 'AuthorURI' ] : 'https://shsec.io/gc'
49
  ],
50
  'classes' => [
36
  'scripts' => []
37
  ],
38
  'ajax' => [
39
+ 'sec_admin_login' => $con->getModule_SecAdmin()->getSecAdminLoginAjaxData(),
40
  ],
41
  'flags' => [
42
+ 'has_session' => $con->getModule_Sessions()
43
+ ->getSessionCon()
44
+ ->hasSession(),
45
+ 'display_freshdesk_widget' => !$mod->isEnabledWhitelabel()
46
  ],
47
  'hrefs' => [
48
+ 'aar_forget_key' => $con->getModule_SecAdmin()->isEnabledWhitelabel() ?
49
  $this->getCon()->getLabels()[ 'AuthorURI' ] : 'https://shsec.io/gc'
50
  ],
51
  'classes' => [
src/lib/src/Modules/CommentsFilter/AjaxHandler.php CHANGED
@@ -7,32 +7,27 @@ use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
9
 
10
- protected function processAjaxAction( string $action ) :array {
11
 
12
  switch ( $action ) {
13
  case 'comment_token'.Services::IP()->getRequestIp():
14
- $aResponse = $this->ajaxExec_GenCommentToken();
15
  break;
16
 
17
  default:
18
- $aResponse = parent::processAjaxAction( $action );
19
  }
20
 
21
- return $aResponse;
22
  }
23
 
24
- /**
25
- * @return array
26
- */
27
- private function ajaxExec_GenCommentToken() {
28
- $oReq = Services::Request();
29
- $sToken = ( new Shield\Modules\CommentsFilter\Token\Create() )
30
- ->setMod( $this->getMod() )
31
- ->run( $oReq->post( 'ts' ), $oReq->post( 'post_id' ) );
32
-
33
  return [
34
  'success' => true,
35
- 'token' => $sToken,
 
 
36
  ];
37
  }
38
  }
7
 
8
  class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
9
 
10
+ protected function processNonAuthAjaxAction( string $action ) :array {
11
 
12
  switch ( $action ) {
13
  case 'comment_token'.Services::IP()->getRequestIp():
14
+ $response = $this->ajaxExec_GenCommentToken();
15
  break;
16
 
17
  default:
18
+ $response = parent::processAjaxAction( $action );
19
  }
20
 
21
+ return $response;
22
  }
23
 
24
+ private function ajaxExec_GenCommentToken() :array {
25
+ $req = Services::Request();
 
 
 
 
 
 
 
26
  return [
27
  'success' => true,
28
+ 'token' => ( new Shield\Modules\CommentsFilter\Token\Create() )
29
+ ->setMod( $this->getMod() )
30
+ ->run( $req->post( 'ts' ), $req->post( 'post_id' ) ),
31
  ];
32
  }
33
  }
src/lib/src/Modules/CommentsFilter/Forms/Gasp.php CHANGED
@@ -2,7 +2,8 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Forms;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
  use FernleafSystems\Wordpress\Services\Services;
@@ -10,7 +11,7 @@ use FernleafSystems\Wordpress\Services\Services;
10
  class Gasp {
11
 
12
  use ModConsumer;
13
- use OneTimeExecute;
14
 
15
  /**
16
  * The unique comment token assigned to this page
@@ -21,9 +22,9 @@ class Gasp {
21
  /**
22
  * @var bool
23
  */
24
- private $bFormItemPrinted = false;
25
 
26
- protected function canRun() {
27
  /** @var CommentsFilter\Options $opts */
28
  $opts = $this->getOptions();
29
  return !Services::Request()->isPost() && $opts->isEnabledGaspCheck() && !Services::WpUsers()->isUserLoggedIn();
@@ -32,76 +33,73 @@ class Gasp {
32
  protected function run() {
33
  add_action( 'wp', [ $this, 'onWP' ] );
34
  add_action( 'wp_footer', [ $this, 'maybeDequeueScript' ] );
35
- add_action( 'wp_enqueue_scripts', [ $this, 'onWpEnqueueJs' ] );
36
  }
37
 
38
  public function onWP() {
 
39
  add_action( 'comment_form', [ $this, 'printGaspFormItems' ], 1 );
40
  }
41
 
42
- public function onWpEnqueueJs() {
43
- /** @var CommentsFilter\ModCon $mod */
44
- $mod = $this->getMod();
45
- /** @var CommentsFilter\Options $opts */
46
- $opts = $this->getOptions();
47
- $con = $this->getCon();
48
-
49
- $sAsset = 'shield-comments';
50
- $handle = $con->prefix( 'shield-comments' );
51
- wp_register_script(
52
- $handle,
53
- $con->getPluginUrl_Js( $sAsset ),
54
- [ 'jquery' ],
55
- $con->getVersion(),
56
- true
57
- );
58
- wp_enqueue_script( $handle );
59
-
60
- $ts = Services::Request()->ts();
61
- $aNonce = $mod->getAjaxActionData( 'comment_token'.Services::IP()->getRequestIp() );
62
- $aNonce[ 'ts' ] = $ts;
63
- $aNonce[ 'post_id' ] = Services::WpPost()->getCurrentPostId();
64
-
65
- wp_localize_script(
66
- $handle,
67
- 'shield_comments',
68
- [
69
- 'ajax' => [
70
- 'comment_token' => $aNonce,
71
- ],
72
- 'vars' => [
73
- 'cbname' => 'cb_nombre'.rand(),
74
- 'botts' => $ts,
75
- 'token' => 'not created',
76
- 'uniq' => $this->getUniqueFormId(),
77
- 'cooldown' => $opts->getTokenCooldown(),
78
- 'expires' => $opts->getTokenExpireInterval(),
79
- ],
80
- 'strings' => [
81
- 'label' => $mod->getTextOpt( 'custom_message_checkbox' ),
82
- 'alert' => $mod->getTextOpt( 'custom_message_alert' ),
83
- 'comment_reload' => $mod->getTextOpt( 'custom_message_comment_reload' ),
84
- 'js_comment_wait' => $mod->getTextOpt( 'custom_message_comment_wait' ),
85
- ],
86
- 'flags' => [
87
- 'gasp' => true,
88
- 'recap' => $opts->isEnabledCaptcha() && $mod->getCaptchaCfg()->ready,
89
- ]
90
- ]
91
- );
92
  }
93
 
94
  /**
95
  * If the comment form component hasn't been printed, there's no comment form to protect.
96
  */
97
  public function maybeDequeueScript() {
98
- if ( empty( $this->bFormItemPrinted ) ) {
99
- wp_dequeue_script( $this->getCon()->prefix( 'shield-comments' ) );
100
  }
101
  }
102
 
103
  public function printGaspFormItems() {
104
- $this->bFormItemPrinted = true;
105
  echo $this->getMod()
106
  ->renderTemplate(
107
  'snippets/comment_form_botbox.twig',
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Forms;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Controller\Assets\Enqueue;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
9
  use FernleafSystems\Wordpress\Services\Services;
11
  class Gasp {
12
 
13
  use ModConsumer;
14
+ use ExecOnce;
15
 
16
  /**
17
  * The unique comment token assigned to this page
22
  /**
23
  * @var bool
24
  */
25
+ private $formItemsPrinted = false;
26
 
27
+ protected function canRun() :bool {
28
  /** @var CommentsFilter\Options $opts */
29
  $opts = $this->getOptions();
30
  return !Services::Request()->isPost() && $opts->isEnabledGaspCheck() && !Services::WpUsers()->isUserLoggedIn();
33
  protected function run() {
34
  add_action( 'wp', [ $this, 'onWP' ] );
35
  add_action( 'wp_footer', [ $this, 'maybeDequeueScript' ] );
 
36
  }
37
 
38
  public function onWP() {
39
+ $this->enqueueJS();
40
  add_action( 'comment_form', [ $this, 'printGaspFormItems' ], 1 );
41
  }
42
 
43
+ protected function enqueueJS() {
44
+ add_filter( 'shield/custom_enqueues', function ( array $enqueues ) {
45
+ $enqueues[ Enqueue::JS ][] = 'shield/comments';
46
+
47
+ add_filter( 'shield/custom_localisations', function ( array $localz ) {
48
+ /** @var CommentsFilter\ModCon $mod */
49
+ $mod = $this->getMod();
50
+ /** @var CommentsFilter\Options $opts */
51
+ $opts = $this->getOptions();
52
+
53
+ $ts = Services::Request()->ts();
54
+ $nonce = $mod->getAjaxActionData( 'comment_token'.Services::IP()->getRequestIp() );
55
+ $nonce[ 'ts' ] = $ts;
56
+ $nonce[ 'post_id' ] = Services::WpPost()->getCurrentPostId();
57
+
58
+ $localz[] = [
59
+ 'shield/comments',
60
+ 'shield_comments',
61
+ [
62
+ 'ajax' => [
63
+ 'comment_token' => $nonce,
64
+ ],
65
+ 'vars' => [
66
+ 'cbname' => 'cb_nombre'.rand(),
67
+ 'botts' => $ts,
68
+ 'token' => 'not created',
69
+ 'uniq' => $this->getUniqueFormId(),
70
+ 'cooldown' => $opts->getTokenCooldown(),
71
+ 'expires' => $opts->getTokenExpireInterval(),
72
+ ],
73
+ 'strings' => [
74
+ 'label' => $mod->getTextOpt( 'custom_message_checkbox' ),
75
+ 'alert' => $mod->getTextOpt( 'custom_message_alert' ),
76
+ 'comment_reload' => $mod->getTextOpt( 'custom_message_comment_reload' ),
77
+ 'js_comment_wait' => $mod->getTextOpt( 'custom_message_comment_wait' ),
78
+ ],
79
+ 'flags' => [
80
+ 'gasp' => true,
81
+ 'recap' => $opts->isEnabledCaptcha() && $mod->getCaptchaCfg()->ready,
82
+ ]
83
+ ]
84
+ ];
85
+ return $localz;
86
+ } );
87
+
88
+ return $enqueues;
89
+ } );
 
 
 
90
  }
91
 
92
  /**
93
  * If the comment form component hasn't been printed, there's no comment form to protect.
94
  */
95
  public function maybeDequeueScript() {
96
+ if ( empty( $this->formItemsPrinted ) ) {
97
+ wp_dequeue_script( $this->getCon()->prefix( 'shield/comments' ) );
98
  }
99
  }
100
 
101
  public function printGaspFormItems() {
102
+ $this->formItemsPrinted = true;
103
  echo $this->getMod()
104
  ->renderTemplate(
105
  'snippets/comment_form_botbox.twig',
src/lib/src/Modules/CommentsFilter/Forms/GoogleRecaptcha.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Forms;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
  use FernleafSystems\Wordpress\Services\Services;
@@ -10,9 +10,9 @@ use FernleafSystems\Wordpress\Services\Services;
10
  class GoogleRecaptcha {
11
 
12
  use ModConsumer;
13
- use OneTimeExecute;
14
 
15
- protected function canRun() {
16
  /** @var CommentsFilter\ModCon $mod */
17
  $mod = $this->getMod();
18
  /** @var CommentsFilter\Options $opts */
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Forms;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
  use FernleafSystems\Wordpress\Services\Services;
10
  class GoogleRecaptcha {
11
 
12
  use ModConsumer;
13
+ use ExecOnce;
14
 
15
+ protected function canRun() :bool {
16
  /** @var CommentsFilter\ModCon $mod */
17
  $mod = $this->getMod();
18
  /** @var CommentsFilter\Options $opts */
src/lib/src/Modules/CommentsFilter/Insights/OverviewCards.php CHANGED
@@ -25,10 +25,11 @@ class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
25
  $cards[ 'mod' ] = $this->getModDisabledCard();
26
  }
27
  else {
 
28
  $cards[ 'bot' ] = [
29
  'name' => __( 'Bot SPAM', 'wp-simple-firewall' ),
30
- 'state' => ( $opts->isEnabledGaspCheck() || $mod->isEnabledCaptcha() ) ? 1 : -1,
31
- 'summary' => ( $opts->isEnabledGaspCheck() || $mod->isEnabledCaptcha() ) ?
32
  __( 'Bot SPAM comments are blocked', 'wp-simple-firewall' )
33
  : __( 'There is no protection against Bot SPAM comments', 'wp-simple-firewall' ),
34
  'href' => $mod->getUrl_DirectLinkToSection( 'section_bot_comment_spam_protection_filter' ),
25
  $cards[ 'mod' ] = $this->getModDisabledCard();
26
  }
27
  else {
28
+ $botSpamOn = $opts->isEnabledAntiBot() || $opts->isEnabledGaspCheck() || $mod->isEnabledCaptcha();
29
  $cards[ 'bot' ] = [
30
  'name' => __( 'Bot SPAM', 'wp-simple-firewall' ),
31
+ 'state' => $botSpamOn ? 1 : -1,
32
+ 'summary' => $botSpamOn ?
33
  __( 'Bot SPAM comments are blocked', 'wp-simple-firewall' )
34
  : __( 'There is no protection against Bot SPAM comments', 'wp-simple-firewall' ),
35
  'href' => $mod->getUrl_DirectLinkToSection( 'section_bot_comment_spam_protection_filter' ),
src/lib/src/Modules/CommentsFilter/ModCon.php CHANGED
@@ -73,6 +73,11 @@ class ModCon extends BaseShield\ModCon {
73
  );
74
 
75
  $this->ensureCorrectCaptchaConfig();
 
 
 
 
 
76
  }
77
 
78
  public function isEnabledCaptcha() :bool {
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 {
src/lib/src/Modules/CommentsFilter/Options.php CHANGED
@@ -53,11 +53,16 @@ class Options extends BaseShield\Options {
53
 
54
  public function isEnabledGaspCheck() :bool {
55
  return $this->isOpt( 'enable_comments_gasp_protection', 'Y' )
56
- && ( $this->getTokenExpireInterval() > $this->getTokenCooldown() );
 
 
 
 
 
57
  }
58
 
59
  public function isEnabledCaptcha() :bool {
60
- return !$this->isOpt( 'google_recaptcha_style_comments', 'disabled' );
61
  }
62
 
63
  public function isEnabledHumanCheck() :bool {
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
+ return $this->isOpt( 'enable_antibot_check', 'Y' );
62
  }
63
 
64
  public function isEnabledCaptcha() :bool {
65
+ return !$this->isOpt( 'google_recaptcha_style_comments', 'disabled' ) && !$this->isEnabledAntiBot();
66
  }
67
 
68
  public function isEnabledHumanCheck() :bool {
src/lib/src/Modules/CommentsFilter/Processor.php CHANGED
@@ -10,16 +10,20 @@ class Processor extends BaseShield\Processor {
10
  public function onWpInit() {
11
  /** @var Options $opts */
12
  $opts = $this->getOptions();
13
- $oWpUsers = Services::WpUsers();
14
 
15
- $bLoadComProc = !$oWpUsers->isUserLoggedIn() ||
16
  !( new Scan\IsEmailTrusted() )->trusted(
17
- $oWpUsers->getCurrentWpUser()->user_email,
18
  $opts->getApprovedMinimum(),
19
  $opts->getTrustedRoles()
20
  );
21
 
22
- if ( $bLoadComProc ) {
 
 
 
 
23
 
24
  ( new Forms\GoogleRecaptcha() )
25
  ->setMod( $this->getMod() )
10
  public function onWpInit() {
11
  /** @var Options $opts */
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() )
src/lib/src/Modules/CommentsFilter/Scan/AntiBot.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @return bool
13
+ * @throws \Exception
14
+ */
15
+ public function scan() :bool {
16
+ $isBot = $this->getCon()
17
+ ->getModule_IPs()
18
+ ->getBotSignalsController()
19
+ ->isBot();
20
+ if ( $isBot ) {
21
+ throw new \Exception( __( 'Failed AntiBot Verification', 'wp-simple-firewall' ) );
22
+ }
23
+ return true;
24
+ }
25
+ }
src/lib/src/Modules/CommentsFilter/Scan/Bot.php CHANGED
@@ -15,46 +15,46 @@ class Bot {
15
  * @return true|\WP_Error
16
  */
17
  public function scan( $nPostId ) {
18
- /** @var CommentsFilter\Options $oOpts */
19
- $oOpts = $this->getOptions();
 
20
 
21
- $oReq = Services::Request();
22
- $sFieldCheckboxName = $oReq->post( 'cb_nombre' );
23
- $sFieldHoney = $oReq->post( 'sugar_sweet_email' );
24
- $nCommentTs = (int)$oReq->post( 'botts' );
25
- $sCommentToken = $oReq->post( 'comment_token' );
26
 
27
- $nCooldown = $oOpts->getTokenCooldown();
28
- $nExpire = $oOpts->getTokenExpireInterval();
29
 
30
- $sKey = null;
31
- $sExplanation = null;
32
- if ( !$sFieldCheckboxName || !$oReq->post( $sFieldCheckboxName ) ) {
33
- $sExplanation = sprintf( __( 'Failed Bot Test (%s)', 'wp-simple-firewall' ), __( 'checkbox', 'wp-simple-firewall' ) );
34
- $sKey = 'checkbox';
35
  }
36
  // honeypot check
37
  elseif ( !empty( $sFieldHoney ) ) {
38
- $sExplanation = sprintf( __( 'Failed Bot Test (%s)', 'wp-simple-firewall' ), __( 'honeypot', 'wp-simple-firewall' ) );
39
- $sKey = 'honeypot';
40
  }
41
- elseif ( $nCooldown > 0 || $nExpire > 0 ) {
42
 
43
- if ( $nCooldown > 0 && $oReq->ts() < ( $nCommentTs + $nCooldown ) ) {
44
- $sExplanation = sprintf( __( 'Failed Bot Test (%s)', 'wp-simple-firewall' ), __( 'cooldown', 'wp-simple-firewall' ) );
45
- $sKey = 'cooldown';
46
  }
47
- elseif ( $nExpire > 0 && $oReq->ts() > ( $nCommentTs + $nExpire ) ) {
48
- $sExplanation = sprintf( __( 'Failed Bot Test (%s)', 'wp-simple-firewall' ), __( 'expired', 'wp-simple-firewall' ) );
49
- $sKey = 'expired';
50
  }
51
  elseif ( !$this->checkTokenHash( $sCommentToken, $nCommentTs, $nPostId ) ) {
52
- $sExplanation = sprintf( __( 'Failed Bot Test (%s)', 'wp-simple-firewall' ), __( 'token', 'wp-simple-firewall' ) );
53
- $sKey = 'token';
54
  }
55
  }
56
 
57
- return empty( $sKey ) ? true : new \WP_Error( 'bot', $sExplanation, [ 'type' => $sKey ] );
58
  }
59
 
60
  /**
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
  /**
src/lib/src/Modules/CommentsFilter/Scan/CommentAdditiveCleaner.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Scan;
4
+
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities;
8
+
9
+ class CommentAdditiveCleaner {
10
+
11
+ use ModConsumer;
12
+ use ExecOnce;
13
+
14
+ protected function run() {
15
+ add_action( 'wp_set_comment_status', function ( $commentID, $newStatus ) {
16
+ if ( in_array( $newStatus, [ '0', 'hold', '1', 'approve' ], true ) ) {
17
+ wp_update_comment(
18
+ [
19
+ 'comment_ID' => $commentID,
20
+ 'comment_content' => preg_replace( '/## Comment SPAM Protection:.*\s##/m', '', get_comment( $commentID )->comment_content ),
21
+ ]
22
+ );
23
+ }
24
+ }, 10, 2 );
25
+ }
26
+ }
src/lib/src/Modules/CommentsFilter/Scan/Human.php CHANGED
@@ -1,9 +1,10 @@
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 Human {
@@ -21,7 +22,9 @@ class Human {
21
  /** @var CommentsFilter\Options $opts */
22
  $opts = $this->getOptions();
23
 
24
- $aItemsToCheck = array_intersect_key(
 
 
25
  [
26
  'comment_content' => $aCommData[ 'comment_content' ],
27
  'url' => $aCommData[ 'comment_author_url' ],
@@ -33,67 +36,24 @@ class Human {
33
  array_flip( $opts->getHumanSpamFilterItems() )
34
  );
35
 
36
- $mResult = true;
37
- foreach ( $this->getSpamBlacklist() as $sBlacklistWord ) {
38
- foreach ( $aItemsToCheck as $sKey => $sItem ) {
39
- if ( stripos( $sItem, $sBlacklistWord ) !== false ) { //mark as spam and exit;
40
- $mResult = new \WP_Error(
41
- 'human',
42
- sprintf( __( 'Human SPAM filter found "%s" in "%s"', 'wp-simple-firewall' ),
43
- $sBlacklistWord, $sKey ),
44
- [
45
- 'word' => $sBlacklistWord,
46
- 'key' => $sKey
47
- ]
48
- );
49
- break 2;
50
- }
51
- }
52
  }
53
 
54
  return $mResult;
55
  }
56
-
57
- /**
58
- * @return string[]
59
- */
60
- private function getSpamBlacklist() {
61
- /** @var CommentsFilter\ModCon $mod */
62
- $mod = $this->getMod();
63
- $aList = [];
64
- $oFs = Services::WpFs();
65
- $sBLFile = $mod->getSpamBlacklistFile();
66
-
67
- // Download if doesn't exist or expired.
68
- if ( !$oFs->exists( $sBLFile )
69
- || ( Services::Request()->ts() - $oFs->getModifiedTime( $sBLFile ) > WEEK_IN_SECONDS ) ) {
70
- Services::WpFs()->deleteFile( $sBLFile );
71
- $this->importBlacklist();
72
- }
73
-
74
- if ( $oFs->exists( $sBLFile ) ) {
75
- $sList = $oFs->getFileContent( $sBLFile, true );
76
- if ( !empty( $sList ) ) {
77
- $aList = array_map( 'base64_decode', explode( "\n", $sList ) );
78
- }
79
- }
80
- return $aList;
81
- }
82
-
83
- private function importBlacklist() {
84
- /** @var CommentsFilter\ModCon $mod */
85
- $mod = $this->getMod();
86
- $FS = Services::WpFs();
87
- $sBLFile = $mod->getSpamBlacklistFile();
88
- if ( !$FS->exists( $sBLFile ) ) {
89
- $sRawList = Services::HttpRequest()->getContent( $this->getOptions()
90
- ->getDef( 'url_spam_blacklist_terms' ) );
91
- $sList = '';
92
- if ( !empty( $sRawList ) ) {
93
- $sList = implode( "\n", array_map( 'base64_encode', array_filter( array_map( 'trim', explode( "\n", $sRawList ) ) ) ) );
94
- }
95
- // save the list to disk for the future.
96
- $FS->putFileContent( $sBLFile, $sList, true );
97
- }
98
- }
99
  }
1
+ <?php declare( strict_types=1 );
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\Plugin\Shield\Utilities\HumanSpam\TestContent;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
10
  class Human {
22
  /** @var CommentsFilter\Options $opts */
23
  $opts = $this->getOptions();
24
 
25
+ $mResult = true;
26
+
27
+ $items = array_intersect_key(
28
  [
29
  'comment_content' => $aCommData[ 'comment_content' ],
30
  'url' => $aCommData[ 'comment_author_url' ],
36
  array_flip( $opts->getHumanSpamFilterItems() )
37
  );
38
 
39
+ $spam = ( new TestContent() )
40
+ ->setCon( $this->getCon() )
41
+ ->findSpam( $items, true );
42
+
43
+ if ( !empty( $spam ) ) {
44
+ $key = key( reset( $spam ) );
45
+ $word = key( $spam );
46
+
47
+ $mResult = new \WP_Error(
48
+ 'human',
49
+ sprintf( __( 'Human SPAM filter found "%s" in "%s"', 'wp-simple-firewall' ), $word, $key ),
50
+ [
51
+ 'word' => $word,
52
+ 'key' => $key
53
+ ]
54
+ );
55
  }
56
 
57
  return $mResult;
58
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  }
src/lib/src/Modules/CommentsFilter/Scan/Scanner.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Scan;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Utilities;
@@ -11,19 +11,19 @@ use FernleafSystems\Wordpress\Services\Services;
11
  class Scanner {
12
 
13
  use ModConsumer;
14
- use OneTimeExecute;
15
 
16
  /**
17
  * @var string|int|null
18
  */
19
- private $mCommentStatus;
20
 
21
  /**
22
  * @var string
23
  */
24
- private $sCommentExplanation;
25
 
26
- protected function canRun() {
27
  return Services::Request()->isPost();
28
  }
29
 
@@ -40,44 +40,48 @@ class Scanner {
40
  * @return int|string|null
41
  */
42
  public function setStatus( $mStatus ) {
43
- if ( !is_null( $this->mCommentStatus ) && in_array( $this->mCommentStatus, [ '0', 'spam', 'trash' ] ) ) {
44
- $mStatus = $this->mCommentStatus;
45
  }
46
  return $mStatus;
47
  }
48
 
49
  /**
50
- * @param string $sContent
51
  * @return string
52
  */
53
- public function insertStatusExplanation( $sContent ) {
54
 
55
- if ( !is_null( $this->mCommentStatus ) && in_array( $this->mCommentStatus, [ '0', 'spam', 'trash' ] ) ) {
56
- switch ( $this->mCommentStatus ) {
 
57
  case 'spam':
58
- $sHumanStatus = 'SPAM';
59
  break;
60
  case 'trash':
61
- $sHumanStatus = __( 'Trash' );
62
  break;
63
  default:
64
  case '0':
65
- $sHumanStatus = __( 'Pending Moderation' );
66
  break;
67
  }
68
 
69
- $sContent =
70
- '[* '.sprintf( __( '%s plugin marked this comment as "%s".', 'wp-simple-firewall' )
71
- .' '.__( 'Reason: %s', 'wp-simple-firewall' ),
72
- $this->getCon()->getHumanName(),
73
- $sHumanStatus,
74
- $this->sCommentExplanation
75
-
76
- )." *]\n"
77
- .$sContent;
 
 
 
78
  }
79
 
80
- return $sContent;
81
  }
82
 
83
  /**
@@ -100,18 +104,18 @@ class Scanner {
100
  );
101
 
102
  if ( $mResult->get_error_code() == 'human' ) {
103
- $sStatus = $opts->getOpt( 'comments_default_action_human_spam' );
104
  }
105
  else {
106
- $sStatus = $opts->getOpt( 'comments_default_action_spam_bot' );
107
  }
108
 
109
- if ( $sStatus == 'reject' ) {
110
  Services::Response()->redirectToHome();
111
  }
112
 
113
- $this->mCommentStatus = $sStatus;
114
- $this->sCommentExplanation = $mResult->get_error_message();
115
  }
116
  }
117
 
@@ -130,28 +134,41 @@ class Scanner {
130
 
131
  $mResult = true;
132
 
133
- if ( !is_wp_error( $mResult ) && $opts->isEnabledGaspCheck() ) {
134
- $mResult = ( new Bot() )
135
- ->setMod( $this->getMod() )
136
- ->scan( $aCommData[ 'comment_post_ID' ] );
 
 
 
 
 
137
  }
 
138
 
139
- if ( !is_wp_error( $mResult ) && $opts->isEnabledCaptcha() && $mod->getCaptchaCfg()->ready ) {
140
- try {
141
- if ( $mod->getCaptchaCfg()->provider === 'hcaptcha' ) {
142
- ( new Utilities\HCaptcha\TestRequest() )
143
- ->setMod( $this->getMod() )
144
- ->test();
 
 
 
 
 
 
 
 
 
 
 
 
145
  }
146
- else {
147
- ( new Utilities\ReCaptcha\TestRequest() )
148
- ->setMod( $this->getMod() )
149
- ->test();
150
  }
151
  }
152
- catch ( \Exception $e ) {
153
- $mResult = new \WP_Error( 'recaptcha', $e->getMessage(), [] );
154
- }
155
  }
156
 
157
  if ( !is_wp_error( $mResult ) && $opts->isEnabledHumanCheck() ) {
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Scan;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Utilities;
11
  class Scanner {
12
 
13
  use ModConsumer;
14
+ use ExecOnce;
15
 
16
  /**
17
  * @var string|int|null
18
  */
19
+ private $spamStatus;
20
 
21
  /**
22
  * @var string
23
  */
24
+ private $spamReason;
25
 
26
+ protected function canRun() :bool {
27
  return Services::Request()->isPost();
28
  }
29
 
40
  * @return int|string|null
41
  */
42
  public function setStatus( $mStatus ) {
43
+ if ( !is_null( $this->spamStatus ) && in_array( $this->spamStatus, [ '0', 'spam', 'trash' ] ) ) {
44
+ $mStatus = $this->spamStatus;
45
  }
46
  return $mStatus;
47
  }
48
 
49
  /**
50
+ * @param string $content
51
  * @return string
52
  */
53
+ public function insertStatusExplanation( $content ) {
54
 
55
+ if ( !is_null( $this->spamStatus ) && in_array( $this->spamStatus, [ '0', 'spam', 'trash' ] ) ) {
56
+
57
+ switch ( $this->spamStatus ) {
58
  case 'spam':
59
+ $humanStatus = 'SPAM';
60
  break;
61
  case 'trash':
62
+ $humanStatus = __( 'Trash' );
63
  break;
64
  default:
65
  case '0':
66
+ $humanStatus = __( 'Pending Moderation' );
67
  break;
68
  }
69
 
70
+ $additional = (string)apply_filters(
71
+ 'shield/comment_spam_explanation',
72
+ sprintf(
73
+ "## Comment SPAM Protection: %s %s ##\n",
74
+ sprintf( __( '%s marked this comment as "%s".', 'wp-simple-firewall' ),
75
+ $this->getCon()->getHumanName(), $humanStatus ),
76
+ sprintf( __( 'Reason: %s', 'wp-simple-firewall' ), $this->spamReason )
77
+ ),
78
+ $this->spamStatus,
79
+ $this->spamReason
80
+ );
81
+ $content = $additional.$content;
82
  }
83
 
84
+ return $content;
85
  }
86
 
87
  /**
104
  );
105
 
106
  if ( $mResult->get_error_code() == 'human' ) {
107
+ $status = $opts->getOpt( 'comments_default_action_human_spam' );
108
  }
109
  else {
110
+ $status = $opts->getOpt( 'comments_default_action_spam_bot' );
111
  }
112
 
113
+ if ( $status == 'reject' ) {
114
  Services::Response()->redirectToHome();
115
  }
116
 
117
+ $this->spamStatus = $status;
118
+ $this->spamReason = $mResult->get_error_message();
119
  }
120
  }
121
 
134
 
135
  $mResult = true;
136
 
137
+ if ( $opts->isEnabledAntiBot() ) {
138
+ try {
139
+ ( new AntiBot() )
140
+ ->setMod( $this->getMod() )
141
+ ->scan();
142
+ }
143
+ catch ( \Exception $e ) {
144
+ $mResult = new \WP_Error( 'antibot', $e->getMessage() );
145
+ }
146
  }
147
+ else {
148
 
149
+ if ( $opts->isEnabledGaspCheck() ) {
150
+ $mResult = ( new Bot() )
151
+ ->setMod( $this->getMod() )
152
+ ->scan( $aCommData[ 'comment_post_ID' ] );
153
+ }
154
+
155
+ if ( !is_wp_error( $mResult ) && $opts->isEnabledCaptcha() && $mod->getCaptchaCfg()->ready ) {
156
+ try {
157
+ if ( $mod->getCaptchaCfg()->provider === 'hcaptcha' ) {
158
+ ( new Utilities\HCaptcha\TestRequest() )
159
+ ->setMod( $this->getMod() )
160
+ ->test();
161
+ }
162
+ else {
163
+ ( new Utilities\ReCaptcha\TestRequest() )
164
+ ->setMod( $this->getMod() )
165
+ ->test();
166
+ }
167
  }
168
+ catch ( \Exception $e ) {
169
+ $mResult = new \WP_Error( 'recaptcha', $e->getMessage(), [] );
 
 
170
  }
171
  }
 
 
 
172
  }
173
 
174
  if ( !is_wp_error( $mResult ) && $opts->isEnabledHumanCheck() ) {
src/lib/src/Modules/CommentsFilter/Strings.php CHANGED
@@ -12,6 +12,7 @@ class Strings extends Base\Strings {
12
  */
13
  protected function getAuditMessages() :array {
14
  return [
 
15
  'spam_block_human' => [
16
  __( 'Blocked human SPAM comment containing suspicious content.', 'wp-simple-firewall' ),
17
  __( 'Human SPAM filter found "%s" in "%s"', 'wp-simple-firewall' )
@@ -94,68 +95,91 @@ class Strings extends Base\Strings {
94
  switch ( $key ) {
95
 
96
  case 'enable_comments_filter' :
97
- $sName = sprintf( __( 'Enable %s Module', 'wp-simple-firewall' ), $modName );
98
- $sSummary = __( 'Enable (or Disable) The Comment SPAM Protection Feature', 'wp-simple-firewall' );
99
- $sDescription = sprintf( __( 'Un-Checking this option will completely disable the %s module.', 'wp-simple-firewall' ), __( 'Comment SPAM Protection', 'wp-simple-firewall' ) );
100
  break;
101
 
102
  case 'trusted_commenter_minimum' :
103
- $sName = __( 'Trusted Commenter Minimum', 'wp-simple-firewall' );
104
- $sSummary = __( 'Minimum Number Of Approved Comments Before Commenter Is Trusted', 'wp-simple-firewall' );
105
- $sDescription = __( 'Specify how many approved comments must exist before a commenter is trusted and their comments are no longer scanned.', 'wp-simple-firewall' )
106
- .'<br />'.__( 'Normally WordPress will trust after 1 comment.', 'wp-simple-firewall' );
107
  break;
108
 
109
  case 'trusted_user_roles' :
110
- $sName = __( 'Trusted User Roles', 'wp-simple-firewall' );
111
- $sSummary = __( "Comments From Users With These Roles Will Never Be Scanned", 'wp-simple-firewall' );
112
- $sDescription = __( "Shield doesn't normally scan comments from logged-in or registered users.", 'wp-simple-firewall' )
113
- .'<br />'.__( "Specify user roles here that shouldn't be scanned.", 'wp-simple-firewall' )
114
- .'<br/>'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( 'Take a new line for each user role.', 'wp-simple-firewall' ) )
115
- .'<br/>'.sprintf( '%s: %s', __( 'Available Roles', 'wp-simple-firewall' ), implode( ', ', Services::WpUsers()
116
- ->getAvailableUserRoles() ) );
 
 
 
 
 
 
 
 
 
 
 
 
117
  break;
118
 
119
  case 'enable_comments_human_spam_filter' :
120
- $sName = __( 'Human SPAM Filter', 'wp-simple-firewall' );
121
- $sSummary = sprintf( __( 'Enable (or Disable) The %s Feature', 'wp-simple-firewall' ), __( 'Human SPAM Filter', 'wp-simple-firewall' ) );
122
- $sDescription = __( 'Scans the content of WordPress comments for keywords that are indicative of SPAM and marks the comment according to your preferred setting below.', 'wp-simple-firewall' );
 
 
 
123
  break;
124
 
125
  case 'comments_default_action_human_spam' :
126
- $sName = __( 'SPAM Action', 'wp-simple-firewall' );
127
- $sSummary = __( 'How To Categorise Comments When Identified To Be SPAM', 'wp-simple-firewall' );
128
- $sDescription = 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>' );
129
  break;
130
 
131
  case 'enable_comments_gasp_protection' :
132
- $sName = __( 'SPAM Bot Protection', 'wp-simple-firewall' );
133
- $sSummary = __( 'Block 100% Comment SPAM From Automated Bots', 'wp-simple-firewall' );
134
- $sDescription = __( 'Highly effective detection for the most common types of comment SPAM.', 'wp-simple-firewall' )
135
- .'<br/>'.sprintf( '%s: %s', __( 'Bonus', 'wp-simple-firewall' ), __( "Unlike Akismet, your data is never sent off-site to 3rd party processing servers.", 'wp-simple-firewall' ) );
 
 
 
 
 
136
  break;
137
 
138
  case 'comments_default_action_spam_bot' :
139
- $sName = __( 'SPAM Action', 'wp-simple-firewall' );
140
- $sSummary = __( 'How To Categorise Comments When Identified To Be SPAM', 'wp-simple-firewall' );
141
- $sDescription = 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;">'.__( 'an automatic bot', 'wp-simple-firewall' ).'</span>' );
 
142
  break;
143
 
144
  case 'custom_message_checkbox' :
145
- $sName = __( 'GASP Checkbox Message', 'wp-simple-firewall' );
146
- $sSummary = __( 'If you want a custom checkbox message, please provide this here', 'wp-simple-firewall' );
147
- $sDescription = __( "You can customise the message beside the checkbox.", 'wp-simple-firewall' )
148
- .'<br />'.sprintf( __( 'Default Message: %s', 'wp-simple-firewall' ), __( "Please check the box to confirm you're not a spammer", 'wp-simple-firewall' ) );
149
  break;
150
 
151
  case 'google_recaptcha_style_comments' :
152
- $sName = __( 'CAPTCHA', 'wp-simple-firewall' );
153
- $sSummary = __( 'Enable CAPTCHA To Protect Against SPAM Comments', 'wp-simple-firewall' );
154
- $sDescription = [
 
 
155
  __( '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' )
156
  ];
157
  if ( !$mod->getCaptchaCfg()->ready ) {
158
- $sDescription[] = sprintf( '<a href="%s">%s</a>',
159
  $this->getCon()
160
  ->getModule_Plugin()
161
  ->getUrl_DirectLinkToSection( 'section_third_party_captcha' ),
@@ -165,24 +189,24 @@ class Strings extends Base\Strings {
165
  break;
166
 
167
  case 'custom_message_alert' :
168
- $sName = __( 'GASP Alert Message', 'wp-simple-firewall' );
169
- $sSummary = __( 'If you want a custom alert message, please provide this here', 'wp-simple-firewall' );
170
- $sDescription = __( "This alert message is displayed when a visitor attempts to submit a comment without checking the box.", 'wp-simple-firewall' )
171
- .'<br />'.sprintf( __( 'Default Message: %s', 'wp-simple-firewall' ), __( "Please check the box to confirm you're not a spammer", 'wp-simple-firewall' ) );
172
  break;
173
 
174
  case 'custom_message_comment_wait' :
175
- $sName = __( 'GASP Wait Message', 'wp-simple-firewall' );
176
- $sSummary = __( 'If you want a custom submit-button wait message, please provide this here.', 'wp-simple-firewall' );
177
- $sDescription = __( "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' )
178
- .'<br />'.sprintf( __( 'Default Message: %s', 'wp-simple-firewall' ), __( 'Please wait %s seconds before posting your comment', 'wp-simple-firewall' ) );
179
  break;
180
 
181
  case 'custom_message_comment_reload' :
182
- $sName = __( 'GASP Reload Message', 'wp-simple-firewall' );
183
- $sSummary = __( 'If you want a custom message when the comment token has expired, please provide this here.', 'wp-simple-firewall' );
184
- $sDescription = __( 'This message is displayed on the submit-button when the comment token is expired', 'wp-simple-firewall' )
185
- .'<br />'.sprintf( __( 'Default Message: %s', 'wp-simple-firewall' ), __( "Please reload this page to post a comment", 'wp-simple-firewall' ) );
186
  break;
187
 
188
  default:
@@ -190,9 +214,9 @@ class Strings extends Base\Strings {
190
  }
191
 
192
  return [
193
- 'name' => $sName,
194
- 'summary' => $sSummary,
195
- 'description' => $sDescription,
196
  ];
197
  }
198
  }
12
  */
13
  protected function getAuditMessages() :array {
14
  return [
15
+ 'spam_block_antibot' => [ __( 'Blocked SPAM comment that failed AntiBot tests.', 'wp-simple-firewall' ) ],
16
  'spam_block_human' => [
17
  __( 'Blocked human SPAM comment containing suspicious content.', 'wp-simple-firewall' ),
18
  __( 'Human SPAM filter found "%s" in "%s"', 'wp-simple-firewall' )
95
  switch ( $key ) {
96
 
97
  case 'enable_comments_filter' :
98
+ $name = sprintf( __( 'Enable %s Module', 'wp-simple-firewall' ), $modName );
99
+ $summary = __( 'Enable (or Disable) The Comment SPAM Protection Feature', 'wp-simple-firewall' );
100
+ $desc = sprintf( __( 'Un-Checking this option will completely disable the %s module.', 'wp-simple-firewall' ), __( 'Comment SPAM Protection', 'wp-simple-firewall' ) );
101
  break;
102
 
103
  case 'trusted_commenter_minimum' :
104
+ $name = __( 'Trusted Commenter Minimum', 'wp-simple-firewall' );
105
+ $summary = __( 'Minimum Number Of Approved Comments Before Commenter Is Trusted', 'wp-simple-firewall' );
106
+ $desc = __( 'Specify how many approved comments must exist before a commenter is trusted and their comments are no longer scanned.', 'wp-simple-firewall' )
107
+ .'<br />'.__( 'Normally WordPress will trust after 1 comment.', 'wp-simple-firewall' );
108
  break;
109
 
110
  case 'trusted_user_roles' :
111
+ $name = __( 'Trusted User Roles', 'wp-simple-firewall' );
112
+ $summary = __( "Comments From Users With These Roles Will Never Be Scanned", 'wp-simple-firewall' );
113
+ $desc = __( "Shield doesn't normally scan comments from logged-in or registered users.", 'wp-simple-firewall' )
114
+ .'<br />'.__( "Specify user roles here that shouldn't be scanned.", 'wp-simple-firewall' )
115
+ .'<br/>'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( 'Take a new line for each user role.', 'wp-simple-firewall' ) )
116
+ .'<br/>'.sprintf( '%s: %s', __( 'Available Roles', 'wp-simple-firewall' ), implode( ', ', Services::WpUsers()
117
+ ->getAvailableUserRoles() ) );
118
+ break;
119
+
120
+ case 'enable_antibot_check' :
121
+ $name = __( 'AntiBot Detection Engine', 'wp-simple-firewall' );
122
+ $summary = __( "Use AntiBot Detection Engine To Detect SPAM Bots", 'wp-simple-firewall' );
123
+ $desc = [
124
+ sprintf( __( "AntiBot Detection Engine is %s's exclusive bot-detection technology that removes the needs for CAPTCHA and other challenges.", 'wp-simple-firewall' ),
125
+ $this->getCon()->getHumanName() ),
126
+ __( 'This feature is designed to replace the CAPTCHA and Bot Protection options.', 'wp-simple-firewall' ),
127
+ sprintf( '%s - %s', __( 'Important', 'wp-simple-firewall' ),
128
+ __( "Switching on this feature will disable the CAPTCHA and Bot Protection settings.", 'wp-simple-firewall' ) )
129
+ ];
130
  break;
131
 
132
  case 'enable_comments_human_spam_filter' :
133
+ $name = __( 'Human SPAM Filter', 'wp-simple-firewall' );
134
+ $summary = sprintf( __( 'Enable (or Disable) The %s Feature', 'wp-simple-firewall' ), __( 'Human SPAM Filter', 'wp-simple-firewall' ) );
135
+ $desc = [
136
+ __( 'Most SPAM is automatic, by bots, but sometimes Humans also post comments to your site and these bypass Bot Detection rules.', 'wp-simple-firewall' ),
137
+ __( 'When this happens, you can scan the content for keywords that are typical of SPAM.', 'wp-simple-firewall' ),
138
+ ];
139
  break;
140
 
141
  case 'comments_default_action_human_spam' :
142
+ $name = __( 'SPAM Action', 'wp-simple-firewall' );
143
+ $summary = __( 'How To Categorise Comments When Identified To Be SPAM', 'wp-simple-firewall' );
144
+ $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>' );
145
  break;
146
 
147
  case 'enable_comments_gasp_protection' :
148
+ $name = __( 'SPAM Bot Protection', 'wp-simple-firewall' );
149
+ $summary = __( 'Block 100% Comment SPAM From Automated Bots', 'wp-simple-firewall' );
150
+ $desc = [
151
+ sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ),
152
+ __( "Use the newer AntiBot Detection Engine to detect SPAM instead of CAPTCHAs.", 'wp-simple-firewall' ) ),
153
+
154
+ __( 'Highly effective detection for the most common types of comment SPAM.', 'wp-simple-firewall' ),
155
+ sprintf( '%s: %s', __( 'Bonus', 'wp-simple-firewall' ), __( "Unlike Akismet, your data is never sent off-site to 3rd party processing servers.", 'wp-simple-firewall' ) )
156
+ ];
157
  break;
158
 
159
  case 'comments_default_action_spam_bot' :
160
+ $name = __( 'SPAM Action', 'wp-simple-firewall' );
161
+ $summary = __( 'Where To Put SPAM Comments', 'wp-simple-firewall' );
162
+ $desc = sprintf( __( 'When a comment is detected as being SPAM, %s will put the comment in the specified folder.', 'wp-simple-firewall' ),
163
+ $this->getCon()->getHumanName() );
164
  break;
165
 
166
  case 'custom_message_checkbox' :
167
+ $name = __( 'GASP Checkbox Message', 'wp-simple-firewall' );
168
+ $summary = __( 'If you want a custom checkbox message, please provide this here', 'wp-simple-firewall' );
169
+ $desc = __( "You can customise the message beside the checkbox.", 'wp-simple-firewall' )
170
+ .'<br />'.sprintf( __( 'Default Message: %s', 'wp-simple-firewall' ), __( "Please check the box to confirm you're not a spammer", 'wp-simple-firewall' ) );
171
  break;
172
 
173
  case 'google_recaptcha_style_comments' :
174
+ $name = __( 'CAPTCHA', 'wp-simple-firewall' );
175
+ $summary = __( 'Enable CAPTCHA To Protect Against SPAM Comments', 'wp-simple-firewall' );
176
+ $desc = [
177
+ sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ),
178
+ __( "Use the newer AntiBot Detection Engine to detect SPAM instead of CAPTCHAs.", 'wp-simple-firewall' ) ),
179
  __( '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' )
180
  ];
181
  if ( !$mod->getCaptchaCfg()->ready ) {
182
+ $desc[] = sprintf( '<a href="%s">%s</a>',
183
  $this->getCon()
184
  ->getModule_Plugin()
185
  ->getUrl_DirectLinkToSection( 'section_third_party_captcha' ),
189
  break;
190
 
191
  case 'custom_message_alert' :
192
+ $name = __( 'GASP Alert Message', 'wp-simple-firewall' );
193
+ $summary = __( 'If you want a custom alert message, please provide this here', 'wp-simple-firewall' );
194
+ $desc = __( "This alert message is displayed when a visitor attempts to submit a comment without checking the box.", 'wp-simple-firewall' )
195
+ .'<br />'.sprintf( __( 'Default Message: %s', 'wp-simple-firewall' ), __( "Please check the box to confirm you're not a spammer", 'wp-simple-firewall' ) );
196
  break;
197
 
198
  case 'custom_message_comment_wait' :
199
+ $name = __( 'GASP Wait Message', 'wp-simple-firewall' );
200
+ $summary = __( 'If you want a custom submit-button wait message, please provide this here.', 'wp-simple-firewall' );
201
+ $desc = __( "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' )
202
+ .'<br />'.sprintf( __( 'Default Message: %s', 'wp-simple-firewall' ), __( 'Please wait %s seconds before posting your comment', 'wp-simple-firewall' ) );
203
  break;
204
 
205
  case 'custom_message_comment_reload' :
206
+ $name = __( 'GASP Reload Message', 'wp-simple-firewall' );
207
+ $summary = __( 'If you want a custom message when the comment token has expired, please provide this here.', 'wp-simple-firewall' );
208
+ $desc = __( 'This message is displayed on the submit-button when the comment token is expired', 'wp-simple-firewall' )
209
+ .'<br />'.sprintf( __( 'Default Message: %s', 'wp-simple-firewall' ), __( "Please reload this page to post a comment", 'wp-simple-firewall' ) );
210
  break;
211
 
212
  default:
214
  }
215
 
216
  return [
217
+ 'name' => $name,
218
+ 'summary' => $summary,
219
+ 'description' => $desc,
220
  ];
221
  }
222
  }
src/lib/src/Modules/CommentsFilter/Upgrade.php CHANGED
@@ -7,15 +7,15 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
7
  class Upgrade extends Base\Upgrade {
8
 
9
  protected function upgrade_905() {
10
- /** @var Options $oOpts */
11
- $oOpts = $this->getOptions();
12
- $oOpts->setOpt(
13
  'comments_default_action_human_spam',
14
- (string)$oOpts->getOpt( 'comments_default_action_human_spam' )
15
  );
16
- $oOpts->setOpt(
17
  'comments_default_action_spam_bot',
18
- (string)$oOpts->getOpt( 'comments_default_action_spam_bot' )
19
  );
20
  }
21
 
7
  class Upgrade extends Base\Upgrade {
8
 
9
  protected function upgrade_905() {
10
+ /** @var Options $opts */
11
+ $opts = $this->getOptions();
12
+ $opts->setOpt(
13
  'comments_default_action_human_spam',
14
+ (string)$opts->getOpt( 'comments_default_action_human_spam' )
15
  );
16
+ $opts->setOpt(
17
  'comments_default_action_spam_bot',
18
+ (string)$opts->getOpt( 'comments_default_action_spam_bot' )
19
  );
20
  }
21
 
src/lib/src/Modules/Events/AjaxHandler.php DELETED
@@ -1,66 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Events;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Events;
7
- use FernleafSystems\Wordpress\Services\Services;
8
-
9
- class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
10
-
11
- protected function processAjaxAction( string $action ) :array {
12
-
13
- switch ( $action ) {
14
- case 'render_chart':
15
- $aResponse = $this->ajaxExec_RenderChart();
16
- break;
17
-
18
- case 'render_chart_post':
19
- $aResponse = $this->ajaxExec_RenderChartPost();
20
- break;
21
-
22
- default:
23
- $aResponse = parent::processAjaxAction( $action );
24
- }
25
-
26
- return $aResponse;
27
- }
28
-
29
- private function ajaxExec_RenderChart() :array {
30
- /** @var ModCon $mod */
31
- $mod = $this->getMod();
32
-
33
- $aParams = $this->getAjaxFormParams();
34
- $oReq = new Events\Charts\ChartRequestVO();
35
- $oReq->render_location = $aParams[ 'render_location' ];
36
- $oReq->chart_params = $aParams[ 'chart_params' ];
37
- $aChart = ( new Events\Charts\BuildData() )
38
- ->setMod( $mod )
39
- ->build( $oReq );
40
-
41
- return [
42
- 'success' => true,
43
- 'message' => 'no message',
44
- 'chart' => $aChart
45
- ];
46
- }
47
-
48
- private function ajaxExec_RenderChartPost() :array {
49
- /** @var ModCon $mod */
50
- $mod = $this->getMod();
51
- $req = Services::Request();
52
- $oChartReq = new Events\Charts\ChartRequestVO();
53
- $oChartReq->render_location = $req->post( 'render_location' );
54
- $oChartReq->chart_params = $req->post( 'chart_params' );
55
-
56
- $aChart = ( new Events\Charts\BuildData() )
57
- ->setMod( $mod )
58
- ->build( $oChartReq );
59
-
60
- return [
61
- 'success' => true,
62
- 'message' => 'no message',
63
- 'chart' => $aChart
64
- ];
65
- }
66
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Events/Charts/BuildData.php DELETED
@@ -1,131 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Events\Charts;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Events;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Events\ModCon;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
- use FernleafSystems\Wordpress\Services\Services;
9
-
10
- class BuildData {
11
-
12
- use ModConsumer;
13
-
14
- /**
15
- * @param ChartRequestVO $req
16
- * @return array
17
- */
18
- public function build( ChartRequestVO $req ) {
19
- /** @var ModCon $mod */
20
- $mod = $this->getMod();
21
-
22
- $this->preProcessRequest( $req );
23
-
24
- /** @var Events\Handler $oDbhEvts */
25
- $oDbhEvts = $mod->getDbHandler_Events();
26
-
27
- $nTick = 0;
28
- $oTime = Services::Request()->carbon();
29
-
30
- $aLabels = [];
31
- $aSeries = [];
32
- do {
33
- $aLabels[] = $oTime->toDateString();
34
-
35
- /** @var Events\Select $oSelEvts */
36
- $oSelEvts = $oDbhEvts->getQuerySelector();
37
- switch ( $req->interval ) {
38
- case 'hourly':
39
- $oSelEvts->filterByBoundary_Hour( $oTime->timestamp );
40
- $oTime->subHour();
41
- break;
42
- case 'daily':
43
- $oSelEvts->filterByBoundary_Day( $oTime->timestamp );
44
- $oTime->subDay();
45
- break;
46
- case 'weekly':
47
- $oSelEvts->filterByBoundary_Week( $oTime->timestamp );
48
- $oTime->subWeek();
49
- break;
50
- case 'monthly':
51
- $oSelEvts->filterByBoundary_Month( $oTime->timestamp );
52
- $oTime->subMonth();
53
- break;
54
- case 'yearly':
55
- $oSelEvts->filterByBoundary_Year( $oTime->timestamp );
56
- $oTime->subYear();
57
- break;
58
- }
59
-
60
- $aSeries[] = $oSelEvts->sumEvents( $req->events );
61
-
62
- $nTick++;
63
- } while ( $nTick < $req->ticks );
64
-
65
- return [
66
- 'data' => [
67
- 'labels' => [],
68
- 'series' => [
69
- array_reverse( $aSeries ),
70
- ]
71
- ],
72
- 'legend_names' => [],
73
- ];
74
- }
75
-
76
- /**
77
- * @param ChartRequestVO $oReq
78
- */
79
- protected function preProcessRequest( ChartRequestVO $oReq ) {
80
-
81
- if ( empty( $oReq->interval ) ) {
82
- switch ( $oReq->render_location ) {
83
- case $oReq::LOCATION_STATCARD:
84
- $oReq->interval = 'daily';
85
- break;
86
- default:
87
- $oReq->interval = 'weekly';
88
- break;
89
- }
90
- }
91
-
92
- $aAll = array_keys( $this->getCon()->loadEventsService()->getEvents() );
93
- if ( !empty( $oReq->chart_params[ 'stat_id' ] ) ) {
94
- switch ( $oReq->chart_params[ 'stat_id' ] ) {
95
- case 'comment_block':
96
- $oReq->events = array_filter(
97
- $aAll,
98
- function ( $sEvent ) {
99
- return strpos( $sEvent, 'spam_block_' ) === 0;
100
- }
101
- );
102
- break;
103
- case 'bot_blocks':
104
- $oReq->events = array_filter(
105
- $aAll,
106
- function ( $sEvent ) {
107
- return strpos( $sEvent, 'bottrack_' ) === 0;
108
- }
109
- );
110
- break;
111
- default:
112
- $oReq->events = (array)$oReq->chart_params[ 'stat_id' ];
113
- break;
114
- }
115
- }
116
-
117
- if ( empty( $oReq->ticks ) ) {
118
- switch ( $oReq->interval ) {
119
- case 'daily':
120
- $oReq->ticks = 7;
121
- break;
122
- case 'weekly':
123
- $oReq->ticks = 8;
124
- break;
125
- default:
126
- $oReq->ticks = 12;
127
- break;
128
- }
129
- }
130
- }
131
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Events/Charts/ChartRequestVO.php DELETED
@@ -1,20 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Events\Charts;
4
-
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
-
7
- /**
8
- * Class ChartRequestVO
9
- * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Events\Charts
10
- * @property string $render_location
11
- * @property string $interval
12
- * @property string $ticks
13
- * @property string[] $events
14
- * @property array $chart_params
15
- */
16
- class ChartRequestVO {
17
-
18
- const LOCATION_STATCARD = 'insights-overview-statcard';
19
- use StdClassAdapter;
20
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Events/Lib/Reports/KeyStats.php CHANGED
@@ -32,6 +32,7 @@ class KeyStats extends BaseReporter {
32
  'bottrack_loginfailed',
33
  'bottrack_logininvalid',
34
  'bottrack_xmlrpc',
 
35
  'spam_block_bot',
36
  'spam_block_recaptcha',
37
  'spam_block_human',
32
  'bottrack_loginfailed',
33
  'bottrack_logininvalid',
34
  'bottrack_xmlrpc',
35
+ 'bottrack_invalidscript',
36
  'spam_block_bot',
37
  'spam_block_recaptcha',
38
  'spam_block_human',
src/lib/src/Modules/Events/Lib/StatsWriter.php CHANGED
@@ -27,9 +27,9 @@ class StatsWriter extends EventsListener {
27
 
28
  protected function onShutdown() {
29
  if ( !$this->getCon()->plugin_deleting ) {
30
- /** @var Handler $oDbH */
31
- $oDbH = $this->getDbHandler();
32
- $oDbH->commitEvents( $this->getEventStats() );
33
  $this->setEventStats();
34
  }
35
  }
27
 
28
  protected function onShutdown() {
29
  if ( !$this->getCon()->plugin_deleting ) {
30
+ /** @var Handler $dbh */
31
+ $dbh = $this->getDbHandler();
32
+ $dbh->commitEvents( $this->getEventStats() );
33
  $this->setEventStats();
34
  }
35
  }
src/lib/src/Modules/Events/Processor.php CHANGED
@@ -2,8 +2,8 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Events;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
 
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Events;
8
 
9
  class Processor extends BaseShield\Processor {
@@ -32,16 +32,16 @@ class Processor extends BaseShield\Processor {
32
  }
33
 
34
  public function statsWidget() {
35
- /** @var Databases\Events\Select $oSelEvents */
36
- $oSelEvents = $this->getCon()
37
- ->getModule_Events()
38
- ->getDbHandler_Events()
39
- ->getQuerySelector();
40
 
41
  $aKeyStats = [
42
  'comments' => [
43
  __( 'Comment Blocks', 'wp-simple-firewall' ),
44
- $oSelEvents->clearWheres()->sumEvents( [
45
  'spam_block_bot',
46
  'spam_block_human',
47
  'spam_block_recaptcha'
@@ -49,38 +49,36 @@ class Processor extends BaseShield\Processor {
49
  ],
50
  'firewall' => [
51
  __( 'Firewall Blocks', 'wp-simple-firewall' ),
52
- $oSelEvents->clearWheres()->sumEvent( 'firewall_block' )
53
  ],
54
  'login_fail' => [
55
  __( 'Login Blocks', 'wp-simple-firewall' ),
56
- $oSelEvents->clearWheres()->sumEvent( 'login_block' )
57
  ],
58
  'login_verified' => [
59
  __( 'Login Verified', 'wp-simple-firewall' ),
60
- $oSelEvents->clearWheres()->sumEvent( '2fa_success' )
61
  ],
62
  'session_start' => [
63
  __( 'User Sessions', 'wp-simple-firewall' ),
64
- $oSelEvents->clearWheres()->sumEvent( 'session_start' )
65
  ],
66
  'ip_killed' => [
67
  __( 'IP Blocks', 'wp-simple-firewall' ),
68
- $oSelEvents->clearWheres()->sumEvent( 'conn_kill' )
69
  ],
70
  'ip_transgressions' => [
71
  __( 'Total Offenses', 'wp-simple-firewall' ),
72
- $oSelEvents->clearWheres()->sumEvent( 'ip_offense' )
73
  ],
74
  ];
75
 
76
- $aDisplayData = [
77
- 'sHeading' => sprintf( __( '%s Statistics', 'wp-simple-firewall' ), $this->getCon()->getHumanName() ),
78
- 'aKeyStats' => $aKeyStats,
79
- ];
80
-
81
  echo $this->getMod()->renderTemplate(
82
  'snippets/widget_dashboard_statistics.php',
83
- $aDisplayData
 
 
 
84
  );
85
  }
86
 
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Events;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Events;
8
 
9
  class Processor extends BaseShield\Processor {
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'
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
 
src/lib/src/Modules/Events/Strings.php CHANGED
@@ -7,12 +7,11 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
7
  class Strings extends Base\Strings {
8
 
9
  /**
10
- * @param string $sKey
11
  * @return string
12
  */
13
- public function getEventName( $sKey ) {
14
- $aE = $this->getEventNames();
15
- return empty( $aE[ $sKey ] ) ? '' : $aE[ $sKey ];
16
  }
17
 
18
  /**
@@ -44,28 +43,32 @@ class Strings extends Base\Strings {
44
  ),
45
  'bottrack_fakewebcrawler' => sprintf( '%s: %s',
46
  __( 'Bot Detection', 'wp-simple-firewall' ),
47
- __( 'Fake Web Crawler' )
48
  ),
49
  'bottrack_linkcheese' => sprintf( '%s: %s',
50
  __( 'Bot Detection', 'wp-simple-firewall' ),
51
- __( 'Link Cheese' )
52
  ),
53
  'bottrack_loginfailed' => sprintf( '%s: %s',
54
  __( 'Bot Detection', 'wp-simple-firewall' ),
55
- __( 'Failed Login' )
56
  ),
57
  'bottrack_logininvalid' => sprintf( '%s: %s',
58
  __( 'Bot Detection', 'wp-simple-firewall' ),
59
- __( 'Invalid Username Login' )
60
  ),
61
  'bottrack_useragent' => sprintf( '%s: %s',
62
  __( 'Bot Detection', 'wp-simple-firewall' ),
63
- __( 'Invalid User-Agent' )
64
  ),
65
  'bottrack_xmlrpc' => sprintf( '%s: %s',
66
  __( 'Bot Detection', 'wp-simple-firewall' ),
67
  'XML-RPC'
68
  ),
 
 
 
 
69
  'apc_alert_sent' => sprintf( '%s: %s',
70
  __( 'Alert Sent', 'wp-simple-firewall' ),
71
  __( 'Abandoned Plugin Detected', 'wp-simple-firewall' )
@@ -163,7 +166,7 @@ class Strings extends Base\Strings {
163
  'cooldown_fail' => __( '', 'wp-simple-firewall' ),
164
  'honeypot_fail' => __( '', 'wp-simple-firewall' ),
165
  'botbox_fail' => __( '', 'wp-simple-firewall' ),
166
- 'login_block' => __( '', 'wp-simple-firewall' ),
167
  'hide_login_url' => __( '', 'wp-simple-firewall' ),
168
  '2fa_success' => __( '', 'wp-simple-firewall' ),
169
  'check_skip' => __( '', 'wp-simple-firewall' ),
@@ -219,7 +222,7 @@ class Strings extends Base\Strings {
219
  ),
220
  'spam_block_recaptcha' => sprintf( '%s: %s',
221
  __( 'SPAM Blocked', 'wp-simple-firewall' ),
222
- __( 'reCAPTCHA', 'wp-simple-firewall' )
223
  ),
224
  'spam_block_human' => sprintf( '%s: %s',
225
  __( 'SPAM Blocked', 'wp-simple-firewall' ),
7
  class Strings extends Base\Strings {
8
 
9
  /**
10
+ * @param string $eventKey
11
  * @return string
12
  */
13
+ public function getEventName( $eventKey ) {
14
+ return $this->getEventNames()[ $eventKey ] ?? '';
 
15
  }
16
 
17
  /**
43
  ),
44
  'bottrack_fakewebcrawler' => sprintf( '%s: %s',
45
  __( 'Bot Detection', 'wp-simple-firewall' ),
46
+ __( 'Fake Web Crawler', 'wp-simple-firewall' )
47
  ),
48
  'bottrack_linkcheese' => sprintf( '%s: %s',
49
  __( 'Bot Detection', 'wp-simple-firewall' ),
50
+ __( 'Link Cheese', 'wp-simple-firewall' )
51
  ),
52
  'bottrack_loginfailed' => sprintf( '%s: %s',
53
  __( 'Bot Detection', 'wp-simple-firewall' ),
54
+ __( 'Failed Login', 'wp-simple-firewall' )
55
  ),
56
  'bottrack_logininvalid' => sprintf( '%s: %s',
57
  __( 'Bot Detection', 'wp-simple-firewall' ),
58
+ __( 'Invalid Username Login', 'wp-simple-firewall' )
59
  ),
60
  'bottrack_useragent' => sprintf( '%s: %s',
61
  __( 'Bot Detection', 'wp-simple-firewall' ),
62
+ __( 'Invalid User-Agent', 'wp-simple-firewall' )
63
  ),
64
  'bottrack_xmlrpc' => sprintf( '%s: %s',
65
  __( 'Bot Detection', 'wp-simple-firewall' ),
66
  'XML-RPC'
67
  ),
68
+ 'bottrack_invalidscript' => sprintf( '%s: %s',
69
+ __( 'Bot Detection', 'wp-simple-firewall' ),
70
+ __( 'Invalid Script Load', 'wp-simple-firewall' )
71
+ ),
72
  'apc_alert_sent' => sprintf( '%s: %s',
73
  __( 'Alert Sent', 'wp-simple-firewall' ),
74
  __( 'Abandoned Plugin Detected', 'wp-simple-firewall' )
166
  'cooldown_fail' => __( '', 'wp-simple-firewall' ),
167
  'honeypot_fail' => __( '', 'wp-simple-firewall' ),
168
  'botbox_fail' => __( '', 'wp-simple-firewall' ),
169
+ 'login_block' => __( 'Blocked Login', 'wp-simple-firewall' ),
170
  'hide_login_url' => __( '', 'wp-simple-firewall' ),
171
  '2fa_success' => __( '', 'wp-simple-firewall' ),
172
  'check_skip' => __( '', 'wp-simple-firewall' ),
222
  ),
223
  'spam_block_recaptcha' => sprintf( '%s: %s',
224
  __( 'SPAM Blocked', 'wp-simple-firewall' ),
225
+ __( 'CAPTCHA', 'wp-simple-firewall' )
226
  ),
227
  'spam_block_human' => sprintf( '%s: %s',
228
  __( 'SPAM Blocked', 'wp-simple-firewall' ),
src/lib/src/Modules/GeoIp/Lookup.php CHANGED
@@ -12,7 +12,7 @@ class Lookup {
12
  use Databases\Base\HandlerConsumer;
13
  use IpAddressConsumer;
14
 
15
- private $aIP = [];
16
 
17
  /**
18
  * @return Databases\GeoIp\EntryVO|null
@@ -20,8 +20,8 @@ class Lookup {
20
  public function lookupIp() {
21
  $ip = $this->getIP();
22
  // Small optimization so we don't SQL it every time.
23
- if ( isset( $this->aIP[ $ip ] ) ) {
24
- return $this->aIP[ $ip ];
25
  }
26
 
27
  /** @var Databases\GeoIp\Handler $dbh */
@@ -39,11 +39,10 @@ class Lookup {
39
  $IP = new Databases\GeoIp\EntryVO();
40
  $IP->ip = $ip;
41
  $IP->meta = $this->redirectliIpLookup();
42
- /** @var Databases\GeoIp\Insert $oIsrt */
43
  $dbh->getQueryInserter()->insert( $IP );
44
  }
45
 
46
- $this->aIP[ $ip ] = $IP;
47
  return $IP;
48
  }
49
 
12
  use Databases\Base\HandlerConsumer;
13
  use IpAddressConsumer;
14
 
15
+ private $ips = [];
16
 
17
  /**
18
  * @return Databases\GeoIp\EntryVO|null
20
  public function lookupIp() {
21
  $ip = $this->getIP();
22
  // Small optimization so we don't SQL it every time.
23
+ if ( isset( $this->ips[ $ip ] ) ) {
24
+ return $this->ips[ $ip ];
25
  }
26
 
27
  /** @var Databases\GeoIp\Handler $dbh */
39
  $IP = new Databases\GeoIp\EntryVO();
40
  $IP->ip = $ip;
41
  $IP->meta = $this->redirectliIpLookup();
 
42
  $dbh->getQueryInserter()->insert( $IP );
43
  }
44
 
45
+ $this->ips[ $ip ] = $IP;
46
  return $IP;
47
  }
48
 
src/lib/src/Modules/HackGuard/AjaxHandler.php CHANGED
@@ -124,7 +124,7 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
124
  $FS = Services::WpFs();
125
 
126
  $nRID = Services::Request()->post( 'rid' );
127
- $aData = [
128
  'error' => '',
129
  'success' => false,
130
  'flags' => [
@@ -165,42 +165,42 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
165
  ];
166
  try {
167
 
168
- $oLock = $oFLCon->getFileLock( $nRID );
169
- $bDiff = $oLock->detected_at > 0;
170
- $aData[ 'ajax' ] = $oFLCon->createFileDownloadLinks( $oLock );
171
- $aData[ 'flags' ][ 'has_diff' ] = $bDiff;
172
- $aData[ 'html' ][ 'diff' ] = $bDiff ?
173
  ( new FileLocker\Ops\PerformAction() )
174
  ->setMod( $this->getMod() )
175
  ->run( $nRID, 'diff' ) : '';
176
 
177
  $oCarb = Services::Request()->carbon( true );
178
- $aData[ 'vars' ][ 'locked_at' ] = $oCarb->setTimestamp( $oLock->created_at )->diffForHumans();
179
- $aData[ 'vars' ][ 'file_modified_at' ] =
180
- Services::WpGeneral()->getTimeStampForDisplay( $FS->getModifiedTime( $oLock->file ) );
181
- $aData[ 'vars' ][ 'change_detected_at' ] = $oCarb->setTimestamp( $oLock->detected_at )->diffForHumans();
182
- $aData[ 'vars' ][ 'file_size_locked' ] = Shield\Utilities\Tool\FormatBytes::Format( strlen(
183
  ( new FileLocker\Ops\ReadOriginalFileContent() )
184
  ->setMod( $mod )
185
- ->run( $oLock )
186
  ), 3 );
187
- $aData[ 'vars' ][ 'file_size_modified' ] = $FS->exists( $oLock->file ) ?
188
- Shield\Utilities\Tool\FormatBytes::Format( $FS->getFileSize( $oLock->file ), 3 )
189
  : 0;
190
- $aData[ 'vars' ][ 'file_name' ] = basename( $oLock->file );
191
- $aData[ 'success' ] = true;
192
  }
193
  catch ( \Exception $e ) {
194
- $aData[ 'error' ] = $e->getMessage();
195
  }
196
 
197
  return [
198
- 'success' => $aData[ 'success' ],
199
- 'message' => $aData[ 'error' ],
200
  'html' => $this->getMod()
201
  ->renderTemplate(
202
  '/wpadmin_pages/insights/scans/realtime/file_locker/file_diff.twig',
203
- $aData,
204
  true
205
  )
206
  ];
124
  $FS = Services::WpFs();
125
 
126
  $nRID = Services::Request()->post( 'rid' );
127
+ $data = [
128
  'error' => '',
129
  'success' => false,
130
  'flags' => [
165
  ];
166
  try {
167
 
168
+ $lock = $oFLCon->getFileLock( $nRID );
169
+ $bDiff = $lock->detected_at > 0;
170
+ $data[ 'ajax' ] = $oFLCon->createFileDownloadLinks( $lock );
171
+ $data[ 'flags' ][ 'has_diff' ] = $bDiff;
172
+ $data[ 'html' ][ 'diff' ] = $bDiff ?
173
  ( new FileLocker\Ops\PerformAction() )
174
  ->setMod( $this->getMod() )
175
  ->run( $nRID, 'diff' ) : '';
176
 
177
  $oCarb = Services::Request()->carbon( true );
178
+ $data[ 'vars' ][ 'locked_at' ] = $oCarb->setTimestamp( $lock->created_at )->diffForHumans();
179
+ $data[ 'vars' ][ 'file_modified_at' ] =
180
+ Services::WpGeneral()->getTimeStampForDisplay( $FS->getModifiedTime( $lock->file ) );
181
+ $data[ 'vars' ][ 'change_detected_at' ] = $oCarb->setTimestamp( $lock->detected_at )->diffForHumans();
182
+ $data[ 'vars' ][ 'file_size_locked' ] = Shield\Utilities\Tool\FormatBytes::Format( strlen(
183
  ( new FileLocker\Ops\ReadOriginalFileContent() )
184
  ->setMod( $mod )
185
+ ->run( $lock )
186
  ), 3 );
187
+ $data[ 'vars' ][ 'file_size_modified' ] = $FS->exists( $lock->file ) ?
188
+ Shield\Utilities\Tool\FormatBytes::Format( $FS->getFileSize( $lock->file ), 3 )
189
  : 0;
190
+ $data[ 'vars' ][ 'file_name' ] = basename( $lock->file );
191
+ $data[ 'success' ] = true;
192
  }
193
  catch ( \Exception $e ) {
194
+ $data[ 'error' ] = $e->getMessage();
195
  }
196
 
197
  return [
198
+ 'success' => $data[ 'success' ],
199
+ 'message' => $data[ 'error' ],
200
  'html' => $this->getMod()
201
  ->renderTemplate(
202
  '/wpadmin_pages/insights/scans/realtime/file_locker/file_diff.twig',
203
+ $data,
204
  true
205
  )
206
  ];
src/lib/src/Modules/HackGuard/Debug.php CHANGED
@@ -3,17 +3,10 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Scans\Mal\Signatures;
7
- use FernleafSystems\Wordpress\Services\Utilities\Net\IpIdentify;
8
 
9
  class Debug extends Modules\Base\Debug {
10
 
11
  public function run() {
12
- $this->dumpSigs();
13
  die();
14
  }
15
-
16
- private function dumpSigs() {
17
- var_dump(Signatures::getAll());
18
- }
19
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
 
 
6
 
7
  class Debug extends Modules\Base\Debug {
8
 
9
  public function run() {
 
10
  die();
11
  }
 
 
 
 
12
  }
src/lib/src/Modules/HackGuard/Lib/FileLocker/File.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  /**
@@ -15,21 +15,21 @@ use FernleafSystems\Wordpress\Services\Services;
15
  */
16
  class File {
17
 
18
- use StdClassAdapter;
19
 
20
- public function __construct( $sFilename, $sDir = ABSPATH ) {
21
- $this->file = $sFilename;
22
- $this->dir = wp_normalize_path( $sDir );
23
  }
24
 
25
  /**
26
  * @return string[]
27
  */
28
- public function getExistingPossiblePaths() {
29
  return array_filter(
30
  $this->getPossiblePaths(),
31
- function ( $sPath ) {
32
- return Services::WpFs()->isFile( $sPath );
33
  }
34
  );
35
  }
@@ -37,7 +37,7 @@ class File {
37
  /**
38
  * @return string[]
39
  */
40
- public function getPossiblePaths() {
41
  $paths = [];
42
  $dirCount = 0;
43
  $workingDir = realpath( $this->dir );
@@ -57,10 +57,7 @@ class File {
57
  return $paths;
58
  }
59
 
60
- /**
61
- * @return int
62
- */
63
- protected function getMaxDirLevels() {
64
  return (int)max( 1, (int)$this->max_levels );
65
  }
66
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynProperties;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  /**
15
  */
16
  class File {
17
 
18
+ use DynProperties;
19
 
20
+ public function __construct( string $filename, $dir = ABSPATH ) {
21
+ $this->file = $filename;
22
+ $this->dir = wp_normalize_path( $dir );
23
  }
24
 
25
  /**
26
  * @return string[]
27
  */
28
+ public function getExistingPossiblePaths() :array {
29
  return array_filter(
30
  $this->getPossiblePaths(),
31
+ function ( $path ) {
32
+ return !empty( $path ) && Services::WpFs()->isFile( $path );
33
  }
34
  );
35
  }
37
  /**
38
  * @return string[]
39
  */
40
+ public function getPossiblePaths() :array {
41
  $paths = [];
42
  $dirCount = 0;
43
  $workingDir = realpath( $this->dir );
57
  return $paths;
58
  }
59
 
60
+ protected function getMaxDirLevels() :int {
 
 
 
61
  return (int)max( 1, (int)$this->max_levels );
62
  }
63
  }
src/lib/src/Modules/HackGuard/Lib/FileLocker/FileLockerController.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\FileLocker;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
@@ -12,12 +12,15 @@ use FernleafSystems\Wordpress\Services\Services;
12
  class FileLockerController {
13
 
14
  use Modules\ModConsumer;
15
- use OneTimeExecute;
16
 
17
- /**
18
- * @return bool
19
- */
20
- public function isEnabled() {
 
 
 
21
  /** @var HackGuard\Options $opts */
22
  $opts = $this->getOptions();
23
  return ( count( $opts->getFilesToLock() ) > 0 )
@@ -27,46 +30,30 @@ class FileLockerController {
27
  ->canHandshake();
28
  }
29
 
30
- /**
31
- * @return bool
32
- */
33
- protected function canRun() {
34
- /** @var HackGuard\ModCon $mod */
35
- $mod = $this->getMod();
36
- return $this->isEnabled() && $mod->getDbHandler_FileLocker()->isReady();
37
- }
38
-
39
  protected function run() {
40
- add_filter( $this->getCon()->prefix( 'admin_bar_menu_items' ), [ $this, 'addAdminMenuBarItem' ], 100 );
 
 
 
41
 
42
- add_action( $this->getCon()->prefix( 'plugin_shutdown' ), function () {
43
- if ( !$this->getCon()->plugin_deactivating ) {
44
- if ( $this->getOptions()->isOptChanged( 'file_locker' ) ) {
45
- $this->deleteAllLocks();
46
- }
47
- else {
48
- $this->runAnalysis();
49
- }
50
- }
51
- } );
52
  }
53
 
54
- /**
55
- * @param array $aItems
56
- * @return array
57
- */
58
- public function addAdminMenuBarItem( array $aItems ) {
59
- $nCountFL = $this->countProblems();
60
- if ( $nCountFL > 0 ) {
61
- $aItems[] = [
62
  'id' => $this->getCon()->prefix( 'filelocker_problems' ),
63
  'title' => __( 'File Locker', 'wp-simple-firewall' )
64
- .sprintf( '<div class="wp-core-ui wp-ui-notification shield-counter"><span aria-hidden="true">%s</span></div>', $nCountFL ),
65
  'href' => $this->getCon()->getModule_Insights()->getUrl_SubInsightsPage( 'scans' ),
66
- 'warnings' => $nCountFL
67
  ];
68
  }
69
- return $aItems;
70
  }
71
 
72
  /**
@@ -79,43 +66,47 @@ class FileLockerController {
79
  }
80
 
81
  /**
82
- * @param FileLocker\EntryVO $VO
83
  * @return string[]
84
  */
85
- public function createFileDownloadLinks( $VO ) {
86
  /** @var HackGuard\ModCon $mod */
87
  $mod = $this->getMod();
88
- $aLinks = [];
89
- foreach ( [ 'original', 'current' ] as $sType ) {
90
- $aActionNonce = $mod->getNonceActionData( 'filelocker_download_'.$sType );
91
- $aActionNonce[ 'rid' ] = $VO->id;
92
- $aActionNonce[ 'rand' ] = rand();
93
- $aLinks[ $sType ] = add_query_arg( $aActionNonce, $mod->getUrl_AdminPage() );
 
94
  }
95
- return $aLinks;
96
  }
97
 
98
  public function handleFileDownloadRequest() {
99
- $oReq = Services::Request();
100
- $oLock = $this->getFileLock( (int)$oReq->query( 'rid', 0 ) );
101
 
102
- if ( $oLock instanceof FileLocker\EntryVO ) {
103
- $sType = str_replace( 'filelocker_download_', '', $oReq->query( 'exec' ) );
104
 
105
  // Note: Download what's on the disk if nothing is changed.
106
- if ( $sType == 'current' ) {
107
- $sContent = Services::WpFs()->getFileContent( $oLock->file );
108
  }
109
- elseif ( $sType == 'original' ) {
110
- $sContent = ( new Lib\FileLocker\Ops\ReadOriginalFileContent() )
111
  ->setMod( $this->getMod() )
112
- ->run( $oLock );
 
 
 
113
  }
114
 
115
- if ( !empty( $sContent ) ) {
116
  header( 'Set-Cookie: fileDownload=true; path=/' );
117
  Services::Response()
118
- ->downloadStringAsFile( $sContent, strtoupper( $sType ).'-'.basename( $oLock->file ) );
119
  }
120
  }
121
 
@@ -135,52 +126,69 @@ class FileLockerController {
135
  }
136
 
137
  /**
138
- * @param int $nID
139
  * @return FileLocker\EntryVO|null
140
  */
141
- public function getFileLock( $nID ) {
142
- $aLocks = ( new Lib\FileLocker\Ops\LoadFileLocks() )
143
- ->setMod( $this->getMod() )
144
- ->loadLocks();
145
- return isset( $aLocks[ $nID ] ) ? $aLocks[ $nID ] : null;
146
  }
147
 
148
  private function runAnalysis() {
149
- /** @var Modules\HackGuard\Options $oOpts */
150
- $oOpts = $this->getOptions();
151
-
152
  // 1. First assess the existing locks for changes.
153
  ( new Ops\AssessLocks() )
154
  ->setMod( $this->getMod() )
155
  ->run();
156
 
157
- // 2. Create new file locks as required
158
- foreach ( $oOpts->getFilesToLock() as $sFileKey ) {
159
- try {
160
- ( new Ops\CreateFileLocks() )
161
- ->setMod( $this->getMod() )
162
- ->setWorkingFile( $this->getFile( $sFileKey ) )
163
- ->create();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  }
165
- catch ( \Exception $e ) {
166
- error_log( $e->getMessage() );
 
167
  }
168
  }
169
  }
170
 
171
  /**
172
- * @param string $sFileKey
173
  * @return File|null
174
  * @throws \Exception
175
  */
176
- private function getFile( $sFileKey ) {
177
  $oFile = null;
178
 
179
  $bIsSplitWp = false;
180
  $nMaxPaths = 0;
181
- switch ( $sFileKey ) {
182
  case 'wpconfig':
183
- $sFileKey = 'wp-config.php';
184
  $nMaxPaths = 1;
185
  $nLevels = $bIsSplitWp ? 3 : 2;
186
 
@@ -192,21 +200,21 @@ class FileLockerController {
192
  break;
193
 
194
  case 'root_htaccess':
195
- $sFileKey = '.htaccess';
196
  $nLevels = $bIsSplitWp ? 2 : 1;
197
  break;
198
 
199
  case 'root_webconfig':
200
- $sFileKey = 'Web.Config';
201
  $nLevels = $bIsSplitWp ? 2 : 1;
202
  break;
203
 
204
  case 'root_index':
205
- $sFileKey = 'index.php';
206
  $nLevels = $bIsSplitWp ? 2 : 1;
207
  break;
208
  default:
209
- if ( Services::WpFs()->isAbsPath( $sFileKey ) && Services::WpFs()->isFile( $sFileKey ) ) {
210
  $nLevels = 1;
211
  $nMaxPaths = 1;
212
  }
@@ -215,9 +223,24 @@ class FileLockerController {
215
  }
216
  break;
217
  }
218
- $oFile = new File( $sFileKey );
219
  $oFile->max_levels = $nLevels;
220
  $oFile->max_paths = $nMaxPaths;
221
  return $oFile;
222
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\FileLocker;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
12
  class FileLockerController {
13
 
14
  use Modules\ModConsumer;
15
+ use ExecOnce;
16
 
17
+ protected function canRun() :bool {
18
+ /** @var HackGuard\ModCon $mod */
19
+ $mod = $this->getMod();
20
+ return $this->isEnabled() && $mod->getDbHandler_FileLocker()->isReady();
21
+ }
22
+
23
+ public function isEnabled() :bool {
24
  /** @var HackGuard\Options $opts */
25
  $opts = $this->getOptions();
26
  return ( count( $opts->getFilesToLock() ) > 0 )
30
  ->canHandshake();
31
  }
32
 
 
 
 
 
 
 
 
 
 
33
  protected function run() {
34
+ $con = $this->getCon();
35
+ add_filter( $con->prefix( 'admin_bar_menu_items' ), [ $this, 'addAdminMenuBarItem' ], 100 );
36
+ add_action( $con->prefix( 'pre_plugin_shutdown' ), [ $this, 'processFileLocks' ] );
37
+ }
38
 
39
+ public function processFileLocks() {
40
+ if ( !$this->getCon()->plugin_deactivating && !$this->getCon()->is_my_upgrade ) {
41
+ $this->getOptions()->isOptChanged( 'file_locker' ) ? $this->deleteAllLocks() : $this->runAnalysis();
42
+ }
 
 
 
 
 
 
43
  }
44
 
45
+ public function addAdminMenuBarItem( array $items ) :array {
46
+ $problems = $this->countProblems();
47
+ if ( $problems > 0 ) {
48
+ $items[] = [
 
 
 
 
49
  'id' => $this->getCon()->prefix( 'filelocker_problems' ),
50
  'title' => __( 'File Locker', 'wp-simple-firewall' )
51
+ .sprintf( '<div class="wp-core-ui wp-ui-notification shield-counter"><span aria-hidden="true">%s</span></div>', $problems ),
52
  'href' => $this->getCon()->getModule_Insights()->getUrl_SubInsightsPage( 'scans' ),
53
+ 'warnings' => $problems
54
  ];
55
  }
56
+ return $items;
57
  }
58
 
59
  /**
66
  }
67
 
68
  /**
69
+ * @param FileLocker\EntryVO $lock
70
  * @return string[]
71
  */
72
+ public function createFileDownloadLinks( $lock ) :array {
73
  /** @var HackGuard\ModCon $mod */
74
  $mod = $this->getMod();
75
+ $links = [];
76
+ foreach ( [ 'original', 'current' ] as $type ) {
77
+ $links[ $type ] = $mod->createFileDownloadLink( 'filelocker', [
78
+ 'type' => $type,
79
+ 'rid' => $lock->id,
80
+ 'rand' => uniqid(),
81
+ ] );
82
  }
83
+ return $links;
84
  }
85
 
86
  public function handleFileDownloadRequest() {
87
+ $req = Services::Request();
88
+ $lock = $this->getFileLock( (int)$req->query( 'rid', 0 ) );
89
 
90
+ if ( $lock instanceof FileLocker\EntryVO ) {
91
+ $type = $req->query( 'type' );
92
 
93
  // Note: Download what's on the disk if nothing is changed.
94
+ if ( $type == 'current' ) {
95
+ $content = Services::WpFs()->getFileContent( $lock->file );
96
  }
97
+ elseif ( $type == 'original' ) {
98
+ $content = ( new Lib\FileLocker\Ops\ReadOriginalFileContent() )
99
  ->setMod( $this->getMod() )
100
+ ->run( $lock );
101
+ }
102
+ else {
103
+ throw new \Exception( 'Invalid file locker type download' );
104
  }
105
 
106
+ if ( !empty( $content ) ) {
107
  header( 'Set-Cookie: fileDownload=true; path=/' );
108
  Services::Response()
109
+ ->downloadStringAsFile( $content, strtoupper( $type ).'-'.basename( $lock->file ) );
110
  }
111
  }
112
 
126
  }
127
 
128
  /**
129
+ * @param int $ID
130
  * @return FileLocker\EntryVO|null
131
  */
132
+ public function getFileLock( $ID ) {
133
+ return ( new Lib\FileLocker\Ops\LoadFileLocks() )
134
+ ->setMod( $this->getMod() )
135
+ ->loadLocks()[ $ID ] ?? null;
 
136
  }
137
 
138
  private function runAnalysis() {
 
 
 
139
  // 1. First assess the existing locks for changes.
140
  ( new Ops\AssessLocks() )
141
  ->setMod( $this->getMod() )
142
  ->run();
143
 
144
+ // 2. Create any outstanding locks.
145
+ $this->runLocksCreation();
146
+ }
147
+
148
+ /**
149
+ * There's at least 15 seconds between each attempt to create a file lock.
150
+ * This ensures our API isn't bombarded by sites that, for some reason, fail to store the lock in the DB.
151
+ */
152
+ private function runLocksCreation() {
153
+ /** @var Modules\HackGuard\Options $opts */
154
+ $opts = $this->getOptions();
155
+ $filesToLock = $opts->getFilesToLock();
156
+
157
+ $state = $this->getState();
158
+ if ( !empty( $filesToLock ) && Services::Request()->ts() - $state[ 'last_locks_created_at' ] > 60 ) {
159
+
160
+ $lockCreated = false;
161
+ foreach ( $opts->getFilesToLock() as $fileKey ) {
162
+ try {
163
+ $lockCreated = ( new Ops\CreateFileLocks() )
164
+ ->setMod( $this->getMod() )
165
+ ->setWorkingFile( $this->getFile( $fileKey ) )
166
+ ->create();
167
+ }
168
+ catch ( \Exception $e ) {
169
+ error_log( $e->getMessage() );
170
+ }
171
  }
172
+ if ( $lockCreated ) {
173
+ $state[ 'last_locks_created_at' ] = Services::Request()->ts();
174
+ $this->setState( $state );
175
  }
176
  }
177
  }
178
 
179
  /**
180
+ * @param string $fileKey
181
  * @return File|null
182
  * @throws \Exception
183
  */
184
+ private function getFile( $fileKey ) {
185
  $oFile = null;
186
 
187
  $bIsSplitWp = false;
188
  $nMaxPaths = 0;
189
+ switch ( $fileKey ) {
190
  case 'wpconfig':
191
+ $fileKey = 'wp-config.php';
192
  $nMaxPaths = 1;
193
  $nLevels = $bIsSplitWp ? 3 : 2;
194
 
200
  break;
201
 
202
  case 'root_htaccess':
203
+ $fileKey = '.htaccess';
204
  $nLevels = $bIsSplitWp ? 2 : 1;
205
  break;
206
 
207
  case 'root_webconfig':
208
+ $fileKey = 'Web.Config';
209
  $nLevels = $bIsSplitWp ? 2 : 1;
210
  break;
211
 
212
  case 'root_index':
213
+ $fileKey = 'index.php';
214
  $nLevels = $bIsSplitWp ? 2 : 1;
215
  break;
216
  default:
217
+ if ( Services::WpFs()->isAbsPath( $fileKey ) && Services::WpFs()->isFile( $fileKey ) ) {
218
  $nLevels = 1;
219
  $nMaxPaths = 1;
220
  }
223
  }
224
  break;
225
  }
226
+ $oFile = new File( $fileKey );
227
  $oFile->max_levels = $nLevels;
228
  $oFile->max_paths = $nMaxPaths;
229
  return $oFile;
230
  }
231
+
232
+ protected function getState() :array {
233
+ /** @var HackGuard\Options $opts */
234
+ $opts = $this->getOptions();
235
+ return array_merge(
236
+ [
237
+ 'last_locks_created_at' => 0
238
+ ],
239
+ is_array( $opts->getOpt( 'filelocker_state' ) ) ? $opts->getOpt( 'filelocker_state' ) : []
240
+ );
241
+ }
242
+
243
+ protected function setState( array $state ) {
244
+ $this->getOptions()->setOpt( 'filelocker_state', $state );
245
+ }
246
  }
src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/Accept.php CHANGED
@@ -13,24 +13,24 @@ use FernleafSystems\Wordpress\Services\Services;
13
  class Accept extends BaseOps {
14
 
15
  /**
16
- * @param FileLocker\EntryVO $oLock
17
  * @return bool
18
  * @throws \ErrorException
19
  */
20
- public function run( $oLock ) {
21
  /** @var ModCon $mod */
22
  $mod = $this->getMod();
23
 
24
  $aPublicKey = $this->getPublicKey();
25
- $sRawContent = ( new BuildEncryptedFilePayload() )
26
  ->setMod( $mod )
27
- ->build( $oLock->file, reset( $aPublicKey ) );
28
 
29
- /** @var FileLocker\Update $oUpdater */
30
- $oUpdater = $mod->getDbHandler_FileLocker()->getQueryUpdater();
31
- $bSuccess = $oUpdater->updateEntry( $oLock, [
32
- 'hash_original' => hash_file( 'sha1', $oLock->file ),
33
- 'content' => base64_encode( $sRawContent ),
34
  'public_key_id' => key( $aPublicKey ),
35
  'detected_at' => 0,
36
  'updated_at' => Services::Request()->ts(),
@@ -38,6 +38,6 @@ class Accept extends BaseOps {
38
  ] );
39
 
40
  $this->clearFileLocksCache();
41
- return $bSuccess;
42
  }
43
  }
13
  class Accept extends BaseOps {
14
 
15
  /**
16
+ * @param FileLocker\EntryVO $lock
17
  * @return bool
18
  * @throws \ErrorException
19
  */
20
+ public function run( $lock ) {
21
  /** @var ModCon $mod */
22
  $mod = $this->getMod();
23
 
24
  $aPublicKey = $this->getPublicKey();
25
+ $raw = ( new BuildEncryptedFilePayload() )
26
  ->setMod( $mod )
27
+ ->build( $lock->file, reset( $aPublicKey ) );
28
 
29
+ /** @var FileLocker\Update $updater */
30
+ $updater = $mod->getDbHandler_FileLocker()->getQueryUpdater();
31
+ $success = $updater->updateEntry( $lock, [
32
+ 'hash_original' => hash_file( 'sha1', $lock->file ),
33
+ 'content' => base64_encode( $raw ),
34
  'public_key_id' => key( $aPublicKey ),
35
  'detected_at' => 0,
36
  'updated_at' => Services::Request()->ts(),
38
  ] );
39
 
40
  $this->clearFileLocksCache();
41
+ return $success;
42
  }
43
  }
src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/AssessLocks.php CHANGED
@@ -14,30 +14,30 @@ class AssessLocks extends BaseOps {
14
  public function run() {
15
  /** @var ModCon $mod */
16
  $mod = $this->getMod();
17
- /** @var FileLocker\Update $oUpd */
18
- $oUpd = $mod->getDbHandler_FileLocker()->getQueryUpdater();
19
 
20
  $this->removeDuplicates();
21
 
22
  $aProblemIds = [];
23
- foreach ( $this->getFileLocks() as $oLock ) {
24
  try {
25
- if ( ( new CompareHash() )->isEqualFileSha1( $oLock->file, $oLock->hash_original ) ) {
26
- if ( !empty( $oLock->hash_current ) ) {
27
- $oUpd->updateCurrentHash( $oLock, '' );
28
  }
29
  }
30
  else {
31
- $sFileHash = hash_file( 'sha1', $oLock->file );
32
- if ( empty( $oLock->hash_current ) || !hash_equals( $oLock->hash_current, $sFileHash ) ) {
33
- $oUpd->updateCurrentHash( $oLock, $sFileHash );
34
- $aProblemIds[] = $oLock->id;
35
  }
36
  }
37
  }
38
  catch ( \InvalidArgumentException $e ) {
39
- $oUpd->markProblem( $oLock );
40
- $aProblemIds[] = $oLock->id;
41
  }
42
  }
43
  $this->clearFileLocksCache();
@@ -45,20 +45,20 @@ class AssessLocks extends BaseOps {
45
  }
46
 
47
  private function removeDuplicates() {
48
- $aPaths = [];
49
- foreach ( $this->getFileLocks() as $oLock ) {
50
- if ( in_array( $oLock->file, $aPaths ) ) {
51
  /** @var ModCon $mod */
52
  $mod = $this->getMod();
53
  $mod->getDbHandler_FileLocker()
54
  ->getQueryDeleter()
55
- ->deleteById( $oLock->id );
56
  }
57
  else {
58
- $aPaths[] = $oLock->file;
59
  }
60
  }
61
- if ( count( $this->getFileLocks() ) != count( $aPaths ) ) {
62
  $this->clearFileLocksCache();
63
  }
64
  }
14
  public function run() {
15
  /** @var ModCon $mod */
16
  $mod = $this->getMod();
17
+ /** @var FileLocker\Update $updater */
18
+ $updater = $mod->getDbHandler_FileLocker()->getQueryUpdater();
19
 
20
  $this->removeDuplicates();
21
 
22
  $aProblemIds = [];
23
+ foreach ( $this->getFileLocks() as $lock ) {
24
  try {
25
+ if ( ( new CompareHash() )->isEqualFileSha1( $lock->file, $lock->hash_original ) ) {
26
+ if ( !empty( $lock->hash_current ) ) {
27
+ $updater->updateCurrentHash( $lock, '' );
28
  }
29
  }
30
  else {
31
+ $fileHash = hash_file( 'sha1', $lock->file );
32
+ if ( empty( $lock->hash_current ) || !hash_equals( $lock->hash_current, $fileHash ) ) {
33
+ $updater->updateCurrentHash( $lock, $fileHash );
34
+ $aProblemIds[] = $lock->id;
35
  }
36
  }
37
  }
38
  catch ( \InvalidArgumentException $e ) {
39
+ $updater->markProblem( $lock );
40
+ $aProblemIds[] = $lock->id;
41
  }
42
  }
43
  $this->clearFileLocksCache();
45
  }
46
 
47
  private function removeDuplicates() {
48
+ $paths = [];
49
+ foreach ( $this->getFileLocks() as $lock ) {
50
+ if ( in_array( $lock->file, $paths ) ) {
51
  /** @var ModCon $mod */
52
  $mod = $this->getMod();
53
  $mod->getDbHandler_FileLocker()
54
  ->getQueryDeleter()
55
+ ->deleteById( $lock->id );
56
  }
57
  else {
58
+ $paths[] = $lock->file;
59
  }
60
  }
61
+ if ( count( $this->getFileLocks() ) != count( $paths ) ) {
62
  $this->clearFileLocksCache();
63
  }
64
  }
src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/BaseOps.php CHANGED
@@ -50,14 +50,14 @@ class BaseOps {
50
  * @return array
51
  * @throws \ErrorException
52
  */
53
- protected function getPublicKey() {
54
- $aPublicKey = ( new GetPublicKey() )
55
  ->setMod( $this->getMod() )
56
  ->retrieve();
57
- if ( empty( $aPublicKey ) ) {
58
  throw new \ErrorException( 'Cannot encrypt without a public key' );
59
  }
60
- return $aPublicKey;
61
  }
62
 
63
  /**
50
  * @return array
51
  * @throws \ErrorException
52
  */
53
+ protected function getPublicKey() :array {
54
+ $key = ( new GetPublicKey() )
55
  ->setMod( $this->getMod() )
56
  ->retrieve();
57
+ if ( empty( $key ) || !is_array( $key ) ) {
58
  throw new \ErrorException( 'Cannot encrypt without a public key' );
59
  }
60
+ return $key;
61
  }
62
 
63
  /**
src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/CreateFileLocks.php CHANGED
@@ -15,44 +15,46 @@ class CreateFileLocks extends BaseOps {
15
  /**
16
  * @throws \Exception
17
  */
18
- public function create() {
19
-
20
- foreach ( $this->oFile->getExistingPossiblePaths() as $sPath ) {
21
- $oTheFileLock = null;
22
- foreach ( $this->getFileLocks() as $oMaybeFileLock ) {
23
- if ( $oMaybeFileLock->file === $sPath ) {
24
- $oTheFileLock = $oMaybeFileLock;
25
  break;
26
  }
27
  }
28
- if ( !$oTheFileLock instanceof FileLocker\EntryVO ) {
29
- $this->processPath( $sPath );
 
30
  }
31
  }
 
32
  }
33
 
34
  /**
35
  * @param string $path
36
  * @throws \Exception
37
  */
38
- private function processPath( $path ) {
39
  /** @var ModCon $mod */
40
  $mod = $this->getMod();
41
 
42
  if ( Services::WpFs()->isFile( $path ) ) {
43
- $oEntry = new FileLocker\EntryVO();
44
- $oEntry->file = $path;
45
- $oEntry->hash_original = hash_file( 'sha1', $path );
46
 
47
- $aPublicKey = $this->getPublicKey();
48
- $oEntry->public_key_id = key( $aPublicKey );
49
- $oEntry->content = ( new BuildEncryptedFilePayload() )
50
  ->setMod( $mod )
51
- ->build( $path, reset( $aPublicKey ) );
52
 
53
  /** @var FileLocker\Insert $oInserter */
54
  $oInserter = $mod->getDbHandler_FileLocker()->getQueryInserter();
55
- $oInserter->insert( $oEntry );
56
 
57
  $this->clearFileLocksCache();
58
  }
15
  /**
16
  * @throws \Exception
17
  */
18
+ public function create() :bool {
19
+ $pathsProcessed = false;
20
+ foreach ( $this->oFile->getExistingPossiblePaths() as $path ) {
21
+ $theLock = null;
22
+ foreach ( $this->getFileLocks() as $maybeLock ) {
23
+ if ( $maybeLock->file === $path ) {
24
+ $theLock = $maybeLock;
25
  break;
26
  }
27
  }
28
+ if ( !$theLock instanceof FileLocker\EntryVO ) {
29
+ $this->processPath( $path );
30
+ $pathsProcessed = true;
31
  }
32
  }
33
+ return $pathsProcessed;
34
  }
35
 
36
  /**
37
  * @param string $path
38
  * @throws \Exception
39
  */
40
+ private function processPath( string $path ) {
41
  /** @var ModCon $mod */
42
  $mod = $this->getMod();
43
 
44
  if ( Services::WpFs()->isFile( $path ) ) {
45
+ $entry = new FileLocker\EntryVO();
46
+ $entry->file = $path;
47
+ $entry->hash_original = hash_file( 'sha1', $path );
48
 
49
+ $publicKey = $this->getPublicKey();
50
+ $entry->public_key_id = key( $publicKey );
51
+ $entry->content = ( new BuildEncryptedFilePayload() )
52
  ->setMod( $mod )
53
+ ->build( $path, reset( $publicKey ) );
54
 
55
  /** @var FileLocker\Insert $oInserter */
56
  $oInserter = $mod->getDbHandler_FileLocker()->getQueryInserter();
57
+ $success = $oInserter->insert( $entry );
58
 
59
  $this->clearFileLocksCache();
60
  }
src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/Diff.php CHANGED
@@ -13,34 +13,38 @@ use FernleafSystems\Wordpress\Services\Utilities\Integrations\WpHashes;
13
  class Diff extends BaseOps {
14
 
15
  /**
16
- * @param FileLocker\EntryVO $oLock
17
  * @return bool
18
  * @throws \Exception
19
  */
20
- public function run( $oLock ) {
21
 
22
  $oFS = Services::WpFs();
23
 
24
- if ( !$oFS->isFile( $oLock->file ) ) {
25
  throw new \Exception( __( 'File is missing or could not be read.', 'wp-simple-firewall' ) );
26
  }
27
 
28
- $sContent = Services::WpFs()->getFileContent( $oLock->file );
29
- if ( empty( $sContent ) ) {
30
  throw new \Exception( __( 'File is empty or could not be read.', 'wp-simple-firewall' ) );
31
  }
32
 
33
- $sOriginal = ( new ReadOriginalFileContent() )
34
  ->setMod( $this->getMod() )
35
- ->run( $oLock );
36
 
37
- $sDiff = $this->useWpDiff( $sOriginal, $sContent );
38
- // The WP Diff is empty if the only difference is white space
39
- if ( empty( $sDiff ) ) {
40
- $sDiff = $this->useWpHashes( $sOriginal, $sContent );
41
- }
 
 
 
 
42
 
43
- return $sDiff;
44
  }
45
 
46
  /**
13
  class Diff extends BaseOps {
14
 
15
  /**
16
+ * @param FileLocker\EntryVO $lock
17
  * @return bool
18
  * @throws \Exception
19
  */
20
+ public function run( $lock ) {
21
 
22
  $oFS = Services::WpFs();
23
 
24
+ if ( !$oFS->isFile( $lock->file ) ) {
25
  throw new \Exception( __( 'File is missing or could not be read.', 'wp-simple-firewall' ) );
26
  }
27
 
28
+ $current = Services::WpFs()->getFileContent( $lock->file );
29
+ if ( empty( $current ) ) {
30
  throw new \Exception( __( 'File is empty or could not be read.', 'wp-simple-firewall' ) );
31
  }
32
 
33
+ $original = ( new ReadOriginalFileContent() )
34
  ->setMod( $this->getMod() )
35
+ ->run( $lock );
36
 
37
+ /**
38
+ * The WP Diff is empty if the only difference is white space
39
+ * @since v10.3 always use WP Hashes DIFF
40
+ *
41
+ * $diff = $this->useWpDiff( $original, $current );
42
+ * if ( empty( $diff ) ) {
43
+ * $this->useWpHashes( $original, $current );
44
+ * }
45
+ */
46
 
47
+ return $this->useWpHashes( $original, $current );
48
  }
49
 
50
  /**
src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/LoadFileLocks.php CHANGED
@@ -29,10 +29,10 @@ class LoadFileLocks {
29
 
30
  self::$aFileLockRecords = [];
31
  if ( $mod->getFileLocker()->isEnabled() ) {
32
- $aAll = $mod->getDbHandler_FileLocker()->getQuerySelector()->all();
33
- if ( is_array( $aAll ) ) {
34
- foreach ( $aAll as $oLock ) {
35
- self::$aFileLockRecords[ $oLock->id ] = $oLock;
36
  }
37
  }
38
  }
@@ -46,8 +46,8 @@ class LoadFileLocks {
46
  public function withProblems() {
47
  return array_filter(
48
  $this->loadLocks(),
49
- function ( $oLock ) {
50
- return $oLock->detected_at > 0;
51
  }
52
  );
53
  }
@@ -58,8 +58,8 @@ class LoadFileLocks {
58
  public function withProblemsNotNotified() {
59
  return array_filter(
60
  $this->withProblems(),
61
- function ( $oLock ) {
62
- return $oLock->notified_at == 0;
63
  }
64
  );
65
  }
@@ -70,8 +70,8 @@ class LoadFileLocks {
70
  public function withoutProblems() {
71
  return array_filter(
72
  $this->loadLocks(),
73
- function ( $oLock ) {
74
- return $oLock->detected_at == 0;
75
  }
76
  );
77
  }
29
 
30
  self::$aFileLockRecords = [];
31
  if ( $mod->getFileLocker()->isEnabled() ) {
32
+ $all = $mod->getDbHandler_FileLocker()->getQuerySelector()->all();
33
+ if ( is_array( $all ) ) {
34
+ foreach ( $all as $lock ) {
35
+ self::$aFileLockRecords[ $lock->id ] = $lock;
36
  }
37
  }
38
  }
46
  public function withProblems() {
47
  return array_filter(
48
  $this->loadLocks(),
49
+ function ( $lock ) {
50
+ return $lock->detected_at > 0;
51
  }
52
  );
53
  }
58
  public function withProblemsNotNotified() {
59
  return array_filter(
60
  $this->withProblems(),
61
+ function ( $lock ) {
62
+ return $lock->notified_at == 0;
63
  }
64
  );
65
  }
70
  public function withoutProblems() {
71
  return array_filter(
72
  $this->loadLocks(),
73
+ function ( $lock ) {
74
+ return $lock->detected_at == 0;
75
  }
76
  );
77
  }
src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/PerformAction.php CHANGED
@@ -12,43 +12,43 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\ModCon;
12
  class PerformAction extends BaseOps {
13
 
14
  /**
15
- * @param int $nLockID
16
- * @param string $sAction
17
  * @return string
18
  * @throws \Exception
19
  */
20
- public function run( $nLockID, $sAction ) {
21
  /** @var ModCon $mod */
22
  $mod = $this->getMod();
23
 
24
- if ( !in_array( $sAction, [ 'accept', 'restore', 'diff' ] ) ) {
25
  throw new \Exception( __( 'Not a supported file lock action.', 'wp-simple-firewall' ) );
26
  }
27
- if ( !is_numeric( $nLockID ) ) {
28
  throw new \Exception( __( 'Please select a valid file.', 'wp-simple-firewall' ) );
29
  }
30
- $oLock = $mod->getDbHandler_FileLocker()
31
- ->getQuerySelector()
32
- ->byId( (int)$nLockID );
33
- if ( !$oLock instanceof Databases\FileLocker\EntryVO ) {
34
  throw new \Exception( __( 'Not valid file lock ID.', 'wp-simple-firewall' ) );
35
  }
36
 
37
- switch ( $sAction ) {
38
  case 'accept':
39
  $mResult = ( new Accept() )
40
  ->setMod( $this->getMod() )
41
- ->run( $oLock );
42
  break;
43
  case 'diff':
44
  $mResult = ( new Diff() )
45
  ->setMod( $this->getMod() )
46
- ->run( $oLock );
47
  break;
48
  case 'restore':
49
  $mResult = ( new Restore() )
50
  ->setMod( $this->getMod() )
51
- ->run( $oLock );
52
  break;
53
  default:
54
  $mResult = false;
12
  class PerformAction extends BaseOps {
13
 
14
  /**
15
+ * @param int $lockID
16
+ * @param string $action
17
  * @return string
18
  * @throws \Exception
19
  */
20
+ public function run( $lockID, $action ) {
21
  /** @var ModCon $mod */
22
  $mod = $this->getMod();
23
 
24
+ if ( !in_array( $action, [ 'accept', 'restore', 'diff' ] ) ) {
25
  throw new \Exception( __( 'Not a supported file lock action.', 'wp-simple-firewall' ) );
26
  }
27
+ if ( !is_numeric( $lockID ) ) {
28
  throw new \Exception( __( 'Please select a valid file.', 'wp-simple-firewall' ) );
29
  }
30
+ $lock = $mod->getDbHandler_FileLocker()
31
+ ->getQuerySelector()
32
+ ->byId( (int)$lockID );
33
+ if ( !$lock instanceof Databases\FileLocker\EntryVO ) {
34
  throw new \Exception( __( 'Not valid file lock ID.', 'wp-simple-firewall' ) );
35
  }
36
 
37
+ switch ( $action ) {
38
  case 'accept':
39
  $mResult = ( new Accept() )
40
  ->setMod( $this->getMod() )
41
+ ->run( $lock );
42
  break;
43
  case 'diff':
44
  $mResult = ( new Diff() )
45
  ->setMod( $this->getMod() )
46
+ ->run( $lock );
47
  break;
48
  case 'restore':
49
  $mResult = ( new Restore() )
50
  ->setMod( $this->getMod() )
51
+ ->run( $lock );
52
  break;
53
  default:
54
  $mResult = false;
src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/ReadOriginalFileContent.php CHANGED
@@ -28,33 +28,33 @@ class ReadOriginalFileContent extends BaseOps {
28
  }
29
 
30
  /**
31
- * @param Databases\FileLocker\EntryVO $oLock
32
  * @return string|null
33
  * @throws \Exception
34
  */
35
- private function useOriginalFile( Databases\FileLocker\EntryVO $oLock ) {
36
- $oFS = Services::WpFs();
37
- if ( empty( $oLock->detected_at ) && empty( $oLock->hash_current )
38
- && $oFS->exists( $oLock->file ) ) {
39
- return $oFS->getFileContent( $oLock->file );
40
  }
41
  throw new \Exception( 'Cannot use original file' );
42
  }
43
 
44
  /**
45
- * @param Databases\FileLocker\EntryVO $oLock
46
  * @return string|null
47
  */
48
- private function useCacheAndApi( Databases\FileLocker\EntryVO $oLock ) {
49
- $sCacheKey = 'file-content-'.$oLock->id;
50
- $sContent = wp_cache_get( $sCacheKey, $this->getCon()->prefix( 'filelocker' ) );
51
- if ( $sContent === false ) {
52
- $oVO = ( new OpenSslEncryptVo() )->applyFromArray( json_decode( $oLock->content, true ) );
53
- $sContent = ( new DecryptFile() )
54
  ->setMod( $this->getMod() )
55
- ->retrieve( $oVO, $oLock->public_key_id );
56
- wp_cache_set( $sCacheKey, $sContent, $this->getCon()->prefix( 'filelocker' ), 3 );
57
  }
58
- return $sContent;
59
  }
60
  }
28
  }
29
 
30
  /**
31
+ * @param Databases\FileLocker\EntryVO $lock
32
  * @return string|null
33
  * @throws \Exception
34
  */
35
+ private function useOriginalFile( Databases\FileLocker\EntryVO $lock ) {
36
+ $FS = Services::WpFs();
37
+ if ( empty( $lock->detected_at ) && empty( $lock->hash_current )
38
+ && $FS->exists( $lock->file ) ) {
39
+ return $FS->getFileContent( $lock->file );
40
  }
41
  throw new \Exception( 'Cannot use original file' );
42
  }
43
 
44
  /**
45
+ * @param Databases\FileLocker\EntryVO $lock
46
  * @return string|null
47
  */
48
+ private function useCacheAndApi( Databases\FileLocker\EntryVO $lock ) {
49
+ $sCacheKey = 'file-content-'.$lock->id;
50
+ $content = wp_cache_get( $sCacheKey, $this->getCon()->prefix( 'filelocker' ) );
51
+ if ( $content === false ) {
52
+ $VO = ( new OpenSslEncryptVo() )->applyFromArray( json_decode( $lock->content, true ) );
53
+ $content = ( new DecryptFile() )
54
  ->setMod( $this->getMod() )
55
+ ->retrieve( $VO, $lock->public_key_id );
56
+ wp_cache_set( $sCacheKey, $content, $this->getCon()->prefix( 'filelocker' ), 3 );
57
  }
58
+ return $content;
59
  }
60
  }
src/lib/src/Modules/HackGuard/Lib/Utility/FileDownloadHandler.php CHANGED
@@ -11,19 +11,19 @@ class FileDownloadHandler {
11
  use HandlerConsumer;
12
 
13
  /**
14
- * @param int $nItemId
15
  */
16
- public function downloadByItemId( $nItemId ) {
17
- /** @var Scanner\EntryVO $oEntry */
18
- $oEntry = $this->getDbHandler()
19
- ->getQuerySelector()
20
- ->byId( (int)$nItemId );
21
- if ( $oEntry instanceof Scanner\EntryVO && !empty( $oEntry->meta[ 'path_full' ] ) ) {
22
- $sPath = $oEntry->meta[ 'path_full' ];
23
- $oFs = Services::WpFs();
24
- if ( $oFs->isFile( $sPath ) ) {
25
  header( 'Set-Cookie: fileDownload=true; path=/' );
26
- Services::Response()->downloadStringAsFile( $oFs->getFileContent( $sPath ), basename( $sPath ) );
27
  }
28
  }
29
 
11
  use HandlerConsumer;
12
 
13
  /**
14
+ * @param int $itemID
15
  */
16
+ public function downloadByItemId( int $itemID ) {
17
+ /** @var Scanner\EntryVO $entry */
18
+ $entry = $this->getDbHandler()
19
+ ->getQuerySelector()
20
+ ->byId( (int)$itemID );
21
+ if ( $entry instanceof Scanner\EntryVO && !empty( $entry->meta[ 'path_full' ] ) ) {
22
+ $path = $entry->meta[ 'path_full' ];
23
+ $FS = Services::WpFs();
24
+ if ( $FS->isFile( $path ) ) {
25
  header( 'Set-Cookie: fileDownload=true; path=/' );
26
+ Services::Response()->downloadStringAsFile( $FS->getFileContent( $path ), basename( $path ) );
27
  }
28
  }
29
 
src/lib/src/Modules/HackGuard/ModCon.php CHANGED
@@ -74,19 +74,16 @@ class ModCon extends BaseShield\ModCon {
74
  ] );
75
  }
76
 
77
- protected function handleModAction( string $action ) {
78
- switch ( $action ) {
79
- case 'scan_file_download':
 
 
 
80
  ( new Lib\Utility\FileDownloadHandler() )
81
  ->setDbHandler( $this->getDbHandler_ScanResults() )
82
  ->downloadByItemId( (int)Services::Request()->query( 'rid', 0 ) );
83
  break;
84
- case 'filelocker_download_original':
85
- case 'filelocker_download_current':
86
- $this->getFileLocker()->handleFileDownloadRequest();
87
- break;
88
- default:
89
- break;
90
  }
91
  }
92
 
@@ -193,7 +190,8 @@ class ModCon extends BaseShield\ModCon {
193
  }
194
 
195
  public function getDbHandler_FileLocker() :Databases\FileLocker\Handler {
196
- return $this->getDbH( 'file_protect' );
 
197
  }
198
 
199
  public function getDbHandler_ScanQueue() :Databases\ScanQueue\Handler {
@@ -201,7 +199,7 @@ class ModCon extends BaseShield\ModCon {
201
  }
202
 
203
  public function getDbHandler_ScanResults() :Databases\Scanner\Handler {
204
- return $this->getDbH( 'scanresults' );
205
  }
206
 
207
  /**
@@ -218,9 +216,9 @@ class ModCon extends BaseShield\ModCon {
218
 
219
  public function onPluginDeactivate() {
220
  // 1. Clean out the scanners
221
- /** @var Options $oOpts */
222
- $oOpts = $this->getOptions();
223
- foreach ( $oOpts->getScanSlugs() as $slug ) {
224
  $this->getScanCon( $slug )->purge();
225
  }
226
  $this->getDbHandler_ScanQueue()->tableDelete();
74
  ] );
75
  }
76
 
77
+ protected function handleFileDownload( string $downloadID ) {
78
+ switch ( $downloadID ) {
79
+ case 'filelocker':
80
+ $this->getFileLocker()->handleFileDownloadRequest();
81
+ break;
82
+ case 'scan_file':
83
  ( new Lib\Utility\FileDownloadHandler() )
84
  ->setDbHandler( $this->getDbHandler_ScanResults() )
85
  ->downloadByItemId( (int)Services::Request()->query( 'rid', 0 ) );
86
  break;
 
 
 
 
 
 
87
  }
88
  }
89
 
190
  }
191
 
192
  public function getDbHandler_FileLocker() :Databases\FileLocker\Handler {
193
+ $new = $this->getDbH( 'filelocker' );
194
+ return empty( $new ) ? $this->getDbH( 'file_protect' ) : $new;
195
  }
196
 
197
  public function getDbHandler_ScanQueue() :Databases\ScanQueue\Handler {
199
  }
200
 
201
  public function getDbHandler_ScanResults() :Databases\Scanner\Handler {
202
+ return $this->getDbH( 'scanner' );
203
  }
204
 
205
  /**
216
 
217
  public function onPluginDeactivate() {
218
  // 1. Clean out the scanners
219
+ /** @var Options $opts */
220
+ $opts = $this->getOptions();
221
+ foreach ( $opts->getScanSlugs() as $slug ) {
222
  $this->getScanCon( $slug )->purge();
223
  }
224
  $this->getDbHandler_ScanQueue()->tableDelete();
src/lib/src/Modules/HackGuard/Scan/Controller/Base.php CHANGED
@@ -59,11 +59,7 @@ abstract class Base {
59
  }
60
 
61
  public function createFileDownloadLink( Databases\Scanner\EntryVO $entry ) :string {
62
- /** @var ModCon $mod */
63
- $mod = $this->getMod();
64
- $aActionNonce = $mod->getNonceActionData( 'scan_file_download' );
65
- $aActionNonce[ 'rid' ] = $entry->id;
66
- return add_query_arg( $aActionNonce, $mod->getUrl_AdminPage() );
67
  }
68
 
69
  public function getLastScanAt() :int {
59
  }
60
 
61
  public function createFileDownloadLink( Databases\Scanner\EntryVO $entry ) :string {
62
+ return $this->getMod()->createFileDownloadLink( 'scan_file', [ 'rid' => $entry->id ] );
 
 
 
 
63
  }
64
 
65
  public function getLastScanAt() :int {
src/lib/src/Modules/HackGuard/Scan/Queue/CompleteQueue.php CHANGED
@@ -25,20 +25,20 @@ class CompleteQueue {
25
  /** @var ModCon $mod */
26
  $mod = $this->getMod();
27
  $con = $this->getCon();
28
- /** @var Databases\ScanQueue\Handler $oDbH */
29
- $oDbH = $this->getDbHandler();
30
- $oSel = $oDbH->getQuerySelector();
31
 
32
- foreach ( $oSel->getDistinctForColumn( 'scan' ) as $sScanSlug ) {
33
 
34
- $oScanCon = $mod->getScanCon( $sScanSlug );
35
 
36
  $oResultsSet = ( new CollateResults() )
37
  ->setScanController( $oScanCon )
38
- ->setDbHandler( $oDbH )
39
- ->collate( $sScanSlug );
40
 
41
- $con->fireEvent( $sScanSlug.'_scan_run' );
42
 
43
  if ( $oResultsSet instanceof Scans\Base\BaseResultsSet ) {
44
  ( new HackGuard\Scan\Results\ResultsUpdate() )
@@ -46,14 +46,14 @@ class CompleteQueue {
46
  ->update( $oResultsSet );
47
 
48
  if ( $oResultsSet->countItems() > 0 ) {
49
- $con->fireEvent( $sScanSlug.'_scan_found' );
50
  }
51
  }
52
 
53
- /** @var Databases\ScanQueue\Delete $oDel */
54
- $oDel = $oDbH->getQueryDeleter();
55
- $oDel->filterByScan( $sScanSlug )
56
- ->query();
57
  }
58
 
59
  /** @var HackGuard\Options $opts */
25
  /** @var ModCon $mod */
26
  $mod = $this->getMod();
27
  $con = $this->getCon();
28
+ /** @var Databases\ScanQueue\Handler $dbh */
29
+ $dbh = $this->getDbHandler();
30
+ $oSel = $dbh->getQuerySelector();
31
 
32
+ foreach ( $oSel->getDistinctForColumn( 'scan' ) as $scanSlug ) {
33
 
34
+ $oScanCon = $mod->getScanCon( $scanSlug );
35
 
36
  $oResultsSet = ( new CollateResults() )
37
  ->setScanController( $oScanCon )
38
+ ->setDbHandler( $dbh )
39
+ ->collate( $scanSlug );
40
 
41
+ $con->fireEvent( $scanSlug.'_scan_run' );
42
 
43
  if ( $oResultsSet instanceof Scans\Base\BaseResultsSet ) {
44
  ( new HackGuard\Scan\Results\ResultsUpdate() )
46
  ->update( $oResultsSet );
47
 
48
  if ( $oResultsSet->countItems() > 0 ) {
49
+ $con->fireEvent( $scanSlug.'_scan_found' );
50
  }
51
  }
52
 
53
+ /** @var Databases\ScanQueue\Delete $deleter */
54
+ $deleter = $dbh->getQueryDeleter();
55
+ $deleter->filterByScan( $scanSlug )
56
+ ->query();
57
  }
58
 
59
  /** @var HackGuard\Options $opts */
src/lib/src/Modules/HackGuard/Scan/Queue/ConvertBetweenTypes.php CHANGED
@@ -39,7 +39,7 @@ class ConvertBetweenTypes {
39
  }
40
  unset( $oAction->items );
41
  unset( $oAction->results );
42
- $entry->meta = $oAction->getRawDataAsArray();
43
  return $entry;
44
  }
45
  }
39
  }
40
  unset( $oAction->items );
41
  unset( $oAction->results );
42
+ $entry->meta = $oAction->getRawData();
43
  return $entry;
44
  }
45
  }
src/lib/src/Modules/HackGuard/Scan/Queue/QueueProcessor.php CHANGED
@@ -96,12 +96,11 @@ class QueueProcessor extends Utilities\BackgroundProcessing\BackgroundProcess {
96
  * @return bool
97
  */
98
  protected function is_queue_empty() {
99
- /** @var ScanQueue\Select $oSel */
100
- $oSel = $this->getDbHandler()->getQuerySelector();
101
- $nUnfinished = $oSel->filterByNotStarted()
102
- ->filterByNotFinished()
103
- ->count();
104
- return $nUnfinished === 0;
105
  }
106
 
107
  /**
@@ -112,12 +111,12 @@ class QueueProcessor extends Utilities\BackgroundProcessing\BackgroundProcess {
112
  public function save() {
113
 
114
  if ( is_array( $this->data ) ) {
115
- /** @var ScanQueue\Insert $oInsert */
116
- $oInsert = $this->getDbHandler()->getQueryInserter();
117
- foreach ( $this->data as $nKey => $oEntry ) {
118
- /** @var ScanQueue\EntryVO $oEntry */
119
- if ( $oEntry instanceof ScanQueue\EntryVO ) {
120
- $oInsert->insert( $oEntry );
121
  }
122
  }
123
  }
@@ -134,18 +133,18 @@ class QueueProcessor extends Utilities\BackgroundProcessing\BackgroundProcess {
134
  * @return $this
135
  */
136
  public function update( $key, $data ) {
137
- /** @var ScanQueue\Update $oUpd */
138
- $oUpd = $this->getDbHandler()->getQueryUpdater();
139
- $oToUpdate = array_shift( $data );
140
- $oUpd->updateEntry( $oToUpdate );
141
  return $this;
142
  }
143
 
144
  public function handleExpiredItems() {
145
- $nBoundary = Services::Request()
146
- ->carbon()
147
- ->subSeconds( $this->getExpirationInterval() )->timestamp;
148
- $this->getDbHandler()->deleteRowsOlderThan( $nBoundary );
149
  }
150
 
151
  /**
96
  * @return bool
97
  */
98
  protected function is_queue_empty() {
99
+ /** @var ScanQueue\Select $selector */
100
+ $selector = $this->getDbHandler()->getQuerySelector();
101
+ return $selector->filterByNotStarted()
102
+ ->filterByNotFinished()
103
+ ->count() === 0;
 
104
  }
105
 
106
  /**
111
  public function save() {
112
 
113
  if ( is_array( $this->data ) ) {
114
+ /** @var ScanQueue\Insert $inserter */
115
+ $inserter = $this->getDbHandler()->getQueryInserter();
116
+ foreach ( $this->data as $nKey => $entry ) {
117
+ /** @var ScanQueue\EntryVO $entry */
118
+ if ( $entry instanceof ScanQueue\EntryVO ) {
119
+ $inserter->insert( $entry );
120
  }
121
  }
122
  }
133
  * @return $this
134
  */
135
  public function update( $key, $data ) {
136
+ /** @var ScanQueue\Update $updater */
137
+ $updater = $this->getDbHandler()->getQueryUpdater();
138
+ $entry = array_shift( $data );
139
+ $updater->updateById( $entry->id, $entry->getRawData() );
140
  return $this;
141
  }
142
 
143
  public function handleExpiredItems() {
144
+ $boundary = Services::Request()
145
+ ->carbon()
146
+ ->subSeconds( $this->getExpirationInterval() )->timestamp;
147
+ $this->getDbHandler()->deleteRowsOlderThan( $boundary );
148
  }
149
 
150
  /**
src/lib/src/Modules/HackGuard/Scan/Results/ResultsUpdate.php CHANGED
@@ -39,7 +39,7 @@ class ResultsUpdate {
39
  $oConverter = ( new ConvertBetweenTypes() )->setScanController( $oSCon );
40
  foreach ( $oConverter->fromResultsToVOs( $oExisting ) as $oVo ) {
41
  $oUp->reset()
42
- ->setUpdateData( $oVo->getRawDataAsArray() )
43
  ->setUpdateWheres(
44
  [
45
  'scan' => $oSCon->getSlug(),
39
  $oConverter = ( new ConvertBetweenTypes() )->setScanController( $oSCon );
40
  foreach ( $oConverter->fromResultsToVOs( $oExisting ) as $oVo ) {
41
  $oUp->reset()
42
+ ->setUpdateData( $oVo->getRawData() )
43
  ->setUpdateWheres(
44
  [
45
  'scan' => $oSCon->getSlug(),
src/lib/src/Modules/HackGuard/Scan/ScansController.php CHANGED
@@ -120,18 +120,18 @@ class ScansController {
120
  $opts = $this->getOptions();
121
 
122
  if ( $this->getCanScansExecute() ) {
123
- $aScans = [];
124
- foreach ( $opts->getScanSlugs() as $sScanSlug ) {
125
- $oScanCon = $mod->getScanCon( $sScanSlug );
126
- if ( $oScanCon->isScanningAvailable() && $oScanCon->isEnabled() ) {
127
- $aScans[] = $sScanSlug;
128
  }
129
  }
130
 
131
  $opts->setIsScanCron( true );
132
  $mod->saveModOptions()
133
  ->getScanQueueController()
134
- ->startScans( $aScans );
135
  }
136
  else {
137
  error_log( 'Shield scans cannot execute.' );
@@ -142,9 +142,15 @@ class ScansController {
142
  * @return string[]
143
  */
144
  public function getReasonsScansCantExecute() :array {
145
- return array_keys( array_filter( [
146
- 'reason_not_call_self' => !$this->getCon()->getModule_Plugin()->getCanSiteCallToItself()
147
- ] ) );
 
 
 
 
 
 
148
  }
149
 
150
  public function getCanScansExecute() :bool {
@@ -158,22 +164,30 @@ class ScansController {
158
  }
159
 
160
  public function getFirstRunTimestamp() :int {
161
- $c = Services::Request()->carbon( true );
162
- $c->addHours( $c->minute < 40 ? 0 : 1 )
163
- ->minute( $c->minute < 40 ? 45 : 15 )
164
- ->second( 0 );
165
 
166
- if ( $this->getCronFrequency() === 1 ) { // If it's a daily scan only, set to 3am by default
167
- $hour = (int)apply_filters( $this->getCon()->prefix( 'daily_scan_cron_hour' ), 3 );
168
- if ( $hour < 0 || $hour > 23 ) {
169
- $hour = 3;
170
- }
171
- if ( $c->hour >= $hour ) {
172
- $c->addDays( 1 );
 
 
 
 
 
 
 
 
 
173
  }
174
- $c->hour( $hour );
175
  }
176
 
 
 
 
 
177
  return $c->timestamp;
178
  }
179
 
120
  $opts = $this->getOptions();
121
 
122
  if ( $this->getCanScansExecute() ) {
123
+ $scans = [];
124
+ foreach ( $opts->getScanSlugs() as $slug ) {
125
+ $scanCon = $mod->getScanCon( $slug );
126
+ if ( $scanCon->isScanningAvailable() && $scanCon->isEnabled() ) {
127
+ $scans[] = $slug;
128
  }
129
  }
130
 
131
  $opts->setIsScanCron( true );
132
  $mod->saveModOptions()
133
  ->getScanQueueController()
134
+ ->startScans( $scans );
135
  }
136
  else {
137
  error_log( 'Shield scans cannot execute.' );
142
  * @return string[]
143
  */
144
  public function getReasonsScansCantExecute() :array {
145
+ try {
146
+ $reasons = array_keys( array_filter( [
147
+ 'reason_not_call_self' => !$this->getCon()->getModule_Plugin()->canSiteLoopback()
148
+ ] ) );
149
+ }
150
+ catch ( \Exception $e ) {
151
+ $reasons = [];
152
+ }
153
+ return $reasons;
154
  }
155
 
156
  public function getCanScansExecute() :bool {
164
  }
165
 
166
  public function getFirstRunTimestamp() :int {
 
 
 
 
167
 
168
+ $startHour = (int)apply_filters( 'shield/scan_cron_start_hour', 3 );
169
+ $startMinute = (int)apply_filters( 'shield/scan_cron_start_minute', (int)rand( 0, 59 ) );
170
+ if ( $startHour < 0 || $startHour > 23 ) {
171
+ $startHour = 3;
172
+ }
173
+ if ( $startMinute < 1 || $startMinute > 59 ) {
174
+ $startMinute = (int)rand( 0, 59 );
175
+ }
176
+
177
+ $c = Services::Request()->carbon( true );
178
+ if ( $c->hour > $startHour ) {
179
+ $c->addDays( 1 ); // Start on this hour, tomorrow
180
+ }
181
+ elseif ( $c->hour === $startHour ) {
182
+ if ( $c->minute >= $startMinute ) {
183
+ $c->addDays( 1 ); // Start on this minute, tomorrow
184
  }
 
185
  }
186
 
187
+ $c->hour( $startHour )
188
+ ->minute( $startMinute )
189
+ ->second( 0 );
190
+
191
  return $c->timestamp;
192
  }
193
 
src/lib/src/Modules/HackGuard/Scan/Utilities/WpvAddPluginRows.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan\Utilities;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan\Controller;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Scans\Wpv;
8
  use FernleafSystems\Wordpress\Services\Services;
@@ -10,7 +10,7 @@ use FernleafSystems\Wordpress\Services\Services;
10
  class WpvAddPluginRows {
11
 
12
  use Controller\ScanControllerConsumer;
13
- use OneTimeExecute;
14
 
15
  /**
16
  * @var int
@@ -26,7 +26,7 @@ class WpvAddPluginRows {
26
  $this->addPluginVulnerabilityRows();
27
  }
28
 
29
- protected function canRun() {
30
  return $this->isWpvulnPluginsHighlightEnabled() && $this->countVulnerablePlugins() > 0;
31
  }
32
 
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan\Utilities;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan\Controller;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Scans\Wpv;
8
  use FernleafSystems\Wordpress\Services\Services;
10
  class WpvAddPluginRows {
11
 
12
  use Controller\ScanControllerConsumer;
13
+ use ExecOnce;
14
 
15
  /**
16
  * @var int
26
  $this->addPluginVulnerabilityRows();
27
  }
28
 
29
+ protected function canRun() :bool {
30
  return $this->isWpvulnPluginsHighlightEnabled() && $this->countVulnerablePlugins() > 0;
31
  }
32
 
src/lib/src/Modules/HackGuard/Strings.php CHANGED
@@ -265,16 +265,18 @@ class Strings extends Base\Strings {
265
  $summary = __( 'Lock Files Against Tampering And Changes', 'wp-simple-firewall' );
266
  $desc = [
267
  __( 'Detects changes to the files, then lets you examine contents and revert as required.', 'wp-simple-firewall' ),
268
- sprintf( '%s: %s', __( 'Note', 'wp-simple-firewall' ), __( 'Web.Config is only available for Windows/IIS.', 'wp-simple-firewall' ) )
 
 
269
  ];
270
 
271
- $aLocks = ( new LoadFileLocks() )
272
  ->setMod( $this->getMod() )
273
  ->loadLocks();
274
- if ( !empty( $aLocks ) ) {
275
  $desc[] = __( 'Locked Files', 'wp-simple-firewall' ).':';
276
- foreach ( $aLocks as $oLock ) {
277
- $desc[] = sprintf( '<code>%s</code>', $oLock->file );
278
  }
279
  }
280
  break;
@@ -289,7 +291,7 @@ class Strings extends Base\Strings {
289
  $name = __( 'Scan Uploads', 'wp-simple-firewall' );
290
  $summary = __( 'Scan Uploads Folder For PHP and Javascript', 'wp-simple-firewall' );
291
  $desc = sprintf( '%s - %s', __( 'Warning', 'wp-simple-firewall' ), __( 'Take care when turning on this option - if you are unsure, leave it disabled.', 'wp-simple-firewall' ) )
292
- .'<br />'.__( 'The Uploads folder is primarily for media, but could be used to store nefarious files.', 'wp-simple-firewall' );
293
  break;
294
 
295
  case 'ufc_exclusions' :
@@ -297,8 +299,8 @@ class Strings extends Base\Strings {
297
  $summary = __( 'Provide A List Of Files To Be Excluded From The Scan', 'wp-simple-firewall' );
298
  $sDefaults = implode( ', ', $this->getOptions()->getOptDefault( 'ufc_exclusions' ) );
299
  $desc = __( 'Take a new line for each file you wish to exclude from the scan.', 'wp-simple-firewall' )
300
- .'<br/><strong>'.__( 'No commas are necessary.', 'wp-simple-firewall' ).'</strong>'
301
- .'<br/>'.sprintf( '%s: %s', __( 'Default', 'wp-simple-firewall' ), $sDefaults );
302
  break;
303
 
304
  case 'ic_enabled' :
@@ -311,16 +313,16 @@ class Strings extends Base\Strings {
311
  $name = __( 'Monitor User Accounts', 'wp-simple-firewall' );
312
  $summary = __( 'Scans For Critical Changes Made To User Accounts', 'wp-simple-firewall' );
313
  $desc = sprintf( __( 'Detects changes made to critical user account information that were made directly on the database and outside of the WordPress system.', 'wp-simple-firewall' ), 'author=' )
314
- .'<br />'.__( 'An example of this might be some form of SQL Injection attack.', 'wp-simple-firewall' )
315
- .'<br />'.sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ), __( 'Enabling this option for every page low may slow down your site with large numbers of users.', 'wp-simple-firewall' ) )
316
- .'<br />'.sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ), __( 'This option may cause critical problem with 3rd party plugins that manage user accounts.', 'wp-simple-firewall' ) );
317
  break;
318
 
319
  case 'ptg_depth' : /* DELETED */
320
  $name = __( 'Guard/Scan Depth', 'wp-simple-firewall' );
321
  $summary = __( 'How Deep Into The Plugin Directories To Scan And Guard', 'wp-simple-firewall' );
322
  $desc = __( 'The Guard normally scans only the top level of a folder. Increasing depth will increase scan times.', 'wp-simple-firewall' )
323
- .'<br/>'.sprintf( __( 'Setting it to %s will remove this limit and all sub-folders will be scanned - not recommended', 'wp-simple-firewall' ), 0 );
324
  break;
325
 
326
  case 'ptg_reinstall_links' :
@@ -351,8 +353,8 @@ class Strings extends Base\Strings {
351
  $name = __( 'Surgical Auto-Repair', 'wp-simple-firewall' );
352
  $summary = __( 'Automatically Attempt To Surgically Remove Malware Code', 'wp-simple-firewall' );
353
  $desc = __( "Attempts to automatically remove code from infected files.", 'wp-simple-firewall' )
354
- .'<br />'.sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ), __( 'This could break your site if code removal leaves remaining code in an inconsistent state.', 'wp-simple-firewall' ) )
355
- .'<br />'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( "Only applies to files that don't fall under the other categories for automatic repair.", 'wp-simple-firewall' ) );
356
  break;
357
 
358
  // REMOVED:
@@ -360,15 +362,15 @@ class Strings extends Base\Strings {
360
  $name = __( 'Auto-Repair WP Plugins', 'wp-simple-firewall' );
361
  $summary = __( 'Automatically Repair WordPress.org Plugins', 'wp-simple-firewall' );
362
  $desc = __( "Automatically repair any plugin files found to have potential malware.", 'wp-simple-firewall' )
363
- .'<br />'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( 'Only compatible with plugins installed from WordPress.org.', 'wp-simple-firewall' ) )
364
- .'<br />'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( "Also deletes suspected files if they weren't originally distributed with the plugin.", 'wp-simple-firewall' ) );
365
  break;
366
  case 'autorepair_themes' :
367
  $name = __( 'Auto-Repair WP Themes', 'wp-simple-firewall' );
368
  $summary = __( 'Automatically Repair WordPress.org Themes', 'wp-simple-firewall' );
369
  $desc = __( "Automatically repair any theme files found to have potential malware.", 'wp-simple-firewall' )
370
- .'<br />'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( 'Only compatible with themes installed from WordPress.org.', 'wp-simple-firewall' ) )
371
- .'<br />'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( "Also deletes suspected files if they weren't originally distributed with the theme.", 'wp-simple-firewall' ) );
372
  break;
373
  case 'wpvuln_scan_display' :
374
  $name = __( 'Highlight Plugins', 'wp-simple-firewall' );
@@ -384,31 +386,31 @@ class Strings extends Base\Strings {
384
  $name = __( 'Ignore False Positives Threshold', 'wp-simple-firewall' );
385
  $summary = __( 'Ignore False Positives In Scan Results Automatically', 'wp-simple-firewall' );
386
  $desc = __( "You can choose to ignore files with potential malware, depending on whether the confidence that it's a 'false positive' meets your minimum threshold.", 'wp-simple-firewall' )
387
- .'<br />'.__( "A false positive happens when a file appears to contain malware and shows up in scan results, but it's actually clean.", 'wp-simple-firewall' )
388
- .' ('.__( "A false positive is similar to when an anti-virus alerts to a file that doesnt have a virus.", 'wp-simple-firewall' ).')'
389
- .'<br />'.__( "The higher the confidence level, the more likely a result is a false positive.", 'wp-simple-firewall' )
390
- .' '.__( "A low level means it's less likely to be a false positive.", 'wp-simple-firewall' )
391
- .'<br />'.__( "The scan will automatically ignore results whose 'false positive' confidence level is greater than your chosen threshold.", 'wp-simple-firewall' )
392
- .'<br />'.__( "The higher the confidence threshold you select, the more likely that 'false positives' will appears in your scan results.", 'wp-simple-firewall' )
393
- .'<br />'.__( "Disabling network intelligence turns off 'false positive confidence' levels.", 'wp-simple-firewall' )
394
- .' '.__( 'You will no longer benefit from the intelligence gathered from the entire network.', 'wp-simple-firewall' )
395
- .' '.__( 'All data shared is completely anonymous.', 'wp-simple-firewall' )
396
- .' '.' [<a href="https://shsec.io/moreinfomalnetwork">'.__( 'More Info', 'wp-simple-firewall' ).'</a>]'
397
- .'<br />'.__( 'The more sites that share this information, the stronger and smarter the network becomes.', 'wp-simple-firewall' );
398
  break;
399
  case 'notification_interval' :
400
  $name = __( 'Repeat Notifications', 'wp-simple-firewall' );
401
  $summary = __( 'Item Repeat Notifications Suppression Interval', 'wp-simple-firewall' );
402
  $desc = __( 'How long the automated scans should wait before repeating a notification about an item.', 'wp-simple-firewall' )
403
- .'<br/>'.__( 'Specify the number of days to suppress repeat notifications.', 'wp-simple-firewall' )
404
- .'<br/>'.sprintf( '%s: %s', __( 'Note', 'wp-simple-firewall' ), __( 'This is per discovered item or file, not per scan.', 'wp-simple-firewall' ) );
405
  break;
406
  case 'ptg_extensions' :
407
  $name = __( 'Included File Types', 'wp-simple-firewall' );
408
  $summary = __( 'The File Types (by File Extension) Included In The Scan', 'wp-simple-firewall' );
409
  $desc = __( 'Take a new line for each file extension.', 'wp-simple-firewall' )
410
- .'<br/>'.__( 'No commas(,) or periods(.) necessary.', 'wp-simple-firewall' )
411
- .'<br/>'.__( 'Remove all extensions to scan all file type (not recommended).', 'wp-simple-firewall' );
412
  break;
413
  default:
414
  return parent::getOptionStrings( $key );
265
  $summary = __( 'Lock Files Against Tampering And Changes', 'wp-simple-firewall' );
266
  $desc = [
267
  __( 'Detects changes to the files, then lets you examine contents and revert as required.', 'wp-simple-firewall' ),
268
+ sprintf( '%s: %s', __( 'Note', 'wp-simple-firewall' ), __( 'Web.Config is only available for Windows/IIS.', 'wp-simple-firewall' ) ),
269
+ sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( 'After saving, it may take up to 60 seconds before a new lock is stored.', 'wp-simple-firewall' ) )
270
+ .' '.__( "It will be displayed below when it's ready.", 'wp-simple-firewall' )
271
  ];
272
 
273
+ $locks = ( new LoadFileLocks() )
274
  ->setMod( $this->getMod() )
275
  ->loadLocks();
276
+ if ( !empty( $locks ) ) {
277
  $desc[] = __( 'Locked Files', 'wp-simple-firewall' ).':';
278
+ foreach ( $locks as $lock ) {
279
+ $desc[] = sprintf( '<code>%s</code>', $lock->file );
280
  }
281
  }
282
  break;
291
  $name = __( 'Scan Uploads', 'wp-simple-firewall' );
292
  $summary = __( 'Scan Uploads Folder For PHP and Javascript', 'wp-simple-firewall' );
293
  $desc = sprintf( '%s - %s', __( 'Warning', 'wp-simple-firewall' ), __( 'Take care when turning on this option - if you are unsure, leave it disabled.', 'wp-simple-firewall' ) )
294
+ .'<br />'.__( 'The Uploads folder is primarily for media, but could be used to store nefarious files.', 'wp-simple-firewall' );
295
  break;
296
 
297
  case 'ufc_exclusions' :
299
  $summary = __( 'Provide A List Of Files To Be Excluded From The Scan', 'wp-simple-firewall' );
300
  $sDefaults = implode( ', ', $this->getOptions()->getOptDefault( 'ufc_exclusions' ) );
301
  $desc = __( 'Take a new line for each file you wish to exclude from the scan.', 'wp-simple-firewall' )
302
+ .'<br/><strong>'.__( 'No commas are necessary.', 'wp-simple-firewall' ).'</strong>'
303
+ .'<br/>'.sprintf( '%s: %s', __( 'Default', 'wp-simple-firewall' ), $sDefaults );
304
  break;
305
 
306
  case 'ic_enabled' :
313
  $name = __( 'Monitor User Accounts', 'wp-simple-firewall' );
314
  $summary = __( 'Scans For Critical Changes Made To User Accounts', 'wp-simple-firewall' );
315
  $desc = sprintf( __( 'Detects changes made to critical user account information that were made directly on the database and outside of the WordPress system.', 'wp-simple-firewall' ), 'author=' )
316
+ .'<br />'.__( 'An example of this might be some form of SQL Injection attack.', 'wp-simple-firewall' )
317
+ .'<br />'.sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ), __( 'Enabling this option for every page low may slow down your site with large numbers of users.', 'wp-simple-firewall' ) )
318
+ .'<br />'.sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ), __( 'This option may cause critical problem with 3rd party plugins that manage user accounts.', 'wp-simple-firewall' ) );
319
  break;
320
 
321
  case 'ptg_depth' : /* DELETED */
322
  $name = __( 'Guard/Scan Depth', 'wp-simple-firewall' );
323
  $summary = __( 'How Deep Into The Plugin Directories To Scan And Guard', 'wp-simple-firewall' );
324
  $desc = __( 'The Guard normally scans only the top level of a folder. Increasing depth will increase scan times.', 'wp-simple-firewall' )
325
+ .'<br/>'.sprintf( __( 'Setting it to %s will remove this limit and all sub-folders will be scanned - not recommended', 'wp-simple-firewall' ), 0 );
326
  break;
327
 
328
  case 'ptg_reinstall_links' :
353
  $name = __( 'Surgical Auto-Repair', 'wp-simple-firewall' );
354
  $summary = __( 'Automatically Attempt To Surgically Remove Malware Code', 'wp-simple-firewall' );
355
  $desc = __( "Attempts to automatically remove code from infected files.", 'wp-simple-firewall' )
356
+ .'<br />'.sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ), __( 'This could break your site if code removal leaves remaining code in an inconsistent state.', 'wp-simple-firewall' ) )
357
+ .'<br />'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( "Only applies to files that don't fall under the other categories for automatic repair.", 'wp-simple-firewall' ) );
358
  break;
359
 
360
  // REMOVED:
362
  $name = __( 'Auto-Repair WP Plugins', 'wp-simple-firewall' );
363
  $summary = __( 'Automatically Repair WordPress.org Plugins', 'wp-simple-firewall' );
364
  $desc = __( "Automatically repair any plugin files found to have potential malware.", 'wp-simple-firewall' )
365
+ .'<br />'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( 'Only compatible with plugins installed from WordPress.org.', 'wp-simple-firewall' ) )
366
+ .'<br />'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( "Also deletes suspected files if they weren't originally distributed with the plugin.", 'wp-simple-firewall' ) );
367
  break;
368
  case 'autorepair_themes' :
369
  $name = __( 'Auto-Repair WP Themes', 'wp-simple-firewall' );
370
  $summary = __( 'Automatically Repair WordPress.org Themes', 'wp-simple-firewall' );
371
  $desc = __( "Automatically repair any theme files found to have potential malware.", 'wp-simple-firewall' )
372
+ .'<br />'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( 'Only compatible with themes installed from WordPress.org.', 'wp-simple-firewall' ) )
373
+ .'<br />'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( "Also deletes suspected files if they weren't originally distributed with the theme.", 'wp-simple-firewall' ) );
374
  break;
375
  case 'wpvuln_scan_display' :
376
  $name = __( 'Highlight Plugins', 'wp-simple-firewall' );
386
  $name = __( 'Ignore False Positives Threshold', 'wp-simple-firewall' );
387
  $summary = __( 'Ignore False Positives In Scan Results Automatically', 'wp-simple-firewall' );
388
  $desc = __( "You can choose to ignore files with potential malware, depending on whether the confidence that it's a 'false positive' meets your minimum threshold.", 'wp-simple-firewall' )
389
+ .'<br />'.__( "A false positive happens when a file appears to contain malware and shows up in scan results, but it's actually clean.", 'wp-simple-firewall' )
390
+ .' ('.__( "A false positive is similar to when an anti-virus alerts to a file that doesnt have a virus.", 'wp-simple-firewall' ).')'
391
+ .'<br />'.__( "The higher the confidence level, the more likely a result is a false positive.", 'wp-simple-firewall' )
392
+ .' '.__( "A low level means it's less likely to be a false positive.", 'wp-simple-firewall' )
393
+ .'<br />'.__( "The scan will automatically ignore results whose 'false positive' confidence level is greater than your chosen threshold.", 'wp-simple-firewall' )
394
+ .'<br />'.__( "The higher the confidence threshold you select, the more likely that 'false positives' will appears in your scan results.", 'wp-simple-firewall' )
395
+ .'<br />'.__( "Disabling network intelligence turns off 'false positive confidence' levels.", 'wp-simple-firewall' )
396
+ .' '.__( 'You will no longer benefit from the intelligence gathered from the entire network.', 'wp-simple-firewall' )
397
+ .' '.__( 'All data shared is completely anonymous.', 'wp-simple-firewall' )
398
+ .' '.' [<a href="https://shsec.io/moreinfomalnetwork">'.__( 'More Info', 'wp-simple-firewall' ).'</a>]'
399
+ .'<br />'.__( 'The more sites that share this information, the stronger and smarter the network becomes.', 'wp-simple-firewall' );
400
  break;
401
  case 'notification_interval' :
402
  $name = __( 'Repeat Notifications', 'wp-simple-firewall' );
403
  $summary = __( 'Item Repeat Notifications Suppression Interval', 'wp-simple-firewall' );
404
  $desc = __( 'How long the automated scans should wait before repeating a notification about an item.', 'wp-simple-firewall' )
405
+ .'<br/>'.__( 'Specify the number of days to suppress repeat notifications.', 'wp-simple-firewall' )
406
+ .'<br/>'.sprintf( '%s: %s', __( 'Note', 'wp-simple-firewall' ), __( 'This is per discovered item or file, not per scan.', 'wp-simple-firewall' ) );
407
  break;
408
  case 'ptg_extensions' :
409
  $name = __( 'Included File Types', 'wp-simple-firewall' );
410
  $summary = __( 'The File Types (by File Extension) Included In The Scan', 'wp-simple-firewall' );
411
  $desc = __( 'Take a new line for each file extension.', 'wp-simple-firewall' )
412
+ .'<br/>'.__( 'No commas(,) or periods(.) necessary.', 'wp-simple-firewall' )
413
+ .'<br/>'.__( 'Remove all extensions to scan all file type (not recommended).', 'wp-simple-firewall' );
414
  break;
415
  default:
416
  return parent::getOptionStrings( $key );
src/lib/src/Modules/HackGuard/UI.php CHANGED
@@ -20,7 +20,7 @@ class UI extends BaseShield\UI {
20
  }
21
 
22
  // Can Scan Checks:
23
- $aReasonCantScan = $mod->getScansCon()->getReasonsScansCantExecute();
24
 
25
  /** @var \FernleafSystems\Wordpress\Plugin\Shield\Databases\Scanner\Select $oSelector */
26
  $oSelector = $mod->getDbHandler_ScanResults()->getQuerySelector();
@@ -39,7 +39,7 @@ class UI extends BaseShield\UI {
39
  ],
40
  'flags' => [
41
  'is_premium' => $this->getCon()->isPremiumActive(),
42
- 'can_scan' => count( $aReasonCantScan ) === 0,
43
  ],
44
  'strings' => [
45
  'never' => __( 'Never', 'wp-simple-firewall' ),
@@ -68,7 +68,7 @@ class UI extends BaseShield\UI {
68
  ],
69
  'vars' => [
70
  'initial_check' => $mod->getScanQueueController()->hasRunningScans(),
71
- 'cannot_scan_reasons' => $aReasonCantScan,
72
  'related_hrefs' => [
73
  [
74
  'href' => $this->getCon()->getModule_HackGuard()->getUrl_AdminPage(),
20
  }
21
 
22
  // Can Scan Checks:
23
+ $reasonsCantScan = $mod->getScansCon()->getReasonsScansCantExecute();
24
 
25
  /** @var \FernleafSystems\Wordpress\Plugin\Shield\Databases\Scanner\Select $oSelector */
26
  $oSelector = $mod->getDbHandler_ScanResults()->getQuerySelector();
39
  ],
40
  'flags' => [
41
  'is_premium' => $this->getCon()->isPremiumActive(),
42
+ 'can_scan' => count( $reasonsCantScan ) === 0,
43
  ],
44
  'strings' => [
45
  'never' => __( 'Never', 'wp-simple-firewall' ),
68
  ],
69
  'vars' => [
70
  'initial_check' => $mod->getScanQueueController()->hasRunningScans(),
71
+ 'cannot_scan_reasons' => $reasonsCantScan,
72
  'related_hrefs' => [
73
  [
74
  'href' => $this->getCon()->getModule_HackGuard()->getUrl_AdminPage(),
src/lib/src/Modules/Headers/ModCon.php CHANGED
@@ -3,12 +3,10 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Headers;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
- use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class ModCon extends BaseShield\ModCon {
9
 
10
  protected function preProcessOptions() {
11
- $this->cleanCspHosts();
12
  $this->cleanCustomRules();
13
  }
14
 
@@ -16,75 +14,20 @@ class ModCon extends BaseShield\ModCon {
16
  /** @var Options $opts */
17
  $opts = $this->getOptions();
18
  $opts->setOpt( 'xcsp_custom', array_unique( array_filter( array_map(
19
- function ( $sRule ) {
20
- $sRule = trim( preg_replace( '#;|\s{2,}#', '', html_entity_decode( $sRule, ENT_QUOTES ) ) );
21
- if ( !empty( $sRule ) ) {
22
- $sRule .= ';';
23
  }
24
- return $sRule;
25
  },
26
  $opts->getOpt( 'xcsp_custom', [] )
27
  ) ) ) );
28
  }
29
 
 
 
 
30
  private function cleanCspHosts() {
31
- /** @var Options $opts */
32
- $opts = $this->getOptions();
33
-
34
- $aValidDomains = [];
35
- foreach ( $opts->getOpt( 'xcsp_hosts', [] ) as $sDomain ) {
36
- $bValidDomain = false;
37
- $sDomain = trim( $sDomain );
38
-
39
- $bHttps = ( strpos( $sDomain, 'https://' ) === 0 );
40
- $bHttp = ( strpos( $sDomain, 'http://' ) === 0 );
41
- if ( $bHttp || $bHttps ) {
42
- $sDomain = preg_replace( '#^http(s)?://#', '', $sDomain );
43
- }
44
-
45
- $sCustomProtocol = '';
46
- // Special wildcard case
47
- if ( $sDomain == '*' ) {
48
- if ( $bHttps ) {
49
- $this->getOptions()->setOpt( 'xcsp_https', 'Y' );
50
- }
51
- else {
52
- $bValidDomain = true;
53
- }
54
- }
55
- elseif ( strpos( $sDomain, '://' ) && preg_match( '#^([a-zA-Z]+://)#', $sDomain, $aMatches ) ) {
56
- // there's a protocol specified
57
- $sCustomProtocol = $aMatches[ 1 ];
58
- $sDomain = str_replace( $sCustomProtocol, '', $sDomain );
59
- }
60
-
61
- // First we remove the wildcard and test domain, then add it back later.
62
- $bWildCard = ( strpos( $sDomain, '*.' ) === 0 );
63
- if ( $bWildCard ) {
64
- $sDomain = preg_replace( '#^\*\.#', '', $sDomain );
65
- }
66
-
67
- if ( !empty ( $sDomain ) && Services::Data()->isValidDomainName( $sDomain ) ) {
68
- $bValidDomain = true;
69
- }
70
-
71
- if ( $bValidDomain ) {
72
- if ( $bWildCard ) {
73
- $sDomain = '*.'.$sDomain;
74
- }
75
- if ( $bHttp ) {
76
- // $sDomain = 'http://'.$sDomain; // it seems there's no need to "explicitly" state http://
77
- }
78
- elseif ( $bHttps ) {
79
- $sDomain = 'https://'.$sDomain;
80
- }
81
- elseif ( !empty( $sCustomProtocol ) ) {
82
- $sDomain = $sCustomProtocol.$sDomain;
83
- }
84
- $aValidDomains[] = $sDomain;
85
- }
86
- }
87
- asort( $aValidDomains );
88
- $opts->setOpt( 'xcsp_hosts', array_unique( $aValidDomains ) );
89
  }
90
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Headers;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
6
 
7
  class ModCon extends BaseShield\ModCon {
8
 
9
  protected function preProcessOptions() {
 
10
  $this->cleanCustomRules();
11
  }
12
 
14
  /** @var Options $opts */
15
  $opts = $this->getOptions();
16
  $opts->setOpt( 'xcsp_custom', array_unique( array_filter( array_map(
17
+ function ( $rule ) {
18
+ $rule = trim( preg_replace( '#;|\s{2,}#', '', html_entity_decode( $rule, ENT_QUOTES ) ) );
19
+ if ( !empty( $rule ) ) {
20
+ $rule .= ';';
21
  }
22
+ return $rule;
23
  },
24
  $opts->getOpt( 'xcsp_custom', [] )
25
  ) ) ) );
26
  }
27
 
28
+ /**
29
+ * @deprecated 10.3
30
+ */
31
  private function cleanCspHosts() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
33
  }
src/lib/src/Modules/Headers/Strings.php CHANGED
@@ -101,46 +101,6 @@ class Strings extends Base\Strings {
101
  $sDescription = __( 'Allows for permission and restriction of all resources loaded on your site.', 'wp-simple-firewall' );
102
  break;
103
 
104
- case 'xcsp_self' :
105
- $sName = __( 'Self', 'wp-simple-firewall' );
106
- $sSummary = __( "Allow 'self' Directive", 'wp-simple-firewall' );
107
- $sDescription = __( "Using 'self' is generally recommended.", 'wp-simple-firewall' )
108
- .__( "It essentially means that resources from your own host:protocol are permitted.", 'wp-simple-firewall' );
109
- break;
110
-
111
- case 'xcsp_inline' :
112
- $sName = __( 'Inline Entities', 'wp-simple-firewall' );
113
- $sSummary = __( 'Allow Inline Scripts and CSS', 'wp-simple-firewall' );
114
- $sDescription = __( 'Allows parsing of Javascript and CSS declared in-line in your html document.', 'wp-simple-firewall' );
115
- break;
116
-
117
- case 'xcsp_data' :
118
- $sName = __( 'Embedded Data', 'wp-simple-firewall' );
119
- $sSummary = __( 'Allow "data:" Directives', 'wp-simple-firewall' );
120
- $sDescription = __( 'Allows use of embedded data directives, most commonly used for images and fonts.', 'wp-simple-firewall' );
121
- break;
122
-
123
- case 'xcsp_eval' :
124
- $sName = __( 'Allow eval()', 'wp-simple-firewall' );
125
- $sSummary = __( 'Allow Javascript eval()', 'wp-simple-firewall' );
126
- $sDescription = __( 'Permits the use of Javascript the eval() function.', 'wp-simple-firewall' );
127
- break;
128
-
129
- case 'xcsp_https' :
130
- $sName = __( 'HTTPS', 'wp-simple-firewall' );
131
- $sSummary = __( 'HTTPS Resource Loading', 'wp-simple-firewall' );
132
- $sDescription = __( 'Allows loading of any content provided over HTTPS.', 'wp-simple-firewall' );
133
- break;
134
-
135
- case 'xcsp_hosts' :
136
- $sName = __( 'Permitted Hosts', 'wp-simple-firewall' );
137
- $sSummary = __( 'Permitted Hosts and Domains', 'wp-simple-firewall' );
138
- $sDescription = __( 'You can explicitly state which hosts/domain from which content may be loaded.', 'wp-simple-firewall' )
139
- .' '.__( 'Take great care and test your site as you may block legitimate resources.', 'wp-simple-firewall' )
140
- .'<br />- '.__( 'If in-doubt, leave blank or use "*" only.', 'wp-simple-firewall' )
141
- .'<br />- '.sprintf( '%s: %s', __( 'Note', 'wp-simple-firewall' ), __( 'You can force only HTTPS for a given domain by prefixing it with "https://".', 'wp-simple-firewall' ) );
142
- break;
143
-
144
  case 'xcsp_custom' :
145
  $sName = __( 'Manual Rules', 'wp-simple-firewall' );
146
  $sSummary = __( 'Manual CSP Rules', 'wp-simple-firewall' );
101
  $sDescription = __( 'Allows for permission and restriction of all resources loaded on your site.', 'wp-simple-firewall' );
102
  break;
103
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  case 'xcsp_custom' :
105
  $sName = __( 'Manual Rules', 'wp-simple-firewall' );
106
  $sSummary = __( 'Manual CSP Rules', 'wp-simple-firewall' );
src/lib/src/Modules/IPs/AjaxHandler.php CHANGED
@@ -5,7 +5,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops;
7
  use FernleafSystems\Wordpress\Services\Services;
8
- use FernleafSystems\Wordpress\Services\Utilities\Net\IpIdentify;
9
 
10
  class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
11
 
@@ -32,6 +32,10 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
32
  $response = $this->ajaxExec_IpAnalyseAction();
33
  break;
34
 
 
 
 
 
35
  default:
36
  $response = parent::processAjaxAction( $action );
37
  }
@@ -39,6 +43,29 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
39
  return $response;
40
  }
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  private function ajaxExec_AddIp() :array {
43
  /** @var ModCon $mod */
44
  $mod = $this->getMod();
@@ -46,8 +73,8 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
46
 
47
  $aFormParams = $this->getAjaxFormParams();
48
 
49
- $bSuccess = false;
50
- $sMessage = __( "IP address wasn't added to the list", 'wp-simple-firewall' );
51
 
52
  $ip = preg_replace( '#[^/:.a-f\d]#i', '', ( isset( $aFormParams[ 'ip' ] ) ? $aFormParams[ 'ip' ] : '' ) );
53
  $sList = isset( $aFormParams[ 'list' ] ) ? $aFormParams[ 'list' ] : '';
@@ -60,22 +87,22 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
60
 
61
  // TODO: Bring this IP verification out of here and make it more accessible
62
  if ( empty( $ip ) ) {
63
- $sMessage = __( "IP address not provided", 'wp-simple-firewall' );
64
  }
65
  elseif ( empty( $sList ) ) {
66
- $sMessage = __( "IP list not provided", 'wp-simple-firewall' );
67
  }
68
  elseif ( !$bAcceptableIp ) {
69
- $sMessage = __( "IP address isn't either a valid IP or a CIDR range", 'wp-simple-firewall' );
70
  }
71
  elseif ( $bIsBlackList && !$mod->isPremium() ) {
72
- $sMessage = __( "Please upgrade to Pro if you'd like to add IPs to the black list manually.", 'wp-simple-firewall' );
73
  }
74
  elseif ( $bIsBlackList && $oIpServ->checkIp( $oIpServ->getRequestIp(), $ip ) ) {
75
- $sMessage = __( "Manually black listing your current IP address is not supported.", 'wp-simple-firewall' );
76
  }
77
  elseif ( $bIsBlackList && in_array( $ip, Services::IP()->getServerPublicIPs() ) ) {
78
- $sMessage = __( "This IP is reserved and can't be blacklisted.", 'wp-simple-firewall' );
79
  }
80
  else {
81
  $label = $aFormParams[ 'label' ] ?? '';
@@ -108,37 +135,45 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
108
  }
109
 
110
  if ( !empty( $oIP ) ) {
111
- $sMessage = __( 'IP address added successfully', 'wp-simple-firewall' );
112
- $bSuccess = true;
113
  }
114
  }
115
 
116
  return [
117
- 'success' => $bSuccess,
118
- 'message' => $sMessage,
119
  ];
120
  }
121
 
122
  private function ajaxExec_IpDelete() :array {
123
  /** @var ModCon $mod */
124
  $mod = $this->getMod();
125
- $bSuccess = false;
126
- $nId = Services::Request()->post( 'rid', -1 );
127
 
128
- if ( !is_numeric( $nId ) || $nId < 0 ) {
129
- $sMessage = __( 'Invalid entry selected', 'wp-simple-firewall' );
130
- }
131
- elseif ( $mod->getDbHandler_IPs()->getQueryDeleter()->deleteById( $nId ) ) {
132
- $sMessage = __( 'IP address deleted', 'wp-simple-firewall' );
133
- $bSuccess = true;
134
  }
135
  else {
136
- $sMessage = __( "IP address wasn't deleted from the list", 'wp-simple-firewall' );
 
 
 
 
 
 
 
 
 
 
 
 
137
  }
138
 
139
  return [
140
- 'success' => $bSuccess,
141
- 'message' => $sMessage,
142
  ];
143
  }
144
 
@@ -163,27 +198,25 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
163
 
164
  $ip = $req->post( 'ip' );
165
 
166
- $ipIdentifier = new IpIdentify( $ip );
167
  try {
168
- $ipID = $ipIdentifier->run();
169
- $ipKey = key( $ipID );
170
  $validIP = true;
171
  }
172
  catch ( \Exception $e ) {
173
- $ipKey = IpIdentify::UNKNOWN;
 
174
  $validIP = false;
175
  }
176
 
177
  $success = false;
178
 
179
- if ( $ipKey !== IpIdentify::UNKNOWN ) {
180
- $msg = sprintf( __( "IP can't be processed from this page as it's a known service IP: %s" ), $ipIdentifier->getName( $ipKey ) );
181
- }
182
- elseif ( !$validIP ) {
183
  $msg = __( "IP provided was invalid.", 'wp-simple-firewall' );
184
  }
 
 
 
185
  else {
186
- $dbh = $this->getCon()->getModule_IPs()->getDbHandler_IPs();
187
  switch ( $req->post( 'ip_action' ) ) {
188
 
189
  case 'block':
@@ -201,7 +234,7 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
201
 
202
  case 'unblock':
203
  $success = ( new Ops\DeleteIp() )
204
- ->setDbHandler( $dbh )
205
  ->setIP( $ip )
206
  ->fromBlacklist();
207
  $msg = $success ? __( 'IP address unblocked.', 'wp-simple-firewall' )
@@ -223,13 +256,22 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
223
 
224
  case 'unbypass':
225
  $success = ( new Ops\DeleteIp() )
226
- ->setDbHandler( $dbh )
227
  ->setIP( $ip )
228
  ->fromWhiteList();
229
  $msg = $success ? __( 'IP address removed from Bypass list.', 'wp-simple-firewall' )
230
  : __( "IP address couldn't be removed from Bypass list at this time.", 'wp-simple-firewall' );
231
  break;
232
 
 
 
 
 
 
 
 
 
 
233
  default:
234
  $msg = __( 'Unsupported Action.', 'wp-simple-firewall' );
235
  break;
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops;
7
  use FernleafSystems\Wordpress\Services\Services;
8
+ use FernleafSystems\Wordpress\Services\Utilities\Net\IpID;
9
 
10
  class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
11
 
32
  $response = $this->ajaxExec_IpAnalyseAction();
33
  break;
34
 
35
+ case 'not_bot':
36
+ $response = $this->ajaxExec_CaptureNotBot();
37
+ break;
38
+
39
  default:
40
  $response = parent::processAjaxAction( $action );
41
  }
43
  return $response;
44
  }
45
 
46
+ protected function processNonAuthAjaxAction( string $action ) :array {
47
+
48
+ switch ( $action ) {
49
+ case 'not_bot':
50
+ $response = $this->ajaxExec_CaptureNotBot();
51
+ break;
52
+ default:
53
+ $response = parent::processNonAuthAjaxAction( $action );
54
+ }
55
+
56
+ return $response;
57
+ }
58
+
59
+ private function ajaxExec_CaptureNotBot() :array {
60
+ /** @var ModCon $mod */
61
+ $mod = $this->getMod();
62
+ return [
63
+ 'success' => $mod->getBotSignalsController()
64
+ ->getHandlerNotBot()
65
+ ->registerAsNotBot()
66
+ ];
67
+ }
68
+
69
  private function ajaxExec_AddIp() :array {
70
  /** @var ModCon $mod */
71
  $mod = $this->getMod();
73
 
74
  $aFormParams = $this->getAjaxFormParams();
75
 
76
+ $success = false;
77
+ $msg = __( "IP address wasn't added to the list", 'wp-simple-firewall' );
78
 
79
  $ip = preg_replace( '#[^/:.a-f\d]#i', '', ( isset( $aFormParams[ 'ip' ] ) ? $aFormParams[ 'ip' ] : '' ) );
80
  $sList = isset( $aFormParams[ 'list' ] ) ? $aFormParams[ 'list' ] : '';
87
 
88
  // TODO: Bring this IP verification out of here and make it more accessible
89
  if ( empty( $ip ) ) {
90
+ $msg = __( "IP address not provided", 'wp-simple-firewall' );
91
  }
92
  elseif ( empty( $sList ) ) {
93
+ $msg = __( "IP list not provided", 'wp-simple-firewall' );
94
  }
95
  elseif ( !$bAcceptableIp ) {
96
+ $msg = __( "IP address isn't either a valid IP or a CIDR range", 'wp-simple-firewall' );
97
  }
98
  elseif ( $bIsBlackList && !$mod->isPremium() ) {
99
+ $msg = __( "Please upgrade to Pro if you'd like to add IPs to the black list manually.", 'wp-simple-firewall' );
100
  }
101
  elseif ( $bIsBlackList && $oIpServ->checkIp( $oIpServ->getRequestIp(), $ip ) ) {
102
+ $msg = __( "Manually black listing your current IP address is not supported.", 'wp-simple-firewall' );
103
  }
104
  elseif ( $bIsBlackList && in_array( $ip, Services::IP()->getServerPublicIPs() ) ) {
105
+ $msg = __( "This IP is reserved and can't be blacklisted.", 'wp-simple-firewall' );
106
  }
107
  else {
108
  $label = $aFormParams[ 'label' ] ?? '';
135
  }
136
 
137
  if ( !empty( $oIP ) ) {
138
+ $msg = __( 'IP address added successfully', 'wp-simple-firewall' );
139
+ $success = true;
140
  }
141
  }
142
 
143
  return [
144
+ 'success' => $success,
145
+ 'message' => $msg,
146
  ];
147
  }
148
 
149
  private function ajaxExec_IpDelete() :array {
150
  /** @var ModCon $mod */
151
  $mod = $this->getMod();
152
+ $success = false;
153
+ $ID = (int)Services::Request()->post( 'rid', -1 );
154
 
155
+ if ( $ID < 0 ) {
156
+ $msg = __( 'Invalid entry selected', 'wp-simple-firewall' );
 
 
 
 
157
  }
158
  else {
159
+ /** @var Shield\Databases\IPs\EntryVO $IP */
160
+ $IP = $mod->getDbHandler_IPs()
161
+ ->getQuerySelector()
162
+ ->byId( $ID );
163
+ if ( $IP instanceof Shield\Databases\IPs\EntryVO ) {
164
+ $del = ( new Ops\DeleteIp() )
165
+ ->setMod( $this->getMod() )
166
+ ->setIP( $IP->ip );
167
+ $success = ( $IP->list == $mod::LIST_MANUAL_WHITE ) ?
168
+ $del->fromWhiteList() : $del->fromBlacklist();
169
+ }
170
+ $msg = $success ? __( 'IP address deleted', 'wp-simple-firewall' )
171
+ : __( "IP address wasn't deleted from the list", 'wp-simple-firewall' );
172
  }
173
 
174
  return [
175
+ 'success' => $success,
176
+ 'message' => $msg,
177
  ];
178
  }
179
 
198
 
199
  $ip = $req->post( 'ip' );
200
 
 
201
  try {
202
+ list( $ipKey, $ipName ) = ( new IpID( $ip ) )->run();
 
203
  $validIP = true;
204
  }
205
  catch ( \Exception $e ) {
206
+ $ipKey = IpID::UNKNOWN;
207
+ $ipName = 'Unknown';
208
  $validIP = false;
209
  }
210
 
211
  $success = false;
212
 
213
+ if ( !$validIP ) {
 
 
 
214
  $msg = __( "IP provided was invalid.", 'wp-simple-firewall' );
215
  }
216
+ elseif ( !in_array( $ipKey, [ IpID::UNKNOWN, IpID::VISITOR ] ) ) {
217
+ $msg = sprintf( __( "IP can't be processed from this page as it's a known service IP: %s" ), $ipName );
218
+ }
219
  else {
 
220
  switch ( $req->post( 'ip_action' ) ) {
221
 
222
  case 'block':
234
 
235
  case 'unblock':
236
  $success = ( new Ops\DeleteIp() )
237
+ ->setMod( $this->getMod() )
238
  ->setIP( $ip )
239
  ->fromBlacklist();
240
  $msg = $success ? __( 'IP address unblocked.', 'wp-simple-firewall' )
256
 
257
  case 'unbypass':
258
  $success = ( new Ops\DeleteIp() )
259
+ ->setMod( $this->getMod() )
260
  ->setIP( $ip )
261
  ->fromWhiteList();
262
  $msg = $success ? __( 'IP address removed from Bypass list.', 'wp-simple-firewall' )
263
  : __( "IP address couldn't be removed from Bypass list at this time.", 'wp-simple-firewall' );
264
  break;
265
 
266
+ case 'delete_notbot':
267
+ $success = ( new Lib\Bots\BotSignalsRecord() )
268
+ ->setMod( $this->getMod() )
269
+ ->setIP( $ip )
270
+ ->delete();
271
+ $msg = $success ? __( 'IP NotBot Score Reset.', 'wp-simple-firewall' )
272
+ : __( "IP NotBot Score couldn't be reset at this time.", 'wp-simple-firewall' );
273
+ break;
274
+
275
  default:
276
  $msg = __( 'Unsupported Action.', 'wp-simple-firewall' );
277
  break;
src/lib/src/Modules/IPs/BotTrack/Base.php CHANGED
@@ -2,6 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\BotTrack;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
7
  use FernleafSystems\Wordpress\Services\Services;
@@ -9,10 +10,11 @@ use FernleafSystems\Wordpress\Services\Services;
9
  abstract class Base {
10
 
11
  use Shield\Modules\ModConsumer;
 
12
 
13
  const OPT_KEY = '';
14
 
15
- public function run() {
16
  $this->process();
17
  }
18
 
@@ -20,15 +22,15 @@ abstract class Base {
20
  /** @var IPs\Options $opts */
21
  $opts = $this->getOptions();
22
 
23
- $bBlock = $opts->isTrackOptImmediateBlock( static::OPT_KEY );
24
- if ( $bBlock ) {
25
- $nCount = 1;
26
  }
27
  elseif ( $opts->isTrackOptTransgression( static::OPT_KEY ) ) {
28
- $nCount = $opts->isTrackOptDoubleTransgression( static::OPT_KEY ) ? 2 : 1;
29
  }
30
  else {
31
- $nCount = 0;
32
  }
33
 
34
  $this->getCon()
@@ -36,8 +38,8 @@ abstract class Base {
36
  'bot'.static::OPT_KEY,
37
  [
38
  'audit' => $this->getAuditData(),
39
- 'offense_count' => $nCount,
40
- 'block' => $bBlock,
41
  ]
42
  );
43
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\BotTrack;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
8
  use FernleafSystems\Wordpress\Services\Services;
10
  abstract class Base {
11
 
12
  use Shield\Modules\ModConsumer;
13
+ use ExecOnce;
14
 
15
  const OPT_KEY = '';
16
 
17
+ protected function run() {
18
  $this->process();
19
  }
20
 
22
  /** @var IPs\Options $opts */
23
  $opts = $this->getOptions();
24
 
25
+ $block = $opts->isTrackOptImmediateBlock( static::OPT_KEY );
26
+ if ( $block ) {
27
+ $count = 1;
28
  }
29
  elseif ( $opts->isTrackOptTransgression( static::OPT_KEY ) ) {
30
+ $count = $opts->isTrackOptDoubleTransgression( static::OPT_KEY ) ? 2 : 1;
31
  }
32
  else {
33
+ $count = 0;
34
  }
35
 
36
  $this->getCon()
38
  'bot'.static::OPT_KEY,
39
  [
40
  'audit' => $this->getAuditData(),
41
+ 'offense_count' => $count,
42
+ 'block' => $block,
43
  ]
44
  );
45
  }
src/lib/src/Modules/IPs/BotTrack/Track404.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\BotTrack;
4
 
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\BotTrack;
4
 
src/lib/src/Modules/IPs/BotTrack/TrackCommentSpam.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\BotTrack;
4
+
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
+ use FernleafSystems\Wordpress\Plugin\Shield;
7
+
8
+ class TrackCommentSpam {
9
+
10
+ use Shield\Modules\ModConsumer;
11
+ use ExecOnce;
12
+
13
+ protected function canRun() :bool {
14
+ return is_admin() || is_network_admin();
15
+ }
16
+
17
+ protected function run() {
18
+ add_action( 'spammed_comment', function ( $id ) {
19
+ $comment = get_comment( $id );
20
+ if ( $comment instanceof \WP_Comment && !empty( $comment->comment_author_IP ) ) {
21
+ /** @var Shield\Modules\IPs\ModCon $mod */
22
+ $mod = $this->getMod();
23
+ $mod->getBotSignalsController()
24
+ ->getEventListener()
25
+ ->fireEventForIP( $comment->comment_author_IP, 'comment_markspam' );
26
+ }
27
+ } );
28
+
29
+ add_action( 'unspammed_comment', function ( $id ) {
30
+ $comment = get_comment( $id );
31
+ if ( $comment instanceof \WP_Comment && !empty( $comment->comment_author_IP ) ) {
32
+ /** @var Shield\Modules\IPs\ModCon $mod */
33
+ $mod = $this->getMod();
34
+ $mod->getBotSignalsController()
35
+ ->getEventListener()
36
+ ->fireEventForIP( $comment->comment_author_IP, 'comment_unmarkspam' );
37
+ }
38
+ } );
39
+ }
40
+ }
src/lib/src/Modules/IPs/BotTrack/TrackFakeWebCrawler.php CHANGED
@@ -1,7 +1,8 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\BotTrack;
4
 
 
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
7
  /**
@@ -12,32 +13,36 @@ class TrackFakeWebCrawler extends Base {
12
 
13
  const OPT_KEY = 'track_fakewebcrawler';
14
 
 
 
15
  protected function process() {
16
- try {
17
- $this->getIfVisitorIdentifiesAsCrawler(); // TEST this logic
18
- }
19
- catch ( \Exception $e ) {
20
  $this->doTransgression();
21
  }
22
  }
23
 
24
- /**
25
- * @return false
26
- * @throws \Exception
27
- */
28
- private function getIfVisitorIdentifiesAsCrawler() {
29
- $bIdentifiesAs = false;
30
-
31
- $sUserAgent = Services::Request()->getUserAgent();
32
- if ( !empty( $sUserAgent ) ) {
33
- foreach ( Services::ServiceProviders()->getAllCrawlerUseragents() as $sPossibleAgent ) {
34
- if ( stripos( $sUserAgent, $sPossibleAgent ) !== false ) {
35
- throw new \Exception( $sPossibleAgent );
36
  break;
37
  }
38
  }
39
  }
40
 
41
- return $bIdentifiesAs;
 
 
 
 
 
 
42
  }
43
  }
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
  /**
13
 
14
  const OPT_KEY = 'track_fakewebcrawler';
15
 
16
+ private $agentUsed = '';
17
+
18
  protected function process() {
19
+ /** @var ModCon $mod */
20
+ $mod = $this->getMod();
21
+ if ( $this->identifiesAsCrawler() && !$mod->isVerifiedBot() ) {
 
22
  $this->doTransgression();
23
  }
24
  }
25
 
26
+ private function identifiesAsCrawler() :bool {
27
+ $identifiesAsCrawler = false;
28
+
29
+ $userAgent = Services::Request()->getUserAgent();
30
+ if ( !empty( $userAgent ) ) {
31
+ foreach ( Services::ServiceProviders()->getAllCrawlerUseragents() as $possibleAgent ) {
32
+ if ( stripos( $userAgent, $possibleAgent ) !== false ) {
33
+ $identifiesAsCrawler = true;
34
+ $this->agentUsed = $possibleAgent;
 
 
 
35
  break;
36
  }
37
  }
38
  }
39
 
40
+ return $identifiesAsCrawler;
41
+ }
42
+
43
+ protected function getAuditData() :array {
44
+ return array_merge( parent::getAuditData(), [
45
+ 'script' => $this->agentUsed
46
+ ] );
47
  }
48
  }
src/lib/src/Modules/IPs/BotTrack/TrackInvalidScriptLoad.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
@@ -13,11 +13,28 @@ use FernleafSystems\Wordpress\Services\Services;
13
  class TrackLinkCheese extends Base {
14
 
15
  const OPT_KEY = 'track_linkcheese';
 
16
 
17
  protected function process() {
18
  add_filter( 'robots_txt', [ $this, 'appendRobotsTxt' ], 15 );
19
  add_action( 'wp_footer', [ $this, 'insertMouseTrap' ], 0 );
20
- if ( $this->isCheese() ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  $this->doTransgression();
22
  }
23
  }
@@ -26,69 +43,53 @@ class TrackLinkCheese extends Base {
26
  * @param string $robotsText
27
  * @return string
28
  */
29
- public function appendRobotsTxt( $robotsText ) {
30
- $template = Services::WpGeneral()->isPermalinksEnabled() ? "Disallow: /%s-*/\n" : "Disallow: /*?*%s=\n";
31
- $robotsText = rtrim( $robotsText, "\n" )."\n";
32
- foreach ( $this->getPossibleWords() as $word ) {
33
- $robotsText .= sprintf( $template, $this->getCon()->prefix( $word ) );
34
- }
35
- return $robotsText;
36
  }
37
 
38
- private function isCheese() {
39
- $con = $this->getCon();
40
- $req = Services::Request();
41
 
42
- $bIsCheese = false;
43
- if ( Services::WpGeneral()->isPermalinksEnabled() ) {
44
- preg_match(
45
- sprintf( '#%s-(%s)-([a-z0-9]{7,9})$#i', $con->prefix(), implode( '|', $this->getPossibleWords() ) ),
46
- trim( $req->getPath(), '/' ),
47
- $aMatches
48
- );
49
- $bIsCheese = isset( $aMatches[ 2 ] );
50
  }
51
  else {
52
- foreach ( $this->getPossibleWords() as $word ) {
53
- if ( preg_match( '#^[a-z0-9]{7,9}$#i', $req->query( $con->prefix( $word ) ) ) ) {
54
- $bIsCheese = true;
55
- break;
56
- }
57
- }
58
  }
59
 
60
- return $bIsCheese;
61
  }
62
 
63
  public function insertMouseTrap() {
64
- $id = chr( rand( 97, 122 ) ).rand( 1000, 10000000 );
65
  echo sprintf(
66
  '<style>#%s{display:none !important;}</style><a rel="nofollow" href="%s" title="%s" id="%s">%s</a>',
67
- $id, $this->buildTrapHref(), 'Click here to see something fantastic',
68
- $id, 'Click to access the login or register cheese'
 
 
 
69
  );
70
  }
71
 
72
- /**
73
- * @return string
74
- */
75
- private function buildTrapHref() {
76
- $con = $this->getCon();
 
77
 
78
- $oWP = Services::WpGeneral();
79
- $sKey = substr( md5( wp_generate_password() ), 5, rand( 7, 9 ) );
80
- $sWord = $this->getPossibleWords()[ rand( 1, count( $this->getPossibleWords() ) ) - 1 ];
81
- if ( $oWP->isPermalinksEnabled() ) {
82
- $sLink = $oWP->getHomeUrl( sprintf( '/%s-%s/', $con->prefix( $sWord ), $sKey ) );
83
- }
84
- else {
85
- $sLink = add_query_arg( [ $con->prefix( $sWord ) => $sKey ], $oWP->getHomeUrl() );
86
- }
87
- return $sLink;
88
  }
89
 
90
  /**
91
  * @return string[]
 
92
  */
93
  private function getPossibleWords() {
94
  return [
13
  class TrackLinkCheese extends Base {
14
 
15
  const OPT_KEY = 'track_linkcheese';
16
+ const CHEESE_WORD = 'link-cheese';
17
 
18
  protected function process() {
19
  add_filter( 'robots_txt', [ $this, 'appendRobotsTxt' ], 15 );
20
  add_action( 'wp_footer', [ $this, 'insertMouseTrap' ], 0 );
21
+ add_action( 'wp', [ $this, 'testCheese' ], 0 );
22
+ }
23
+
24
+ public function testCheese() {
25
+ if ( is_404() && $this->isCheese() ) {
26
+
27
+ if ( function_exists( 'wp_robots_sensitive_page' ) ) {
28
+ add_filter( 'wp_robots', 'wp_robots_sensitive_page', 1000 );
29
+ }
30
+ elseif ( function_exists( 'wp_sensitive_page_meta' ) ) {
31
+ if ( !has_action( 'wp_head', 'wp_sensitive_page_meta' ) ) {
32
+ add_action( 'wp_head', 'wp_sensitive_page_meta' );
33
+ }
34
+ }
35
+ elseif ( !has_action( 'wp_head', 'wp_no_robots' ) ) {
36
+ add_action( 'wp_head', 'wp_no_robots' );
37
+ }
38
  $this->doTransgression();
39
  }
40
  }
43
  * @param string $robotsText
44
  * @return string
45
  */
46
+ public function appendRobotsTxt( $robotsText ) :string {
47
+ $template = Services::WpGeneral()->isPermalinksEnabled() ? "Disallow: /%s/\n" : "Disallow: /*?*%s=\n";
48
+ return rtrim( $robotsText, "\n" )."\n".sprintf( $template, $this->getCheeseWord() );
 
 
 
 
49
  }
50
 
51
+ private function isCheese() :bool {
52
+ $WP = Services::WpGeneral();
 
53
 
54
+ if ( $WP->isPermalinksEnabled() ) {
55
+ $reqPath = trim( (string)Services::Request()->getPath(), '/' );
56
+ $isCheese = ( $reqPath ===
57
+ trim( (string)parse_url( $WP->getHomeUrl( $this->getCheeseWord() ), PHP_URL_PATH ), '/' ) )
58
+ || preg_match( '#icwp-wpsf-[a-z]+-[a-z0-9]{7,9}#', $reqPath ) > 0;
59
+ /** TODO: 10.3 legacy remove */
 
 
60
  }
61
  else {
62
+ $isCheese = Services::Request()->query( $this->getCheeseWord() ) === '1';
 
 
 
 
 
63
  }
64
 
65
+ return $isCheese;
66
  }
67
 
68
  public function insertMouseTrap() {
 
69
  echo sprintf(
70
  '<style>#%s{display:none !important;}</style><a rel="nofollow" href="%s" title="%s" id="%s">%s</a>',
71
+ 'icwpWpsfLinkCheese',
72
+ $this->buildTrapHref(),
73
+ 'Click here to see something fantastic',
74
+ 'icwpWpsfLinkCheese',
75
+ 'Click to access the login or register cheese'
76
  );
77
  }
78
 
79
+ private function buildTrapHref() :string {
80
+ $WP = Services::WpGeneral();
81
+ return $WP->isPermalinksEnabled() ?
82
+ $WP->getHomeUrl( sprintf( '/%s/', $this->getCheeseWord() ) )
83
+ : add_query_arg( [ $this->getCheeseWord() => '1' ], $WP->getHomeUrl() );
84
+ }
85
 
86
+ private function getCheeseWord() :string {
87
+ return $this->getCon()->prefix( self::CHEESE_WORD );
 
 
 
 
 
 
 
 
88
  }
89
 
90
  /**
91
  * @return string[]
92
+ * @deprecated 10.3
93
  */
94
  private function getPossibleWords() {
95
  return [
src/lib/src/Modules/IPs/Components/ImportIpsFromFile.php CHANGED
@@ -19,7 +19,7 @@ class ImportIpsFromFile {
19
  private function runFileImport( string $type ) {
20
  $FS = Services::WpFs();
21
 
22
- $fileImport = $FS->findFileInDir( 'ip_import_'.$type, $this->getCon()->getPath_Flags() );
23
  if ( $FS->isFile( $fileImport ) ) {
24
  $content = $FS->getFileContent( $fileImport );
25
  if ( !empty( $content ) ) {
19
  private function runFileImport( string $type ) {
20
  $FS = Services::WpFs();
21
 
22
+ $fileImport = $FS->findFileInDir( 'ip_import_'.$type, $this->getCon()->paths->forFlag() );
23
  if ( $FS->isFile( $fileImport ) ) {
24
  $content = $FS->getFileContent( $fileImport );
25
  if ( !empty( $content ) ) {
src/lib/src/Modules/IPs/Components/QueryIpBlock.php CHANGED
@@ -57,7 +57,7 @@ class QueryIpBlock {
57
  && $oIP->last_access_at < Services::Request()->ts() - $oOpts->getAutoExpireTime() ) {
58
 
59
  ( new IPs\Lib\Ops\DeleteIp() )
60
- ->setDbHandler( $mod->getDbHandler_IPs() )
61
  ->setIP( Services::IP()->getRequestIp() )
62
  ->fromBlacklist();
63
  }
57
  && $oIP->last_access_at < Services::Request()->ts() - $oOpts->getAutoExpireTime() ) {
58
 
59
  ( new IPs\Lib\Ops\DeleteIp() )
60
+ ->setMod( $mod )
61
  ->setIP( Services::IP()->getRequestIp() )
62
  ->fromBlacklist();
63
  }
src/lib/src/Modules/IPs/Components/UnblockIpByFlag.php CHANGED
@@ -15,7 +15,7 @@ class UnblockIpByFlag {
15
  $mod = $this->getMod();
16
  $FS = Services::WpFs();
17
 
18
- $path = $FS->findFileInDir( 'unblock', $this->getCon()->getPath_Flags() );
19
  if ( !empty( $path ) && $FS->isFile( $path ) ) {
20
  $sContent = $FS->getFileContent( $path );
21
  if ( !empty( $sContent ) ) {
@@ -23,7 +23,7 @@ class UnblockIpByFlag {
23
  $aLines = array_map( 'trim', explode( "\n", $sContent ) );
24
  foreach ( $aLines as $sIp ) {
25
  $bRemoved = ( new IPs\Lib\Ops\DeleteIp() )
26
- ->setDbHandler( $mod->getDbHandler_IPs() )
27
  ->setIP( $sIp )
28
  ->fromBlacklist();
29
  if ( $bRemoved ) {
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
  $sContent = $FS->getFileContent( $path );
21
  if ( !empty( $sContent ) ) {
23
  $aLines = array_map( 'trim', explode( "\n", $sContent ) );
24
  foreach ( $aLines as $sIp ) {
25
  $bRemoved = ( new IPs\Lib\Ops\DeleteIp() )
26
+ ->setMod( $mod )
27
  ->setIP( $sIp )
28
  ->fromBlacklist();
29
  if ( $bRemoved ) {
src/lib/src/Modules/IPs/Lib/AutoUnblock.php CHANGED
@@ -3,7 +3,6 @@
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\IPs\ModCon;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
@@ -17,19 +16,40 @@ class AutoUnblock {
17
  */
18
  public function run() :bool {
19
  try {
20
- $bUnblocked = $this->processAutoUnblockRequest();
21
  }
22
  catch ( \Exception $e ) {
23
- $bUnblocked = false;
24
  }
25
- if ( !$bUnblocked ) {
26
  try {
27
- $bUnblocked = $this->processUserMagicLink();
28
  }
29
  catch ( \Exception $e ) {
30
  }
31
  }
32
- return $bUnblocked;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  }
34
 
35
  /**
@@ -83,7 +103,7 @@ class AutoUnblock {
83
  }
84
 
85
  ( new IPs\Lib\Ops\DeleteIp() )
86
- ->setDbHandler( $mod->getDbHandler_IPs() )
87
  ->setIP( $sIP )
88
  ->fromBlacklist();
89
  $unblocked = true;
@@ -149,7 +169,7 @@ class AutoUnblock {
149
  }
150
  elseif ( $linkParts[ 1 ] === 'go' ) {
151
  ( new IPs\Lib\Ops\DeleteIp() )
152
- ->setDbHandler( $mod->getDbHandler_IPs() )
153
  ->setIP( Services::IP()->getRequestIp() )
154
  ->fromBlacklist();
155
  $unblocked = true;
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
 
16
  */
17
  public function run() :bool {
18
  try {
19
+ $unblocked = $this->processAutoUnblockRequest();
20
  }
21
  catch ( \Exception $e ) {
22
+ $unblocked = false;
23
  }
24
+ if ( !$unblocked ) {
25
  try {
26
+ $unblocked = $this->processUserMagicLink();
27
  }
28
  catch ( \Exception $e ) {
29
  }
30
  }
31
+ if ( !$unblocked ) {
32
+ $unblocked = $this->checkForBlockedServiceBot();
33
+ }
34
+ return $unblocked;
35
+ }
36
+
37
+ /**
38
+ * @deprecated 10.3 - temporary to ensure that service bots aren't blocked and reduce spurious Audit Trail
39
+ */
40
+ private function checkForBlockedServiceBot() :bool {
41
+ /** @var IPs\ModCon $mod */
42
+ $mod = $this->getMod();
43
+
44
+ $unblocked = false;
45
+ if ( $mod->isVerifiedBot() ) {
46
+ ( new IPs\Lib\Ops\DeleteIp() )
47
+ ->setMod( $mod )
48
+ ->setIP( Services::IP()->getRequestIp() )
49
+ ->fromBlacklist();
50
+ $unblocked = true;
51
+ }
52
+ return $unblocked;
53
  }
54
 
55
  /**
103
  }
104
 
105
  ( new IPs\Lib\Ops\DeleteIp() )
106
+ ->setMod( $mod )
107
  ->setIP( $sIP )
108
  ->fromBlacklist();
109
  $unblocked = true;
169
  }
170
  elseif ( $linkParts[ 1 ] === 'go' ) {
171
  ( new IPs\Lib\Ops\DeleteIp() )
172
+ ->setMod( $mod )
173
  ->setIP( Services::IP()->getRequestIp() )
174
  ->fromBlacklist();
175
  $unblocked = true;
src/lib/src/Modules/IPs/Lib/BlacklistHandler.php CHANGED
@@ -2,7 +2,8 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
8
  use FernleafSystems\Wordpress\Services\Services;
@@ -10,18 +11,20 @@ use FernleafSystems\Wordpress\Services\Services;
10
  class BlacklistHandler {
11
 
12
  use Modules\ModConsumer;
13
- use OneTimeExecute;
 
14
 
15
  protected function run() {
16
  /** @var IPs\ModCon $mod */
17
  $mod = $this->getMod();
18
- /** @var IPs\Options $oOpts */
19
- $oOpts = $this->getOptions();
20
- if ( $oOpts->isEnabledAutoBlackList() ) {
 
21
 
22
- $oCon = $this->getCon();
23
- if ( Services::WpGeneral()->isCron() && $oCon->isPremiumActive() ) {
24
- add_action( $oCon->prefix( 'hourly_cron' ), [ $this, 'runHourlyCron' ] );
25
  }
26
 
27
  ( new IPs\Components\UnblockIpByFlag() )
@@ -30,17 +33,17 @@ class BlacklistHandler {
30
 
31
  add_action( 'init', [ $this, 'loadBotDetectors' ] ); // hook in the bot detection
32
 
33
- if ( !$mod->isVisitorWhitelisted()
34
- && !$this->isRequestWhitelisted() && !$mod->isVerifiedBot() ) {
35
 
36
  // We setup offenses processing immediately but run the blocks on 'init
37
  ( new ProcessOffenses() )
38
  ->setMod( $this->getMod() )
39
- ->run();
 
40
  add_action( 'init', function () {
41
  ( new BlockRequest() )
42
  ->setMod( $this->getMod() )
43
- ->run();
44
  }, -100000 );
45
  }
46
  }
@@ -58,27 +61,32 @@ class BlacklistHandler {
58
  if ( $opts->isEnabledTrackXmlRpc() ) {
59
  ( new IPs\BotTrack\TrackXmlRpc() )
60
  ->setMod( $mod )
61
- ->run();
62
  }
63
  if ( $opts->isEnabledTrack404() ) {
64
  ( new IPs\BotTrack\Track404() )
65
  ->setMod( $mod )
66
- ->run();
67
  }
68
  if ( $opts->isEnabledTrackLoginFailed() ) {
69
  ( new IPs\BotTrack\TrackLoginFailed() )
70
  ->setMod( $mod )
71
- ->run();
72
  }
73
  if ( $opts->isEnabledTrackLoginInvalid() ) {
74
  ( new IPs\BotTrack\TrackLoginInvalid() )
75
  ->setMod( $mod )
76
- ->run();
77
  }
78
  if ( $opts->isEnabledTrackFakeWebCrawler() ) {
79
  ( new IPs\BotTrack\TrackFakeWebCrawler() )
80
  ->setMod( $mod )
81
- ->run();
 
 
 
 
 
82
  }
83
  }
84
 
@@ -86,29 +94,31 @@ class BlacklistHandler {
86
  if ( $opts->isEnabledTrackLinkCheese() && $mod->canLinkCheese() ) {
87
  ( new IPs\BotTrack\TrackLinkCheese() )
88
  ->setMod( $mod )
89
- ->run();
90
  }
91
  }
 
 
 
 
 
92
  }
93
 
94
- /**
95
- * @return bool
96
- */
97
- private function isRequestWhitelisted() {
98
- /** @var IPs\Options $oOpts */
99
- $oOpts = $this->getOptions();
100
- $bWhitelisted = false;
101
- $aWhitelist = $oOpts->getRequestWhitelistAsRegex();
102
- if ( !empty( $aWhitelist ) ) {
103
  $sPath = strtolower( '/'.ltrim( (string)Services::Request()->getPath(), '/' ) );
104
- foreach ( $aWhitelist as $sRule ) {
105
- if ( preg_match( $sRule, $sPath ) ) {
106
- $bWhitelisted = true;
107
  break;
108
  }
109
  }
110
  }
111
- return $bWhitelisted;
112
  }
113
 
114
  public function runHourlyCron() {
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Crons\PluginCronsConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
9
  use FernleafSystems\Wordpress\Services\Services;
11
  class BlacklistHandler {
12
 
13
  use Modules\ModConsumer;
14
+ use ExecOnce;
15
+ use PluginCronsConsumer;
16
 
17
  protected function run() {
18
  /** @var IPs\ModCon $mod */
19
  $mod = $this->getMod();
20
+ /** @var IPs\Options $opts */
21
+ $opts = $this->getOptions();
22
+
23
+ if ( $opts->isEnabledAutoBlackList() ) {
24
 
25
+ $con = $this->getCon();
26
+ if ( Services::WpGeneral()->isCron() && $con->isPremiumActive() ) {
27
+ $this->setupCronHooks();
28
  }
29
 
30
  ( new IPs\Components\UnblockIpByFlag() )
33
 
34
  add_action( 'init', [ $this, 'loadBotDetectors' ] ); // hook in the bot detection
35
 
36
+ if ( !$mod->isVisitorWhitelisted() && !$this->isRequestWhitelisted() ) {
 
37
 
38
  // We setup offenses processing immediately but run the blocks on 'init
39
  ( new ProcessOffenses() )
40
  ->setMod( $this->getMod() )
41
+ ->execute();
42
+
43
  add_action( 'init', function () {
44
  ( new BlockRequest() )
45
  ->setMod( $this->getMod() )
46
+ ->execute();
47
  }, -100000 );
48
  }
49
  }
61
  if ( $opts->isEnabledTrackXmlRpc() ) {
62
  ( new IPs\BotTrack\TrackXmlRpc() )
63
  ->setMod( $mod )
64
+ ->execute();
65
  }
66
  if ( $opts->isEnabledTrack404() ) {
67
  ( new IPs\BotTrack\Track404() )
68
  ->setMod( $mod )
69
+ ->execute();
70
  }
71
  if ( $opts->isEnabledTrackLoginFailed() ) {
72
  ( new IPs\BotTrack\TrackLoginFailed() )
73
  ->setMod( $mod )
74
+ ->execute();
75
  }
76
  if ( $opts->isEnabledTrackLoginInvalid() ) {
77
  ( new IPs\BotTrack\TrackLoginInvalid() )
78
  ->setMod( $mod )
79
+ ->execute();
80
  }
81
  if ( $opts->isEnabledTrackFakeWebCrawler() ) {
82
  ( new IPs\BotTrack\TrackFakeWebCrawler() )
83
  ->setMod( $mod )
84
+ ->execute();
85
+ }
86
+ if ( $opts->isEnabledTrackInvalidScript() ) {
87
+ ( new IPs\BotTrack\TrackInvalidScriptLoad() )
88
+ ->setMod( $mod )
89
+ ->execute();
90
  }
91
  }
92
 
94
  if ( $opts->isEnabledTrackLinkCheese() && $mod->canLinkCheese() ) {
95
  ( new IPs\BotTrack\TrackLinkCheese() )
96
  ->setMod( $mod )
97
+ ->execute();
98
  }
99
  }
100
+
101
+ // Capture when admins un/mark comments as spam
102
+ ( new IPs\BotTrack\TrackCommentSpam() )
103
+ ->setMod( $mod )
104
+ ->execute();
105
  }
106
 
107
+ private function isRequestWhitelisted() :bool {
108
+ /** @var IPs\Options $opts */
109
+ $opts = $this->getOptions();
110
+ $isWhitelisted = false;
111
+ $whitelistPaths = $opts->getRequestWhitelistAsRegex();
112
+ if ( !empty( $whitelistPaths ) ) {
 
 
 
113
  $sPath = strtolower( '/'.ltrim( (string)Services::Request()->getPath(), '/' ) );
114
+ foreach ( $whitelistPaths as $rule ) {
115
+ if ( preg_match( $rule, $sPath ) ) {
116
+ $isWhitelisted = true;
117
  break;
118
  }
119
  }
120
  }
121
+ return $isWhitelisted;
122
  }
123
 
124
  public function runHourlyCron() {
src/lib/src/Modules/IPs/Lib/BlockRequest.php CHANGED
@@ -2,6 +2,7 @@
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;
@@ -10,18 +11,19 @@ use FernleafSystems\Wordpress\Services\Utilities\Obfuscate;
10
  class BlockRequest {
11
 
12
  use ModConsumer;
 
13
 
14
- public function run() {
15
  if ( $this->isBlocked() ) {
16
 
17
  if ( $this->isAutoUnBlocked() ) {
18
  Services::Response()->redirectToHome();
19
  }
20
-
21
- // don't log killed requests
22
- add_filter( $this->getCon()->prefix( 'is_log_traffic' ), '__return_false' );
23
- $this->getCon()->fireEvent( 'conn_kill' );
24
- $this->renderKillPage();
25
  }
26
  }
27
 
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
  use FernleafSystems\Wordpress\Services\Services;
11
  class BlockRequest {
12
 
13
  use ModConsumer;
14
+ use ExecOnce;
15
 
16
+ protected function run() {
17
  if ( $this->isBlocked() ) {
18
 
19
  if ( $this->isAutoUnBlocked() ) {
20
  Services::Response()->redirectToHome();
21
  }
22
+ else {
23
+ add_filter( 'shield/is_log_traffic', '__return_false' ); // don't log killed requests
24
+ $this->getCon()->fireEvent( 'conn_kill' );
25
+ $this->renderKillPage();
26
+ }
27
  }
28
  }
29
 
src/lib/src/Modules/IPs/Lib/Bots/BotSignalsController.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Bots;
4
+
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Bots\Calculator\CalculateVisitorBotScores;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
+ use FernleafSystems\Wordpress\Services\Services;
9
+
10
+ class BotSignalsController {
11
+
12
+ use ModConsumer;
13
+ use ExecOnce;
14
+
15
+ /**
16
+ * @var NotBot\NotBotHandler
17
+ */
18
+ private $handlerNotBot;
19
+
20
+ /**
21
+ * @var EventListener
22
+ */
23
+ private $eventListener;
24
+
25
+ public function isBot( string $IP = '', bool $allowEventFire = true ) :bool {
26
+ $score = ( new CalculateVisitorBotScores() )
27
+ ->setMod( $this->getMod() )
28
+ ->setIP( empty( $IP ) ? Services::IP()->getRequestIp() : $IP )
29
+ ->probability();
30
+ $botScoreMinimum = (int)apply_filters( 'shield/antibot_score_minimum',
31
+ (int)$this->getOptions()->getOpt( 'antibot_minimum', 50 ) );
32
+
33
+ $isBot = $score < $botScoreMinimum;
34
+
35
+ if ( $allowEventFire ) {
36
+ $this->getCon()->fireEvent(
37
+ 'antibot_'.( $isBot ? 'fail' : 'pass' ),
38
+ [
39
+ 'audit' => [
40
+ 'score' => $score,
41
+ 'minimum' => $botScoreMinimum,
42
+ ]
43
+ ]
44
+ );
45
+ }
46
+ return $isBot;
47
+ }
48
+
49
+ public function getHandlerNotBot() :NotBot\NotBotHandler {
50
+ if ( !isset( $this->handlerNotBot ) ) {
51
+ $this->handlerNotBot = ( new NotBot\NotBotHandler() )->setMod( $this->getMod() );
52
+ }
53
+ return $this->handlerNotBot;
54
+ }
55
+
56
+ public function getEventListener() :EventListener {
57
+ if ( !isset( $this->eventListener ) ) {
58
+ $this->eventListener = ( new EventListener() )->setMod( $this->getMod() );
59
+ }
60
+ return $this->eventListener;
61
+ }
62
+
63
+ protected function run() {
64
+ $this->getEventListener()->execute();
65
+ add_action( 'init', function () {
66
+ $this->getHandlerNotBot()->execute();
67
+ } );
68
+ }
69
+ }
src/lib/src/Modules/IPs/Lib/Bots/BotSignalsRecord.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Bots;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases\BotSignals\EntryVO;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases\BotSignals\Select;
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\ModCon;
10
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
11
+ use FernleafSystems\Wordpress\Services\Services;
12
+
13
+ class BotSignalsRecord {
14
+
15
+ use ModConsumer;
16
+ use IpAddressConsumer;
17
+
18
+ public function delete() :bool {
19
+ /** @var ModCon $mod */
20
+ $mod = $this->getMod();
21
+ /** @var Select $select */
22
+ $select = $mod->getDbHandler_BotSignals()->getQueryDeleter();
23
+ return $select->filterByIPHuman( $this->getIP() )->query();
24
+ }
25
+
26
+ public function retrieve( bool $storeOnLoad = true ) :EntryVO {
27
+ /** @var ModCon $mod */
28
+ $mod = $this->getMod();
29
+ /** @var Select $select */
30
+ $select = $mod->getDbHandler_BotSignals()->getQuerySelector();
31
+ $e = $select->filterByIPHuman( $this->getIP() )->first();
32
+ if ( !$e instanceof EntryVO ) {
33
+ $e = new EntryVO();
34
+ $e->ip = $this->getIP();
35
+ }
36
+
37
+ $ipOnList = ( new LookupIpOnList() )
38
+ ->setDbHandler( $mod->getDbHandler_IPs() )
39
+ ->setIP( $e->ip )
40
+ ->lookupIp();
41
+
42
+ if ( !empty( $ipOnList ) ) {
43
+ if ( empty( $e->bypass_at ) && $ipOnList->list === $mod::LIST_MANUAL_WHITE ) {
44
+ $e->bypass_at = $ipOnList->created_at;
45
+ }
46
+ if ( empty( $e->offense_at ) && $ipOnList->list === $mod::LIST_AUTO_BLACK ) {
47
+ $e->offense_at = $ipOnList->last_access_at;
48
+ }
49
+ $e->blocked_at = $ipOnList->blocked_at;
50
+ }
51
+
52
+ if ( empty( $e->notbot_at ) && Services::IP()->getRequestIp() === $this->getIP() ) {
53
+ $e->notbot_at = $mod->getBotSignalsController()
54
+ ->getHandlerNotBot()
55
+ ->hasCookie() ? Services::Request()->ts() : 0;
56
+ }
57
+
58
+ if ( $storeOnLoad ) {
59
+ $this->store( $e );
60
+ }
61
+
62
+ return $e;
63
+ }
64
+
65
+ public function store( EntryVO $entry ) :bool {
66
+ /** @var ModCon $mod */
67
+ $mod = $this->getMod();
68
+
69
+ if ( empty( $entry->id ) ) {
70
+ $success = $mod->getDbHandler_BotSignals()
71
+ ->getQueryInserter()
72
+ ->insert( $entry );
73
+ }
74
+ else {
75
+ $data = $entry->getRawData();
76
+ $data[ 'updated_at' ] = Services::Request()->ts();
77
+ $success = $mod->getDbHandler_BotSignals()
78
+ ->getQueryUpdater()
79
+ ->updateById( $entry->id, $data );
80
+ }
81
+ return $success;
82
+ }
83
+
84
+ /**
85
+ * @param string $field
86
+ * @param int|null $ts
87
+ * @return EntryVO
88
+ * @throws \LogicException
89
+ */
90
+ public function updateSignalField( string $field, $ts = null ) :EntryVO {
91
+ /** @var ModCon $mod */
92
+ $mod = $this->getMod();
93
+
94
+ if ( !$mod->getDbHandler_BotSignals()->getTableSchema()->hasColumn( $field ) ) {
95
+ throw new \LogicException( sprintf( '"%s" is not a valid column on Bot Signals', $field ) );
96
+ }
97
+
98
+ $entry = $this->retrieve( false ); // false as we're going to store it anyway
99
+ $entry->{$field} = is_null( $ts ) ? Services::Request()->ts() : $ts;
100
+
101
+ $this->store( $entry );
102
+
103
+ return $entry;
104
+ }
105
+ }
src/lib/src/Modules/IPs/Lib/Bots/Calculator/BuildScores.php ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Bots\Calculator;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base\EntryVoConsumer;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases\BotSignals\EntryVO;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+ use FernleafSystems\Wordpress\Services\Utilities\Net\IpID;
9
+
10
+ class BuildScores {
11
+
12
+ use EntryVoConsumer;
13
+
14
+ public function build() :array {
15
+ $scores = [];
16
+ foreach ( $this->getAllFields( true ) as $field ) {
17
+ $scores[ $field ] = $this->{'score_'.$field}();
18
+ }
19
+ $scores[ 'known' ] = $this->score_known();
20
+ return $scores;
21
+ }
22
+
23
+ private function score_known() :int {
24
+ try {
25
+ list( $ipID, $ipName ) = ( new IpID( $this->getRecord()->ip ) )->run();
26
+ }
27
+ catch ( \Exception $e ) {
28
+ $ipID = null;
29
+ }
30
+ return ( empty( $ipID ) || in_array( $ipID, [ IpID::UNKNOWN, IpID::VISITOR ] ) )
31
+ ? 0 : 100;
32
+ }
33
+
34
+ private function score_auth() :int {
35
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
36
+ $score = 0;
37
+ }
38
+ else {
39
+ $score = $this->diffTs( __FUNCTION__ ) < DAY_IN_SECONDS ? 150 : 100;
40
+ }
41
+ return $score;
42
+ }
43
+
44
+ private function score_bt404() :int {
45
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
46
+ $score = 0;
47
+ }
48
+ else {
49
+ $score = $this->diffTs( __FUNCTION__ ) < HOUR_IN_SECONDS ? -25 : -15;
50
+ }
51
+ return $score;
52
+ }
53
+
54
+ private function score_btcheese() :int {
55
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
56
+ $score = 0;
57
+ }
58
+ else {
59
+ $score = $this->diffTs( __FUNCTION__ ) < DAY_IN_SECONDS ? -100 : -75;
60
+ }
61
+ return $score;
62
+ }
63
+
64
+ private function score_btfake() :int {
65
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
66
+ $score = 0;
67
+ }
68
+ else {
69
+ $score = $this->diffTs( __FUNCTION__ ) < DAY_IN_SECONDS ? -65 : -45;
70
+ }
71
+ return $score;
72
+ }
73
+
74
+ private function score_btinvalidscript() :int {
75
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
76
+ $score = 0;
77
+ }
78
+ else {
79
+ $score = $this->diffTs( __FUNCTION__ ) < DAY_IN_SECONDS ? -35 : -25;
80
+ }
81
+ return $score;
82
+ }
83
+
84
+ private function score_btloginfail() :int {
85
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
86
+ $score = 0;
87
+ }
88
+ else {
89
+ $score = $this->diffTs( __FUNCTION__ ) < MINUTE_IN_SECONDS ? -50 : -25;
90
+ }
91
+ return $score;
92
+ }
93
+
94
+ private function score_btlogininvalid() :int {
95
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
96
+ $score = 0;
97
+ }
98
+ else {
99
+ $score = $this->diffTs( __FUNCTION__ ) < DAY_IN_SECONDS ? -85 : -55;
100
+ }
101
+ return $score;
102
+ }
103
+
104
+ private function score_btua() :int {
105
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
106
+ $score = 0;
107
+ }
108
+ else {
109
+ $score = $this->diffTs( __FUNCTION__ ) < DAY_IN_SECONDS ? -35 : -25;
110
+ }
111
+ return $score;
112
+ }
113
+
114
+ private function score_btxml() :int {
115
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
116
+ $score = 0;
117
+ }
118
+ else {
119
+ $score = $this->diffTs( __FUNCTION__ ) < DAY_IN_SECONDS ? -75 : -35;
120
+ }
121
+ return $score;
122
+ }
123
+
124
+ private function score_cooldown() :int {
125
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
126
+ $score = 0;
127
+ }
128
+ else {
129
+ $score = $this->diffTs( __FUNCTION__ ) < MINUTE_IN_SECONDS ? -35 : -15;
130
+ }
131
+ return $score;
132
+ }
133
+
134
+ private function score_firewall() :int {
135
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
136
+ $score = 0;
137
+ }
138
+ else {
139
+ $score = $this->diffTs( __FUNCTION__ ) < DAY_IN_SECONDS ? -45 : -25;
140
+ }
141
+ return $score;
142
+ }
143
+
144
+ private function score_offense() :int {
145
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
146
+ $score = 0;
147
+ }
148
+ else {
149
+ $score = $this->diffTs( __FUNCTION__ ) < MINUTE_IN_SECONDS ? -45 : -25;
150
+ }
151
+ return $score;
152
+ }
153
+
154
+ private function score_blocked() :int {
155
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
156
+ $score = 0;
157
+ }
158
+ else {
159
+ $score = $this->diffTs( __FUNCTION__ ) < DAY_IN_SECONDS ? -75 : -55;
160
+ }
161
+ return $score;
162
+ }
163
+
164
+ private function score_unblocked() :int {
165
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
166
+ $score = 0;
167
+ }
168
+ else {
169
+ $score = $this->diffTs( __FUNCTION__ ) < DAY_IN_SECONDS ? 100 : 75;
170
+ }
171
+ return $score;
172
+ }
173
+
174
+ private function score_bypass() :int {
175
+ return $this->lastAtTs( __FUNCTION__ ) > 0 ? 150 : 0;
176
+ }
177
+
178
+ private function score_captchapass() :int {
179
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
180
+ $score = 0;
181
+ }
182
+ else {
183
+ $score = $this->diffTs( __FUNCTION__ ) < DAY_IN_SECONDS ? 55 : 25;
184
+ }
185
+ return $score;
186
+ }
187
+
188
+ private function score_ratelimit() :int {
189
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
190
+ $score = 0;
191
+ }
192
+ else {
193
+ $score = $this->diffTs( __FUNCTION__ ) < MINUTE_IN_SECONDS ? -55 : -25;
194
+ }
195
+ return $score;
196
+ }
197
+
198
+ private function score_captchafail() :int {
199
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
200
+ $score = 0;
201
+ }
202
+ else {
203
+ $score = $this->diffTs( __FUNCTION__ ) < HOUR_IN_SECONDS ? -55 : -25;
204
+ }
205
+ return $score;
206
+ }
207
+
208
+ private function score_humanspam() :int {
209
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
210
+ $score = 0;
211
+ }
212
+ else {
213
+ $score = $this->diffTs( __FUNCTION__ ) < DAY_IN_SECONDS ? -30 : -15;
214
+ }
215
+ return $score;
216
+ }
217
+
218
+ private function score_markspam() :int {
219
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
220
+ $score = 0;
221
+ }
222
+ else {
223
+ $score = $this->diffTs( __FUNCTION__ ) < WEEK_IN_SECONDS ? -50 : -25;
224
+ }
225
+ return $score;
226
+ }
227
+
228
+ private function score_unmarkspam() :int {
229
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
230
+ $score = 0;
231
+ }
232
+ else {
233
+ $score = $this->diffTs( __FUNCTION__ ) < WEEK_IN_SECONDS ? 75 : 35;
234
+ }
235
+ return $score;
236
+ }
237
+
238
+ private function score_frontpage() :int {
239
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
240
+ $score = -15;
241
+ }
242
+ else {
243
+ $score = $this->diffTs( __FUNCTION__ ) < HOUR_IN_SECONDS ? 25 : 15;
244
+ }
245
+ return $score;
246
+ }
247
+
248
+ private function score_notbot() :int {
249
+ if ( $this->lastAtTs( __FUNCTION__ ) === 0 ) {
250
+ $score = -15;
251
+ }
252
+ else {
253
+ $score = $this->diffTs( __FUNCTION__ ) < HOUR_IN_SECONDS ? 125 : 65;
254
+ }
255
+ return $score;
256
+ }
257
+
258
+ private function lastAtTs( $fieldFunction ) :int {
259
+ $field = str_replace( 'score_', '', $fieldFunction ).'_at';
260
+ return $this->getRecord()->{$field} ?? 0;
261
+ }
262
+
263
+ private function diffTs( $fieldFunction ) :int {
264
+ $field = str_replace( 'score_', '', $fieldFunction ).'_at';
265
+ return Services::Request()->ts() - ( $this->getRecord()->{$field} ?? 0 );
266
+ }
267
+
268
+ private function getAllFields( $filterForMethods = false ) :array {
269
+ $botSignalDBH = shield_security_get_plugin()->getController()
270
+ ->getModule_IPs()
271
+ ->getDbHandler_BotSignals();
272
+ $fields = array_map(
273
+ function ( $col ) {
274
+ return str_replace( '_at', '', $col );
275
+ },
276
+ array_filter(
277
+ $botSignalDBH->getTableSchema()->getColumnNames(),
278
+ function ( $col ) {
279
+ return preg_match( '#_at$#', $col ) &&
280
+ !in_array( $col, [ 'updated_at', 'created_at', 'deleted_at' ] );
281
+ }
282
+ )
283
+ );
284
+
285
+ if ( $filterForMethods ) {
286
+ $fields = array_filter( $fields, function ( $field ) {
287
+ return method_exists( $this, 'score_'.$field );
288
+ } );
289
+ }
290
+
291
+ return $fields;
292
+ }
293
+
294
+ private function getRecord() :EntryVO {
295
+ return $this->getEntryVO();
296
+ }
297
+ }
src/lib/src/Modules/IPs/Lib/Bots/Calculator/CalculateVisitorBotScores.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Bots\Calculator;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases\BotSignals\EntryVO;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Components\IpAddressConsumer;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Bots\BotSignalsRecord;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
9
+ use FernleafSystems\Wordpress\Services\Services;
10
+
11
+ class CalculateVisitorBotScores {
12
+
13
+ use IpAddressConsumer;
14
+ use ModConsumer;
15
+
16
+ private $scores = [];
17
+
18
+ public function scores() :array {
19
+ $this->scores = ( new BuildScores() )
20
+ ->setEntryVO( $this->loadEntry() )
21
+ ->build();
22
+ return $this->getActiveScores();
23
+ }
24
+
25
+ public function total() :int {
26
+ return (int)array_sum( $this->scores() );
27
+ }
28
+
29
+ public function probability() :int {
30
+ return (int)max( 0, min( 100, $this->total() ) );
31
+ }
32
+
33
+ private function getActiveScores() :array {
34
+ return array_filter(
35
+ $this->scores,
36
+ function ( $score ) {
37
+ return $score !== -1;
38
+ }
39
+ );
40
+ }
41
+
42
+ private function loadEntry() :EntryVO {
43
+ $ip = $this->getIP();
44
+ if ( empty( $ip ) ) {
45
+ $ip = Services::IP()->getRequestIp();
46
+ }
47
+ try {
48
+ $entry = ( new BotSignalsRecord() )
49
+ ->setMod( $this->getMod() )
50
+ ->setIP( $ip )
51
+ ->retrieve();
52
+ }
53
+ catch ( \Exception $e ) {
54
+ $entry = new EntryVO();
55
+ $entry->ip = $ip;
56
+ }
57
+
58
+ return $entry;
59
+ }
60
+ }
src/lib/src/Modules/IPs/Lib/Bots/EventListener.php ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Bots;
4
+
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\ModCon;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
+ use FernleafSystems\Wordpress\Services\Services;
9
+
10
+ class EventListener {
11
+
12
+ use ModConsumer;
13
+ use ExecOnce;
14
+
15
+ public function fireEventForIP( $ip, $event ) {
16
+ $events = $this->getEventsToColumn();
17
+
18
+ foreach ( $events as $eventTrigger => $column ) {
19
+ if ( $eventTrigger === $event || preg_match( sprintf( '#^%s$#', $eventTrigger ), $event ) ) {
20
+ try {
21
+ ( new BotSignalsRecord() )
22
+ ->setMod( $this->getMod() )
23
+ ->setIP( $ip )
24
+ ->updateSignalField( $column );
25
+ }
26
+ catch ( \LogicException $e ) {
27
+ error_log( 'Error updating bot signal: '.$e->getMessage() );
28
+ }
29
+ }
30
+ }
31
+ }
32
+
33
+ protected function canRun() :bool {
34
+ /** @var ModCon $mod */
35
+ $mod = $this->getMod();
36
+ return !$mod->isVerifiedBot();
37
+ }
38
+
39
+ protected function run() {
40
+ add_action( $this->getCon()->prefix( 'event' ), function ( $event ) {
41
+ $this->fireEventForIP( Services::IP()->getRequestIp(), $event );
42
+ } );
43
+ }
44
+
45
+ /**
46
+ * @return string[]
47
+ */
48
+ private function getEventsToColumn() :array {
49
+ return array_map(
50
+ function ( $column ) {
51
+ return str_replace( '_at', '', $column ).'_at';
52
+ },
53
+ [
54
+ 'bottrack_notbot' => 'notbot',
55
+ 'frontpage_load' => 'frontpage',
56
+ 'bottrack_404' => 'bt404',
57
+ 'bottrack_fakewebcrawler' => 'btfake',
58
+ 'bottrack_linkcheese' => 'btcheese',
59
+ 'bottrack_loginfailed' => 'btloginfail',
60
+ 'bottrack_useragent' => 'btua',
61
+ 'bottrack_xmlrpc' => 'btxml',
62
+ 'bottrack_logininvalid' => 'btlogininvalid',
63
+ 'bottrack_invalidscript' => 'btinvalidscript',
64
+ 'cooldown_fail' => 'cooldown',
65
+ 'recaptcha_success' => 'captchapass',
66
+ 'request_limit_exceeded' => 'ratelimit',
67
+ 'recaptcha_fail' => 'captchafail',
68
+ 'spam_block_human' => 'humanspam',
69
+ 'comment_markspam' => 'markspam',
70
+ 'comment_unmarkspam' => 'unmarkspam',
71
+ 'blockparam_.*' => 'firewall',
72
+ 'ip_offense' => 'offense',
73
+ 'ip_blocked' => 'blocked',
74
+ 'ip_unblock' => 'unblocked',
75
+ 'ip_bypass' => 'bypass',
76
+ 'login_success' => 'auth',
77
+ ]
78
+ );
79
+ }
80
+ }
src/lib/src/Modules/IPs/Lib/Bots/NotBot/InsertNotBotJs.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Bots\NotBot;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Controller\Assets\Enqueue;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
+
8
+ class InsertNotBotJs {
9
+
10
+ use ModConsumer;
11
+
12
+ public function run() {
13
+ $this->enqueueJS();
14
+ $this->nonceJs();
15
+ }
16
+
17
+ protected function enqueueJS() {
18
+ add_filter( 'shield/custom_enqueues', function ( array $enqueues ) {
19
+ $enqueues[ Enqueue::JS ][] = 'shield/antibot';
20
+ return $enqueues;
21
+ } );
22
+ }
23
+
24
+ private function nonceJs() {
25
+ add_filter( 'shield/custom_localisations', function ( array $localz ) {
26
+
27
+ $ajaxData = $this->getMod()->getAjaxActionData( 'not_bot' );
28
+ $ajaxHref = $ajaxData[ 'ajaxurl' ];
29
+ unset( $ajaxData[ 'ajaxurl' ] );
30
+
31
+ $localz[] = [
32
+ 'shield/antibot',
33
+ 'shield_vars_antibotjs',
34
+ [
35
+ 'ajax' => [
36
+ 'not_bot' => http_build_query( $ajaxData )
37
+ ],
38
+ 'hrefs' => [
39
+ 'ajax' => $ajaxHref
40
+ ],
41
+ ]
42
+ ];
43
+ return $localz;
44
+ } );
45
+ }
46
+ }
src/lib/src/Modules/IPs/Lib/Bots/NotBot/NotBotHandler.php ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Bots\NotBot;
4
+
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\ModCon;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
+ use FernleafSystems\Wordpress\Services\Services;
9
+
10
+ class NotBotHandler {
11
+
12
+ const LIFETIME = 600;
13
+ const SLUG = 'notbot';
14
+ use ModConsumer;
15
+ use ExecOnce;
16
+
17
+ private $hashTested = false;
18
+
19
+ protected function canRun() :bool {
20
+ return (bool)apply_filters( 'shield/can_run_antibot', true );
21
+ }
22
+
23
+ protected function run() {
24
+ ( new InsertNotBotJs() )
25
+ ->setMod( $this->getMod() )
26
+ ->run();
27
+ $this->registerFrontPageLoad();
28
+ $this->maybeDeleteCookie();
29
+ }
30
+
31
+ private function registerFrontPageLoad() {
32
+ add_action( 'wp', function () {
33
+ $req = Services::Request();
34
+ if ( $req->isGet() && is_front_page() ) {
35
+ /** @var ModCon $mod */
36
+ $mod = $this->getMod();
37
+ $mod->getBotSignalsController()
38
+ ->getEventListener()
39
+ ->fireEventForIP( Services::IP()->getRequestIp(), 'frontpage_load' );
40
+ }
41
+ } );
42
+ }
43
+
44
+ private function maybeDeleteCookie() {
45
+ $cookie = $this->getCookieParts();
46
+ if ( !empty( $cookie ) && $cookie[ 'ts' ] - Services::Request()->ts() < 300 ) {
47
+ $this->clearCookie();
48
+ }
49
+ }
50
+
51
+ public function registerAsNotBot() :bool {
52
+ $ts = Services::Request()->ts() + self::LIFETIME;
53
+ Services::Response()->cookieSet(
54
+ $this->getMod()->prefix( self::SLUG ),
55
+ sprintf( '%sz%s', $ts, $this->getHashForVisitorTS( $ts ) ),
56
+ self::LIFETIME
57
+ );
58
+ $this->getCon()->fireEvent( 'bottrack_notbot' );
59
+ return true;
60
+ }
61
+
62
+ public function clearCookie() :bool {
63
+ Services::Response()->cookieSet(
64
+ $this->getMod()->prefix( self::SLUG ),
65
+ '',
66
+ -self::LIFETIME
67
+ );
68
+ return true;
69
+ }
70
+
71
+ public function hasCookie() :bool {
72
+ $cookie = $this->getCookieParts();
73
+ return !empty( $cookie )
74
+ && ( Services::Request()->ts() < $cookie[ 'ts' ] )
75
+ && hash_equals( $this->getHashForVisitorTS( (int)$cookie[ 'ts' ] ), $cookie[ 'hash' ] );
76
+ }
77
+
78
+ protected function getHashForVisitorTS( int $timestamp ) {
79
+ return hash_hmac( 'sha1',
80
+ $timestamp.(string)Services::IP()->getRequestIp(),
81
+ $this->getCon()->getSiteInstallationId()
82
+ );
83
+ }
84
+
85
+ private function getCookieParts() :array {
86
+ $parts = [];
87
+ $req = Services::Request();
88
+ $notBot = $req->cookie( $this->getMod()->prefix( self::SLUG ), '' );
89
+ if ( !empty( $notBot ) && strpos( $notBot, 'z' ) ) {
90
+ list( $ts, $hash ) = explode( 'z', $notBot );
91
+ $parts[ 'ts' ] = $ts;
92
+ $parts[ 'hash' ] = $hash;
93
+ }
94
+ return $parts;
95
+ }
96
+ }
src/lib/src/Modules/IPs/Lib/IpAnalyse/BuildDisplay.php CHANGED
@@ -3,13 +3,18 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\IpAnalyse;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\GeoIp\Lookup;
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\ModCon;
 
10
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
11
  use FernleafSystems\Wordpress\Services\Services;
12
- use FernleafSystems\Wordpress\Services\Utilities\Net\IpIdentify;
13
 
14
  class BuildDisplay {
15
 
@@ -26,7 +31,7 @@ class BuildDisplay {
26
 
27
  $ip = $this->getIP();
28
  if ( !Services::IP()->isValidIp( $ip ) ) {
29
- throw new \Exception( "A valid IP address was not provided." );
30
  }
31
 
32
  return $mod->renderTemplate(
@@ -34,6 +39,7 @@ class BuildDisplay {
34
  [
35
  'strings' => [
36
  'title' => sprintf( __( 'Info For IP Address %s', 'wp-simple-firewall' ), $ip ),
 
37
  'nav_general' => __( 'General Info', 'wp-simple-firewall' ),
38
  'nav_sessions' => __( 'User Sessions', 'wp-simple-firewall' ),
39
  'nav_audit' => __( 'Audit Trail', 'wp-simple-firewall' ),
@@ -44,6 +50,7 @@ class BuildDisplay {
44
  ],
45
  'content' => [
46
  'general' => $this->renderForGeneral(),
 
47
  'sessions' => $this->renderForSessions(),
48
  'audit_trail' => $this->renderForAuditTrail(),
49
  'traffic' => $this->renderForTraffic(),
@@ -55,18 +62,18 @@ class BuildDisplay {
55
 
56
  private function renderForGeneral() :string {
57
  $con = $this->getCon();
 
 
58
  $ip = $this->getIP();
59
 
60
- $dbh = $con->getModule_IPs()->getDbHandler_IPs();
61
-
62
- $oBlockIP = ( new LookupIpOnList() )
63
- ->setDbHandler( $dbh )
64
  ->setListTypeBlack()
65
  ->setIP( $ip )
66
  ->lookup( true );
67
 
68
- $oBypassIP = ( new LookupIpOnList() )
69
- ->setDbHandler( $dbh )
70
  ->setListTypeWhite()
71
  ->setIP( $ip )
72
  ->lookup( true );
@@ -79,28 +86,42 @@ class BuildDisplay {
79
 
80
  $sRDNS = gethostbyaddr( $ip );
81
 
82
- $ipIdentifier = new IpIdentify( $ip );
83
  try {
84
- $ipWhoIs = $ipIdentifier->run();
85
- $ipIdKey = key( $ipWhoIs );
86
- $ipID = current( $ipWhoIs );
 
 
 
 
 
 
 
 
 
87
  }
88
  catch ( \Exception $e ) {
89
- $ipIdKey = IpIdentify::UNKNOWN;
90
- $ipID = $ipIdentifier->getName( $ipIdKey );
91
  }
92
 
93
- if ( $ipIdKey === IpIdentify::UNKNOWN ) {
94
  $ipEntry = ( new LookupIpOnList() )
95
- ->setDbHandler( $dbh )
96
  ->setIP( $ip )
97
  ->setListTypeWhite()
98
  ->lookup();
99
  if ( $ipEntry instanceof Databases\IPs\EntryVO ) {
100
- $ipID = $ipEntry->label;
101
  }
102
  }
103
 
 
 
 
 
 
 
104
  return $this->getMod()->renderTemplate(
105
  '/wpadmin_pages/insights/ips/ip_analyse/ip_general.twig',
106
  [
@@ -108,16 +129,18 @@ class BuildDisplay {
108
  'title_general' => __( 'Identifying Info', 'wp-simple-firewall' ),
109
  'title_status' => __( 'IP Status', 'wp-simple-firewall' ),
110
 
111
- 'block_ip' => __( 'Block IP', 'wp-simple-firewall' ),
112
- 'unblock_ip' => __( 'Unblock IP', 'wp-simple-firewall' ),
113
- 'bypass_ip' => __( 'Add IP Bypass', 'wp-simple-firewall' ),
114
- 'unbypass_ip' => __( 'Remove IP Bypass', 'wp-simple-firewall' ),
 
115
 
116
  'status' => [
117
- 'is_you' => __( 'Is It You?', 'wp-simple-firewall' ),
118
- 'offenses' => __( 'Number of offenses', 'wp-simple-firewall' ),
119
- 'is_blocked' => __( 'Is Blocked', 'wp-simple-firewall' ),
120
- 'is_bypass' => __( 'Is Bypass IP', 'wp-simple-firewall' ),
 
121
  ],
122
 
123
  'yes' => __( 'Yes', 'wp-simple-firewall' ),
@@ -140,13 +163,15 @@ class BuildDisplay {
140
  'vars' => [
141
  'ip' => $ip,
142
  'status' => [
143
- 'is_you' => Services::IP()->checkIp( $ip, Services::IP()->getRequestIp() ),
144
- 'offenses' => $oBlockIP instanceof Databases\IPs\EntryVO ? $oBlockIP->transgressions : 0,
145
- 'is_blocked' => $oBlockIP instanceof Databases\IPs\EntryVO ? $oBlockIP->blocked_at > 0 : false,
146
- 'is_bypass' => $oBypassIP instanceof Databases\IPs\EntryVO,
 
 
147
  ],
148
  'identity' => [
149
- 'who_is_it' => $ipID,
150
  'rdns' => $sRDNS === $ip ? __( 'Unavailable', 'wp-simple-firewall' ) : $sRDNS,
151
  'country_name' => $validGeo ? $geo->getCountryName() : __( 'Unknown', 'wp-simple-firewall' ),
152
  'timezone' => $validGeo ? $geo->getTimezone() : __( 'Unknown', 'wp-simple-firewall' ),
@@ -178,7 +203,7 @@ class BuildDisplay {
178
  ->query();
179
 
180
  foreach ( $sessions as $key => $session ) {
181
- $asArray = $session->getRawDataAsArray();
182
  $asArray[ 'logged_in_at' ] = $this->formatTimestampField( (int)$session->logged_in_at );
183
  $asArray[ 'last_activity_at' ] = $this->formatTimestampField( (int)$session->last_activity_at );
184
  $asArray[ 'is_sec_admin' ] = $session->secadmin_at > 0;
@@ -216,7 +241,7 @@ class BuildDisplay {
216
  ->query();
217
 
218
  foreach ( $requests as $key => $request ) {
219
- $asArray = $request->getRawDataAsArray();
220
  $asArray[ 'created_at' ] = $this->formatTimestampField( (int)$request->created_at );
221
  if ( strpos( $request->path, '?' ) === false ) {
222
  $request->path .= '?';
@@ -249,6 +274,79 @@ class BuildDisplay {
249
  );
250
  }
251
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  private function renderForAuditTrail() :string {
253
  $con = $this->getCon();
254
  /** @var Databases\AuditTrail\Select $sel */
@@ -259,22 +357,17 @@ class BuildDisplay {
259
  $logs = $sel->filterByIp( $this->getIP() )
260
  ->query();
261
 
262
- foreach ( $logs as $key => $log ) {
263
- $asArray = $log->getRawDataAsArray();
264
 
265
- $module = $con->getModule( $log->context );
266
  if ( empty( $module ) ) {
267
  $module = $con->getModule_AuditTrail();
268
  }
269
- $oStrings = $module->getStrings();
270
-
271
- $asArray[ 'event' ] = stripslashes( sanitize_textarea_field(
272
- vsprintf(
273
- implode( "\n", $oStrings->getAuditMessage( $log->event ) ),
274
- $log->meta
275
- )
276
- ) );
277
- $asArray[ 'created_at' ] = $this->formatTimestampField( (int)$log->created_at );
278
 
279
  $logs[ $key ] = $asArray;
280
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\IpAnalyse;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\Lib\AuditMessageBuilder;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\GeoIp\Lookup;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Components\IpAddressConsumer;
9
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Bots\Calculator\CalculateVisitorBotScores;
10
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Bots\BotSignalsRecord;
11
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops\DeleteIp;
12
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops\LookupIpOnList;
13
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\ModCon;
14
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Strings;
15
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
16
  use FernleafSystems\Wordpress\Services\Services;
17
+ use FernleafSystems\Wordpress\Services\Utilities\Net\IpID;
18
 
19
  class BuildDisplay {
20
 
31
 
32
  $ip = $this->getIP();
33
  if ( !Services::IP()->isValidIp( $ip ) ) {
34
+ throw new \Exception( "A valid IP address wasn't provided." );
35
  }
36
 
37
  return $mod->renderTemplate(
39
  [
40
  'strings' => [
41
  'title' => sprintf( __( 'Info For IP Address %s', 'wp-simple-firewall' ), $ip ),
42
+ 'nav_signals' => __( 'Bot Signals', 'wp-simple-firewall' ),
43
  'nav_general' => __( 'General Info', 'wp-simple-firewall' ),
44
  'nav_sessions' => __( 'User Sessions', 'wp-simple-firewall' ),
45
  'nav_audit' => __( 'Audit Trail', 'wp-simple-firewall' ),
50
  ],
51
  'content' => [
52
  'general' => $this->renderForGeneral(),
53
+ 'signals' => $this->renderForBotSignals(),
54
  'sessions' => $this->renderForSessions(),
55
  'audit_trail' => $this->renderForAuditTrail(),
56
  'traffic' => $this->renderForTraffic(),
62
 
63
  private function renderForGeneral() :string {
64
  $con = $this->getCon();
65
+ /** @var ModCon $mod */
66
+ $mod = $this->getMod();
67
  $ip = $this->getIP();
68
 
69
+ $blockIP = ( new LookupIpOnList() )
70
+ ->setDbHandler( $mod->getDbHandler_IPs() )
 
 
71
  ->setListTypeBlack()
72
  ->setIP( $ip )
73
  ->lookup( true );
74
 
75
+ $bypassIP = ( new LookupIpOnList() )
76
+ ->setDbHandler( $mod->getDbHandler_IPs() )
77
  ->setListTypeWhite()
78
  ->setIP( $ip )
79
  ->lookup( true );
86
 
87
  $sRDNS = gethostbyaddr( $ip );
88
 
 
89
  try {
90
+ list( $ipKey, $ipName ) = ( new IpID( $ip ) )
91
+ ->setIgnoreUserAgent( true )
92
+ ->run();
93
+ // We do a "repair" and unblock previously blocked search providers:
94
+ if ( $blockIP instanceof Databases\IPs\EntryVO
95
+ && in_array( $ipKey, Services::ServiceProviders()->getSearchProviders() ) ) {
96
+ ( new DeleteIp() )
97
+ ->setMod( $mod )
98
+ ->setIP( $ip )
99
+ ->fromBlacklist();
100
+ unset( $blockIP );
101
+ }
102
  }
103
  catch ( \Exception $e ) {
104
+ $ipKey = IpID::UNKNOWN;
105
+ $ipName = 'Unknown';
106
  }
107
 
108
+ if ( $ipKey === IpID::UNKNOWN ) {
109
  $ipEntry = ( new LookupIpOnList() )
110
+ ->setDbHandler( $mod->getDbHandler_IPs() )
111
  ->setIP( $ip )
112
  ->setListTypeWhite()
113
  ->lookup();
114
  if ( $ipEntry instanceof Databases\IPs\EntryVO ) {
115
+ $ipName = $ipEntry->label;
116
  }
117
  }
118
 
119
+ $botScore = ( new CalculateVisitorBotScores() )
120
+ ->setMod( $mod )
121
+ ->setIP( $ip )
122
+ ->probability();
123
+ $isBot = $mod->getBotSignalsController()->isBot( $ip, false );
124
+
125
  return $this->getMod()->renderTemplate(
126
  '/wpadmin_pages/insights/ips/ip_analyse/ip_general.twig',
127
  [
129
  'title_general' => __( 'Identifying Info', 'wp-simple-firewall' ),
130
  'title_status' => __( 'IP Status', 'wp-simple-firewall' ),
131
 
132
+ 'block_ip' => __( 'Block IP', 'wp-simple-firewall' ),
133
+ 'unblock_ip' => __( 'Unblock IP', 'wp-simple-firewall' ),
134
+ 'bypass_ip' => __( 'Add IP Bypass', 'wp-simple-firewall' ),
135
+ 'unbypass_ip' => __( 'Remove IP Bypass', 'wp-simple-firewall' ),
136
+ 'delete_notbot' => __( 'Reset For This IP', 'wp-simple-firewall' ),
137
 
138
  'status' => [
139
+ 'is_you' => __( 'Is It You?', 'wp-simple-firewall' ),
140
+ 'offenses' => __( 'Number of offenses', 'wp-simple-firewall' ),
141
+ 'is_blocked' => __( 'Is Blocked', 'wp-simple-firewall' ),
142
+ 'is_bypass' => __( 'Is Bypass IP', 'wp-simple-firewall' ),
143
+ 'notbot_score' => __( 'NotBot Score', 'wp-simple-firewall' ),
144
  ],
145
 
146
  'yes' => __( 'Yes', 'wp-simple-firewall' ),
163
  'vars' => [
164
  'ip' => $ip,
165
  'status' => [
166
+ 'is_you' => Services::IP()->checkIp( $ip, Services::IP()->getRequestIp() ),
167
+ 'offenses' => $blockIP instanceof Databases\IPs\EntryVO ? $blockIP->transgressions : 0,
168
+ 'is_blocked' => $blockIP instanceof Databases\IPs\EntryVO ? $blockIP->blocked_at > 0 : false,
169
+ 'is_bypass' => $bypassIP instanceof Databases\IPs\EntryVO,
170
+ 'notbot_score' => $botScore,
171
+ 'is_bot' => $isBot,
172
  ],
173
  'identity' => [
174
+ 'who_is_it' => $ipName,
175
  'rdns' => $sRDNS === $ip ? __( 'Unavailable', 'wp-simple-firewall' ) : $sRDNS,
176
  'country_name' => $validGeo ? $geo->getCountryName() : __( 'Unknown', 'wp-simple-firewall' ),
177
  'timezone' => $validGeo ? $geo->getTimezone() : __( 'Unknown', 'wp-simple-firewall' ),
203
  ->query();
204
 
205
  foreach ( $sessions as $key => $session ) {
206
+ $asArray = $session->getRawData();
207
  $asArray[ 'logged_in_at' ] = $this->formatTimestampField( (int)$session->logged_in_at );
208
  $asArray[ 'last_activity_at' ] = $this->formatTimestampField( (int)$session->last_activity_at );
209
  $asArray[ 'is_sec_admin' ] = $session->secadmin_at > 0;
241
  ->query();
242
 
243
  foreach ( $requests as $key => $request ) {
244
+ $asArray = $request->getRawData();
245
  $asArray[ 'created_at' ] = $this->formatTimestampField( (int)$request->created_at );
246
  if ( strpos( $request->path, '?' ) === false ) {
247
  $request->path .= '?';
274
  );
275
  }
276
 
277
+ private function renderForBotSignals() :string {
278
+ /** @var Strings $strings */
279
+ $strings = $this->getMod()->getStrings();
280
+ $names = $strings->getBotSignalNames();
281
+
282
+ $signals = [];
283
+ $scores = ( new CalculateVisitorBotScores() )
284
+ ->setMod( $this->getMod() )
285
+ ->setIP( $this->getIP() )
286
+ ->scores();
287
+ try {
288
+ $record = ( new BotSignalsRecord() )
289
+ ->setMod( $this->getMod() )
290
+ ->setIP( $this->getIP() )
291
+ ->retrieve();
292
+ }
293
+ catch ( \Exception $e ) {
294
+ $record = null;
295
+ $signals = [];
296
+ }
297
+
298
+ foreach ( $scores as $scoreKey => $scoreValue ) {
299
+ $column = $scoreKey.'_at';
300
+ if ( $scoreValue !== 0 ) {
301
+ if ( empty( $record ) || empty( $record->{$column} ) ) {
302
+ if ( in_array( $scoreKey, [ 'known' ] ) ) {
303
+ $signals[ $scoreKey ] = __( 'N/A', 'wp-simple-firewall' );
304
+ }
305
+ else {
306
+ $signals[ $scoreKey ] = __( 'Never Recorded', 'wp-simple-firewall' );
307
+ }
308
+ }
309
+ else {
310
+ $signals[ $scoreKey ] = Services::Request()
311
+ ->carbon()
312
+ ->setTimestamp( $record->{$column} )->diffForHumans();
313
+ }
314
+ }
315
+ }
316
+
317
+ return $this->getMod()->renderTemplate(
318
+ '/wpadmin_pages/insights/ips/ip_analyse/ip_botsignals.twig',
319
+ [
320
+ 'strings' => [
321
+ 'title' => __( 'Bot Signals', 'wp-simple-firewall' ),
322
+ 'signal' => __( 'Signal', 'wp-simple-firewall' ),
323
+ 'score' => __( 'Score', 'wp-simple-firewall' ),
324
+ 'total_score' => __( 'Total NotBot Score', 'wp-simple-firewall' ),
325
+ 'when' => __( 'When', 'wp-simple-firewall' ),
326
+ 'bot_probability' => __( 'Bot Probability', 'wp-simple-firewall' ),
327
+ 'botsignal_delete' => __( 'Delete All Bot Signals', 'wp-simple-firewall' ),
328
+ 'signal_names' => $names,
329
+ 'no_signals' => __( 'There are no bot signals for this IP address.', 'wp-simple-firewall' ),
330
+ ],
331
+ 'ajax' => [
332
+ 'has_signals' => !empty( $signals ),
333
+ ],
334
+ 'flags' => [
335
+ 'has_signals' => !empty( $signals ),
336
+ ],
337
+ 'vars' => [
338
+ 'signals' => $signals,
339
+ 'total_signals' => count( $signals ),
340
+ 'scores' => $scores,
341
+ 'total_score' => array_sum( $scores ),
342
+ 'minimum' => array_sum( $scores ),
343
+ 'probability' => 100 - (int)max( 0, min( 100, array_sum( $scores ) ) )
344
+ ],
345
+ ],
346
+ true
347
+ );
348
+ }
349
+
350
  private function renderForAuditTrail() :string {
351
  $con = $this->getCon();
352
  /** @var Databases\AuditTrail\Select $sel */
357
  $logs = $sel->filterByIp( $this->getIP() )
358
  ->query();
359
 
360
+ foreach ( $logs as $key => $entry ) {
361
+ $asArray = $entry->getRawData();
362
 
363
+ $module = $con->getModule( $entry->context );
364
  if ( empty( $module ) ) {
365
  $module = $con->getModule_AuditTrail();
366
  }
367
+ $strings = $module->getStrings();
368
+
369
+ $asArray[ 'event' ] = AuditMessageBuilder::Build( $entry, $strings->getAuditMessage( $entry->event ) );
370
+ $asArray[ 'created_at' ] = $this->formatTimestampField( (int)$entry->created_at );
 
 
 
 
 
371
 
372
  $logs[ $key ] = $asArray;
373
  }
src/lib/src/Modules/IPs/Lib/IpAnalyse/FindAllPluginIps.php CHANGED
@@ -41,6 +41,13 @@ class FindAllPluginIps {
41
  ->getQuerySelector();
42
  $ips = array_merge( $ips, $sel->getDistinctForColumn( 'ip' ) );
43
 
 
 
 
 
 
 
 
44
  return IpListSort::Sort( array_unique( $ips ) );
45
  }
46
  }
41
  ->getQuerySelector();
42
  $ips = array_merge( $ips, $sel->getDistinctForColumn( 'ip' ) );
43
 
44
+ // Bot Signal
45
+ /** @var Databases\BotSignals\Select $sel */
46
+ $sel = $con->getModule_IPs()
47
+ ->getDbHandler_BotSignals()
48
+ ->getQuerySelector();
49
+ $ips = array_merge( $ips, $sel->getDistinctIps() );
50
+
51
  return IpListSort::Sort( array_unique( $ips ) );
52
  }
53
  }
src/lib/src/Modules/IPs/Lib/Ops/AddIp.php CHANGED
@@ -23,40 +23,39 @@ class AddIp {
23
  public function toAutoBlacklist() {
24
  /** @var ModCon $mod */
25
  $mod = $this->getMod();
26
- $oReq = Services::Request();
27
 
28
- $sIP = $this->getIP();
29
- if ( !Services::IP()->isValidIp( $sIP ) ) {
30
  throw new \Exception( "IP address isn't valid." );
31
  }
32
- if ( in_array( $sIP, Services::IP()->getServerPublicIPs() ) ) {
33
  throw new \Exception( 'Will not black mark our own server IP' );
34
  }
35
 
36
- $oIP = ( new LookupIpOnList() )
37
  ->setDbHandler( $mod->getDbHandler_IPs() )
38
  ->setListTypeBlack()
39
- ->setIP( $sIP )
40
  ->lookup( false );
41
- if ( !$oIP instanceof Databases\IPs\EntryVO ) {
42
- $oIP = $this->add( $mod::LIST_AUTO_BLACK, 'auto', $oReq->ts() );
43
  }
44
 
45
  // Edge-case: the IP is on the list but the last access long-enough passed
46
  // that it's set to be removed by the cron - the IP is basically expired.
47
  // We just reset the transgressions
48
- /** @var Modules\IPs\Options $oOpts */
49
- $oOpts = $this->getOptions();
50
- if ( $oIP->transgressions > 0
51
- && ( $oReq->ts() - $oOpts->getAutoExpireTime() > (int)$oIP->last_access_at ) ) {
52
  $mod->getDbHandler_IPs()
53
- ->getQueryUpdater()
54
- ->updateEntry( $oIP, [
55
- 'last_access_at' => Services::Request()->ts(),
56
- 'transgressions' => 0
57
- ] );
58
  }
59
- return $oIP;
60
  }
61
 
62
  /**
@@ -74,46 +73,46 @@ class AddIp {
74
  throw new \Exception( "IP address isn't valid." );
75
  }
76
 
77
- $oIP = null;
78
  if ( !in_array( $sIP, $oIpServ->getServerPublicIPs() ) ) {
79
 
80
  if ( $oIpServ->isValidIpRange( $sIP ) ) {
81
  ( new DeleteIp() )
82
- ->setDbHandler( $mod->getDbHandler_IPs() )
83
  ->setIP( $sIP )
84
  ->fromBlacklist();
85
  }
86
 
87
- $oIP = ( new LookupIpOnList() )
88
  ->setDbHandler( $mod->getDbHandler_IPs() )
89
  ->setListTypeBlack()
90
  ->setIP( $sIP )
91
  ->lookup( false );
92
 
93
- if ( !$oIP instanceof Databases\IPs\EntryVO ) {
94
- $oIP = $this->add( $mod::LIST_MANUAL_BLACK, $sLabel );
95
  }
96
 
97
- $aUpdateData = [
98
  'last_access_at' => Services::Request()->ts()
99
  ];
100
 
101
- if ( $oIP->list != $mod::LIST_MANUAL_BLACK ) {
102
- $aUpdateData[ 'list' ] = $mod::LIST_MANUAL_BLACK;
103
  }
104
- if ( $oIP->label != $sLabel ) {
105
- $aUpdateData[ 'label' ] = $sLabel;
106
  }
107
- if ( $oIP->blocked_at == 0 ) {
108
- $aUpdateData[ 'blocked_at' ] = Services::Request()->ts();
109
  }
110
 
111
  $mod->getDbHandler_IPs()
112
- ->getQueryUpdater()
113
- ->updateEntry( $oIP, $aUpdateData );
114
  }
115
 
116
- return $oIP;
117
  }
118
 
119
  /**
@@ -133,50 +132,51 @@ class AddIp {
133
 
134
  if ( $oIpServ->isValidIpRange( $ip ) ) {
135
  ( new DeleteIp() )
136
- ->setDbHandler( $mod->getDbHandler_IPs() )
137
  ->setIP( $ip )
138
  ->fromWhiteList();
139
  }
140
 
141
- $oIP = ( new LookupIpOnList() )
142
  ->setDbHandler( $mod->getDbHandler_IPs() )
143
  ->setIP( $this->getIP() )
144
  ->lookup( false );
145
- if ( !$oIP instanceof Databases\IPs\EntryVO ) {
146
- $oIP = $this->add( $mod::LIST_MANUAL_WHITE, $label );
 
147
  }
148
 
149
- $aUpdateData = [];
150
- if ( $oIP->list != $mod::LIST_MANUAL_WHITE ) {
151
- $aUpdateData[ 'list' ] = $mod::LIST_MANUAL_WHITE;
152
  }
153
- if ( !empty( $label ) && $oIP->label != $label ) {
154
- $aUpdateData[ 'label' ] = $label;
155
  }
156
- if ( $oIP->blocked_at > 0 ) {
157
- $aUpdateData[ 'blocked_at' ] = 0;
158
  }
159
- if ( $oIP->transgressions > 0 ) {
160
- $aUpdateData[ 'transgressions' ] = 0;
161
  }
162
 
163
- if ( !empty( $aUpdateData ) ) {
164
  $mod->getDbHandler_IPs()
165
- ->getQueryUpdater()
166
- ->updateEntry( $oIP, $aUpdateData );
167
  }
168
 
169
- return $oIP;
170
  }
171
 
172
  /**
173
- * @param string $sList
174
  * @param string $sLabel
175
  * @param int|null $nLastAccessAt
176
  * @return Databases\IPs\EntryVO|null
177
  * @throws \Exception
178
  */
179
- private function add( $sList, $sLabel = '', $nLastAccessAt = null ) {
180
  $oIP = null;
181
 
182
  /** @var ModCon $mod */
@@ -188,7 +188,7 @@ class AddIp {
188
  /** @var Databases\IPs\EntryVO $oTempIp */
189
  $oTempIp = $oDbh->getVo();
190
  $oTempIp->ip = $this->getIP();
191
- $oTempIp->list = $sList;
192
  $oTempIp->label = empty( $sLabel ) ? __( 'No Label', 'wp-simple-firewall' ) : trim( $sLabel );
193
  if ( is_numeric( $nLastAccessAt ) && $nLastAccessAt > 0 ) {
194
  $oTempIp->last_access_at = $nLastAccessAt;
23
  public function toAutoBlacklist() {
24
  /** @var ModCon $mod */
25
  $mod = $this->getMod();
26
+ $req = Services::Request();
27
 
28
+ $ip = $this->getIP();
29
+ if ( !Services::IP()->isValidIp( $ip ) ) {
30
  throw new \Exception( "IP address isn't valid." );
31
  }
32
+ if ( in_array( $ip, Services::IP()->getServerPublicIPs() ) ) {
33
  throw new \Exception( 'Will not black mark our own server IP' );
34
  }
35
 
36
+ $IP = ( new LookupIpOnList() )
37
  ->setDbHandler( $mod->getDbHandler_IPs() )
38
  ->setListTypeBlack()
39
+ ->setIP( $ip )
40
  ->lookup( false );
41
+ if ( !$IP instanceof Databases\IPs\EntryVO ) {
42
+ $IP = $this->add( $mod::LIST_AUTO_BLACK, 'auto', $req->ts() );
43
  }
44
 
45
  // Edge-case: the IP is on the list but the last access long-enough passed
46
  // that it's set to be removed by the cron - the IP is basically expired.
47
  // We just reset the transgressions
48
+ /** @var Modules\IPs\Options $opts */
49
+ $opts = $this->getOptions();
50
+ if ( $IP->transgressions > 0 && ( $req->ts() - $opts->getAutoExpireTime() > (int)$IP->last_access_at ) ) {
 
51
  $mod->getDbHandler_IPs()
52
+ ->getQueryUpdater()
53
+ ->updateEntry( $IP, [
54
+ 'last_access_at' => Services::Request()->ts(),
55
+ 'transgressions' => 0
56
+ ] );
57
  }
58
+ return $IP;
59
  }
60
 
61
  /**
73
  throw new \Exception( "IP address isn't valid." );
74
  }
75
 
76
+ $IP = null;
77
  if ( !in_array( $sIP, $oIpServ->getServerPublicIPs() ) ) {
78
 
79
  if ( $oIpServ->isValidIpRange( $sIP ) ) {
80
  ( new DeleteIp() )
81
+ ->setMod( $mod )
82
  ->setIP( $sIP )
83
  ->fromBlacklist();
84
  }
85
 
86
+ $IP = ( new LookupIpOnList() )
87
  ->setDbHandler( $mod->getDbHandler_IPs() )
88
  ->setListTypeBlack()
89
  ->setIP( $sIP )
90
  ->lookup( false );
91
 
92
+ if ( !$IP instanceof Databases\IPs\EntryVO ) {
93
+ $IP = $this->add( $mod::LIST_MANUAL_BLACK, $sLabel );
94
  }
95
 
96
+ $updateData = [
97
  'last_access_at' => Services::Request()->ts()
98
  ];
99
 
100
+ if ( $IP->list != $mod::LIST_MANUAL_BLACK ) {
101
+ $updateData[ 'list' ] = $mod::LIST_MANUAL_BLACK;
102
  }
103
+ if ( $IP->label != $sLabel ) {
104
+ $updateData[ 'label' ] = $sLabel;
105
  }
106
+ if ( $IP->blocked_at == 0 ) {
107
+ $updateData[ 'blocked_at' ] = Services::Request()->ts();
108
  }
109
 
110
  $mod->getDbHandler_IPs()
111
+ ->getQueryUpdater()
112
+ ->updateEntry( $IP, $updateData );
113
  }
114
 
115
+ return $IP;
116
  }
117
 
118
  /**
132
 
133
  if ( $oIpServ->isValidIpRange( $ip ) ) {
134
  ( new DeleteIp() )
135
+ ->setMod( $mod )
136
  ->setIP( $ip )
137
  ->fromWhiteList();
138
  }
139
 
140
+ $IP = ( new LookupIpOnList() )
141
  ->setDbHandler( $mod->getDbHandler_IPs() )
142
  ->setIP( $this->getIP() )
143
  ->lookup( false );
144
+ if ( !$IP instanceof Databases\IPs\EntryVO ) {
145
+ $this->getCon()->fireEvent( 'ip_bypass' );
146
+ $IP = $this->add( $mod::LIST_MANUAL_WHITE, $label );
147
  }
148
 
149
+ $updateData = [];
150
+ if ( $IP->list != $mod::LIST_MANUAL_WHITE ) {
151
+ $updateData[ 'list' ] = $mod::LIST_MANUAL_WHITE;
152
  }
153
+ if ( !empty( $label ) && $IP->label != $label ) {
154
+ $updateData[ 'label' ] = $label;
155
  }
156
+ if ( $IP->blocked_at > 0 ) {
157
+ $updateData[ 'blocked_at' ] = 0;
158
  }
159
+ if ( $IP->transgressions > 0 ) {
160
+ $updateData[ 'transgressions' ] = 0;
161
  }
162
 
163
+ if ( !empty( $updateData ) ) {
164
  $mod->getDbHandler_IPs()
165
+ ->getQueryUpdater()
166
+ ->updateEntry( $IP, $updateData );
167
  }
168
 
169
+ return $IP;
170
  }
171
 
172
  /**
173
+ * @param string $list
174
  * @param string $sLabel
175
  * @param int|null $nLastAccessAt
176
  * @return Databases\IPs\EntryVO|null
177
  * @throws \Exception
178
  */
179
+ private function add( string $list, $sLabel = '', $nLastAccessAt = null ) {
180
  $oIP = null;
181
 
182
  /** @var ModCon $mod */
188
  /** @var Databases\IPs\EntryVO $oTempIp */
189
  $oTempIp = $oDbh->getVo();
190
  $oTempIp->ip = $this->getIP();
191
+ $oTempIp->list = $list;
192
  $oTempIp->label = empty( $sLabel ) ? __( 'No Label', 'wp-simple-firewall' ) : trim( $sLabel );
193
  if ( is_numeric( $nLastAccessAt ) && $nLastAccessAt > 0 ) {
194
  $oTempIp->last_access_at = $nLastAccessAt;
src/lib/src/Modules/IPs/Lib/Ops/DeleteIp.php CHANGED
@@ -6,16 +6,13 @@ use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
8
 
9
- /**
10
- * Class DeleteIp
11
- * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops
12
- */
13
  class DeleteIp {
14
 
15
- use Databases\Base\HandlerConsumer;
16
  use IPs\Components\IpAddressConsumer;
17
 
18
  public function fromBlacklist() :bool {
 
19
  return (bool)$this->getDeleter()
20
  ->filterByBlacklist()
21
  ->query();
@@ -28,9 +25,11 @@ class DeleteIp {
28
  }
29
 
30
  private function getDeleter() :Databases\IPs\Delete {
31
- /** @var Databases\IPs\Delete $oDel */
32
- $oDel = $this->getDbHandler()->getQueryDeleter();
33
- return $oDel->filterByIp( $this->getIP() )
34
- ->setLimit( 1 );
 
 
35
  }
36
  }
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
8
 
 
 
 
 
9
  class DeleteIp {
10
 
11
+ use Shield\Modules\ModConsumer;
12
  use IPs\Components\IpAddressConsumer;
13
 
14
  public function fromBlacklist() :bool {
15
+ $this->getCon()->fireEvent( 'ip_unblock' );
16
  return (bool)$this->getDeleter()
17
  ->filterByBlacklist()
18
  ->query();
25
  }
26
 
27
  private function getDeleter() :Databases\IPs\Delete {
28
+ /** @var IPs\ModCon $mod */
29
+ $mod = $this->getMod();
30
+ /** @var Databases\IPs\Delete $deleter */
31
+ $deleter = $mod->getDbHandler_IPs()->getQueryDeleter();
32
+ return $deleter->filterByIp( $this->getIP() )
33
+ ->setLimit( 1 );
34
  }
35
  }
src/lib/src/Modules/IPs/Lib/Ops/LookupIpOnList.php CHANGED
@@ -106,17 +106,34 @@ class LookupIpOnList {
106
  }
107
 
108
  /**
109
- * @param bool $bIsBlocked
110
  * @return $this
111
  */
112
- public function setIsIpBlocked( $bIsBlocked ) {
113
- $this->isBlocked = $bIsBlocked;
114
  return $this;
115
  }
116
 
117
  /**
118
  * @return $this
119
  */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  public function setListTypeBlack() {
121
  $this->listType = 'black';
122
  return $this;
@@ -124,6 +141,7 @@ class LookupIpOnList {
124
 
125
  /**
126
  * @return $this
 
127
  */
128
  public function setListTypeWhite() {
129
  $this->listType = 'white';
106
  }
107
 
108
  /**
109
+ * @param bool $blocked
110
  * @return $this
111
  */
112
+ public function setIsIpBlocked( bool $blocked ) {
113
+ $this->isBlocked = $blocked;
114
  return $this;
115
  }
116
 
117
  /**
118
  * @return $this
119
  */
120
+ public function setListTypeBlock() {
121
+ $this->listType = 'black';
122
+ return $this;
123
+ }
124
+
125
+ /**
126
+ * @return $this
127
+ */
128
+ public function setListTypeBypass() {
129
+ $this->listType = 'white';
130
+ return $this;
131
+ }
132
+
133
+ /**
134
+ * @return $this
135
+ * @deprecated 11.0
136
+ */
137
  public function setListTypeBlack() {
138
  $this->listType = 'black';
139
  return $this;
141
 
142
  /**
143
  * @return $this
144
+ * @deprecated 11.0
145
  */
146
  public function setListTypeWhite() {
147
  $this->listType = 'white';
src/lib/src/Modules/IPs/Lib/ProcessOffenses.php CHANGED
@@ -2,6 +2,7 @@
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;
@@ -9,8 +10,13 @@ use FernleafSystems\Wordpress\Services\Services;
9
  class ProcessOffenses {
10
 
11
  use ModConsumer;
 
12
 
13
- public function run() {
 
 
 
 
14
  /** @var IPs\ModCon $mod */
15
  $mod = $this->getMod();
16
 
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
  use FernleafSystems\Wordpress\Services\Services;
10
  class ProcessOffenses {
11
 
12
  use ModConsumer;
13
+ use ExecOnce;
14
 
15
+ protected function canRun() :bool {
16
+ return !$this->getMod()->isVerifiedBot();
17
+ }
18
+
19
+ protected function run() {
20
  /** @var IPs\ModCon $mod */
21
  $mod = $this->getMod();
22
 
src/lib/src/Modules/IPs/ModCon.php CHANGED
@@ -4,6 +4,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class ModCon extends BaseShield\ModCon {
@@ -22,6 +23,19 @@ class ModCon extends BaseShield\ModCon {
22
  */
23
  private $oBlacklistHandler;
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  public function getBlacklistHandler() :Lib\BlacklistHandler {
26
  if ( !isset( $this->oBlacklistHandler ) ) {
27
  $this->oBlacklistHandler = ( new Lib\BlacklistHandler() )->setMod( $this );
@@ -29,8 +43,13 @@ class ModCon extends BaseShield\ModCon {
29
  return $this->oBlacklistHandler;
30
  }
31
 
 
 
 
 
32
  public function getDbHandler_IPs() :Shield\Databases\IPs\Handler {
33
- return $this->getDbH( 'ips' );
 
34
  }
35
 
36
  /**
@@ -45,6 +64,16 @@ class ModCon extends BaseShield\ModCon {
45
  && parent::isReadyToExecute();
46
  }
47
 
 
 
 
 
 
 
 
 
 
 
48
  protected function preProcessOptions() {
49
  /** @var Options $opts */
50
  $opts = $this->getOptions();
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Tool\DbTableExport;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
10
  class ModCon extends BaseShield\ModCon {
23
  */
24
  private $oBlacklistHandler;
25
 
26
+ /**
27
+ * @var Lib\Bots\BotSignalsController
28
+ */
29
+ private $botSignalsCon;
30
+
31
+ public function getBotSignalsController() :Lib\Bots\BotSignalsController {
32
+ if ( !isset( $this->botSignalsCon ) ) {
33
+ $this->botSignalsCon = ( new Lib\Bots\BotSignalsController() )
34
+ ->setMod( $this );
35
+ }
36
+ return $this->botSignalsCon;
37
+ }
38
+
39
  public function getBlacklistHandler() :Lib\BlacklistHandler {
40
  if ( !isset( $this->oBlacklistHandler ) ) {
41
  $this->oBlacklistHandler = ( new Lib\BlacklistHandler() )->setMod( $this );
43
  return $this->oBlacklistHandler;
44
  }
45
 
46
+ public function getDbHandler_BotSignals() :Shield\Databases\BotSignals\Handler {
47
+ return $this->getDbH( 'botsignals' );
48
+ }
49
+
50
  public function getDbHandler_IPs() :Shield\Databases\IPs\Handler {
51
+ $new = $this->getDbH( 'ip_lists' );
52
+ return empty( $new ) ? $this->getDbH( 'ips' ) : $new;
53
  }
54
 
55
  /**
64
  && parent::isReadyToExecute();
65
  }
66
 
67
+ protected function handleFileDownload( string $downloadID ) {
68
+ switch ( $downloadID ) {
69
+ case 'db_ip':
70
+ ( new DbTableExport() )
71
+ ->setDbHandler( $this->getDbHandler_IPs() )
72
+ ->toCSV();
73
+ break;
74
+ }
75
+ }
76
+
77
  protected function preProcessOptions() {
78
  /** @var Options $opts */
79
  $opts = $this->getOptions();
src/lib/src/Modules/IPs/Options.php CHANGED
@@ -69,59 +69,39 @@ class Options extends BaseShield\Options {
69
  );
70
  }
71
 
72
- /**
73
- * @return bool
74
- */
75
- public function isEnabledAutoBlackList() {
76
  return $this->getOffenseLimit() > 0;
77
  }
78
 
79
- /**
80
- * @return bool
81
- */
82
- public function isEnabledAutoVisitorRecover() {
83
  return in_array( 'gasp', (array)$this->getOpt( 'user_auto_recover', [] ) );
84
  }
85
 
86
- /**
87
- * @return bool
88
- */
89
- public function isEnabledMagicEmailLinkRecover() {
90
  return in_array( 'email', (array)$this->getOpt( 'user_auto_recover', [] ) );
91
  }
92
 
93
- /**
94
- * @return bool
95
- */
96
- public function isEnabledTrack404() {
97
  return $this->isSelectOptionEnabled( 'track_404' );
98
  }
99
 
100
- /**
101
- * @return bool
102
- */
103
- public function isEnabledTrackFakeWebCrawler() {
104
  return $this->isSelectOptionEnabled( 'track_fakewebcrawler' );
105
  }
106
 
107
- /**
108
- * @return bool
109
- */
110
- public function isEnabledTrackLoginInvalid() {
 
111
  return $this->isSelectOptionEnabled( 'track_logininvalid' );
112
  }
113
 
114
- /**
115
- * @return bool
116
- */
117
- public function isEnabledTrackLoginFailed() {
118
  return $this->isSelectOptionEnabled( 'track_loginfailed' );
119
  }
120
 
121
- /**
122
- * @return bool
123
- */
124
- public function isEnabledTrackLinkCheese() {
125
  return $this->isSelectOptionEnabled( 'track_linkcheese' );
126
  }
127
 
@@ -156,11 +136,7 @@ class Options extends BaseShield\Options {
156
  return $this->isOpt( $key, 'block' );
157
  }
158
 
159
- /**
160
- * @param string $key
161
- * @return bool
162
- */
163
- protected function isSelectOptionEnabled( $key ) {
164
  return !$this->isOpt( $key, 'disabled' );
165
  }
166
  }
69
  );
70
  }
71
 
72
+ public function isEnabledAutoBlackList() :bool {
 
 
 
73
  return $this->getOffenseLimit() > 0;
74
  }
75
 
76
+ public function isEnabledAutoVisitorRecover() :bool {
 
 
 
77
  return in_array( 'gasp', (array)$this->getOpt( 'user_auto_recover', [] ) );
78
  }
79
 
80
+ public function isEnabledMagicEmailLinkRecover() :bool {
 
 
 
81
  return in_array( 'email', (array)$this->getOpt( 'user_auto_recover', [] ) );
82
  }
83
 
84
+ public function isEnabledTrack404() :bool {
 
 
 
85
  return $this->isSelectOptionEnabled( 'track_404' );
86
  }
87
 
88
+ public function isEnabledTrackFakeWebCrawler() :bool {
 
 
 
89
  return $this->isSelectOptionEnabled( 'track_fakewebcrawler' );
90
  }
91
 
92
+ public function isEnabledTrackInvalidScript() :bool {
93
+ return $this->isSelectOptionEnabled( 'track_invalidscript' );
94
+ }
95
+
96
+ public function isEnabledTrackLoginInvalid() :bool {
97
  return $this->isSelectOptionEnabled( 'track_logininvalid' );
98
  }
99
 
100
+ public function isEnabledTrackLoginFailed() :bool {
 
 
 
101
  return $this->isSelectOptionEnabled( 'track_loginfailed' );
102
  }
103
 
104
+ public function isEnabledTrackLinkCheese() :bool {
 
 
 
105
  return $this->isSelectOptionEnabled( 'track_linkcheese' );
106
  }
107
 
136
  return $this->isOpt( $key, 'block' );
137
  }
138
 
139
+ protected function isSelectOptionEnabled( string $key ) :bool {
 
 
 
 
140
  return !$this->isOpt( $key, 'disabled' );
141
  }
142
  }
src/lib/src/Modules/IPs/Processor.php CHANGED
@@ -10,5 +10,6 @@ class Processor extends BaseShield\Processor {
10
  /** @var ModCon $mod */
11
  $mod = $this->getMod();
12
  $mod->getBlacklistHandler()->execute();
 
13
  }
14
  }
10
  /** @var ModCon $mod */
11
  $mod = $this->getMod();
12
  $mod->getBlacklistHandler()->execute();
13
+ $mod->getBotSignalsController()->execute();
14
  }
15
  }
src/lib/src/Modules/IPs/Strings.php CHANGED
@@ -114,15 +114,15 @@ class Strings extends Base\Strings {
114
  switch ( $key ) {
115
 
116
  case 'enable_ips' :
117
- $sName = sprintf( __( 'Enable %s Module', 'wp-simple-firewall' ), $sModName );
118
- $sSummary = sprintf( __( 'Enable (or Disable) The %s Module', 'wp-simple-firewall' ), $sModName );
119
- $sDescription = sprintf( __( 'Un-Checking this option will completely disable the %s module.', 'wp-simple-firewall' ), $sModName );
120
  break;
121
 
122
  case 'transgression_limit' :
123
- $sName = __( 'Offense Limit', 'wp-simple-firewall' );
124
- $sSummary = __( 'Visitor IP address will be Black Listed after X bad actions on your site', 'wp-simple-firewall' );
125
- $sDescription = [
126
  sprintf( __( 'An offense is registered against an IP address each time a visitor trips the defenses of the %s plugin.', 'wp-simple-firewall' ), $sPlugName ),
127
  __( 'When the number of these offenses exceeds the limit, they are automatically blocked from accessing the site.', 'wp-simple-firewall' ),
128
  sprintf( __( 'Set this to "0" to turn off the %s feature.', 'wp-simple-firewall' ), __( 'Automatic IP Black List', 'wp-simple-firewall' ) )
@@ -130,9 +130,9 @@ class Strings extends Base\Strings {
130
  break;
131
 
132
  case 'auto_expire' :
133
- $sName = __( 'Auto Block Expiration', 'wp-simple-firewall' );
134
- $sSummary = __( 'After 1 "X" a black listed IP will be removed from the black list', 'wp-simple-firewall' );
135
- $sDescription = [
136
  __( 'Blocked IP addresses are eventually removed.', 'wp-simple-firewall' )
137
  .'<br/>'.__( 'This option lets you specify how long they should be kept.', 'wp-simple-firewall' ),
138
  __( 'Large, permanent IP Block Lists will degrade site performance.', 'wp-simple-firewall' ),
@@ -141,74 +141,98 @@ class Strings extends Base\Strings {
141
  break;
142
 
143
  case 'user_auto_recover' :
144
- $sName = __( 'User Auto Unblock', 'wp-simple-firewall' );
145
- $sSummary = __( 'Allow Visitors To Unblock Their IP', 'wp-simple-firewall' );
146
- $sDescription = __( 'Allow visitors blocked by the plugin to automatically unblock themselves.', 'wp-simple-firewall' );
147
  break;
148
 
149
  case 'request_whitelist' :
150
- $sName = __( 'Request Path Whitelist', 'wp-simple-firewall' );
151
- $sSummary = __( 'Request Path Whitelist', 'wp-simple-firewall' );
152
- $sDescription = __( 'A list of request paths that will never trigger an offense.', 'wp-simple-firewall' )
153
- .'<br />- '.__( 'This is an advanced option and should be used with great care.', 'wp-simple-firewall' )
154
- .'<br />- '.__( 'Take a new line for each whitelisted path.', 'wp-simple-firewall' )
155
- .'<br />- '.__( "All characters will be treated as case-insensitive.", 'wp-simple-firewall' )
156
- .'<br />- '.__( "The paths are compared against only the request path, not the query portion.", 'wp-simple-firewall' )
157
- .'<br />- '.__( "If a path you add matches your website root (/), it'll be removed automatically.", 'wp-simple-firewall' );
158
 
159
  break;
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  case 'text_loginfailed' :
162
- $sName = __( 'Login Failed', 'wp-simple-firewall' );
163
- $sSummary = __( 'Visitor Triggers The IP Offense System Through A Failed Login', 'wp-simple-firewall' );
164
- $sDescription = __( 'This message is displayed if the visitor fails a login attempt.', 'wp-simple-firewall' );
165
  break;
166
 
167
  case 'text_remainingtrans' :
168
- $sName = __( 'Remaining Offenses', 'wp-simple-firewall' );
169
- $sSummary = __( 'Visitor Triggers The IP Offenses System Through A Firewall Block', 'wp-simple-firewall' );
170
- $sDescription = __( 'This message is displayed if the visitor triggered the IP Offense system and reports how many offenses remain before being blocked.', 'wp-simple-firewall' );
171
  break;
172
 
173
  case 'track_404' :
174
- $sName = __( '404 Detect', 'wp-simple-firewall' );
175
- $sSummary = __( 'Identify A Bot When It Hits A 404', 'wp-simple-firewall' );
176
- $sDescription = __( "Detect when a visitor tries to load a non-existent page.", 'wp-simple-firewall' )
177
- .'<br/>'.__( "Care should be taken to ensure you don't have legitimate links on your site that are 404s.", 'wp-simple-firewall' );
178
  break;
179
 
180
  case 'track_xmlrpc' :
181
- $sName = __( 'XML-RPC Access', 'wp-simple-firewall' );
182
- $sSummary = __( 'Identify A Bot When It Accesses XML-RPC', 'wp-simple-firewall' );
183
- $sDescription = __( "If you don't use XML-RPC, there's no reason anything should be accessing it.", 'wp-simple-firewall' )
184
- .'<br/>'.__( "Be careful the ensure you don't block legitimate XML-RPC traffic if your site needs it.", 'wp-simple-firewall' )
185
- .'<br/>'.__( "We recommend logging here in-case of blocking valid request unless you're sure.", 'wp-simple-firewall' );
186
  break;
187
 
188
  case 'track_linkcheese' :
189
- $sName = __( 'Link Cheese', 'wp-simple-firewall' );
190
- $sSummary = __( 'Tempt A Bot With A Fake Link To Follow', 'wp-simple-firewall' );
191
- $sDescription = __( "Detect a bot when it follows a fake 'no-follow' link.", 'wp-simple-firewall' )
192
- .'<br/>'.__( "This works because legitimate web crawlers respect 'robots.txt' and 'nofollow' directives.", 'wp-simple-firewall' );
193
  break;
194
 
195
  case 'track_logininvalid' :
196
- $sName = __( 'Invalid Usernames', 'wp-simple-firewall' );
197
- $sSummary = __( "Detect Attempted Logins With Usernames That Don't Exist", 'wp-simple-firewall' );
198
- $sDescription = __( "Identify a Bot when it tries to login with a non-existent username.", 'wp-simple-firewall' )
199
- .'<br/>'.__( "This includes the default 'admin' if you've removed that account.", 'wp-simple-firewall' );
200
  break;
201
 
202
  case 'track_loginfailed' :
203
- $sName = __( 'Failed Login', 'wp-simple-firewall' );
204
- $sSummary = __( 'Detect Failed Login Attempts For Users That Exist', 'wp-simple-firewall' );
205
- $sDescription = __( "Penalise a visitor when they try to login using a valid username, but it fails.", 'wp-simple-firewall' );
 
 
 
 
 
 
 
 
 
 
 
206
  break;
207
 
208
  case 'track_fakewebcrawler' :
209
- $sName = __( 'Fake Web Crawler', 'wp-simple-firewall' );
210
- $sSummary = __( 'Detect Fake Search Engine Crawlers', 'wp-simple-firewall' );
211
- $sDescription = [
212
  __( "Identify a visitor as a Bot when it presents as an official web crawler, but analysis shows it's fake.", 'wp-simple-firewall' ),
213
  __( "Many bots pretend to be a Google Bot.", 'wp-simple-firewall' )
214
  .'<br/>'.__( "We can then know that a bot isn't here for anything good and block them.", 'wp-simple-firewall' ),
@@ -216,9 +240,9 @@ class Strings extends Base\Strings {
216
  break;
217
 
218
  case 'track_useragent' :
219
- $sName = __( 'Empty User Agents', 'wp-simple-firewall' );
220
- $sSummary = __( 'Detect Requests With Empty User Agents', 'wp-simple-firewall' );
221
- $sDescription = [
222
  __( "Identify a bot when the user agent is not provided.", 'wp-simple-firewall' ),
223
  sprintf( '%s:<br/><code>%s</code>',
224
  __( 'For example, your browser user agent is', 'wp-simple-firewall' ), Services::Request()
@@ -231,9 +255,45 @@ class Strings extends Base\Strings {
231
  }
232
 
233
  return [
234
- 'name' => $sName,
235
- 'summary' => $sSummary,
236
- 'description' => $sDescription,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  ];
238
  }
239
 
@@ -265,7 +325,8 @@ class Strings extends Base\Strings {
265
  __( '404 detected at "%s".', 'wp-simple-firewall' )
266
  ],
267
  'bottrack_fakewebcrawler' => [
268
- __( 'Fake Web Crawler detected at "%s".', 'wp-simple-firewall' )
 
269
  ],
270
  'bottrack_linkcheese' => [
271
  __( 'Link cheese access detected at "%s".', 'wp-simple-firewall' )
@@ -282,6 +343,9 @@ class Strings extends Base\Strings {
282
  'bottrack_xmlrpc' => [
283
  __( 'Access to XML-RPC detected at "%s".', 'wp-simple-firewall' )
284
  ],
 
 
 
285
  ];
286
  }
287
  }
114
  switch ( $key ) {
115
 
116
  case 'enable_ips' :
117
+ $name = sprintf( __( 'Enable %s Module', 'wp-simple-firewall' ), $sModName );
118
+ $summary = sprintf( __( 'Enable (or Disable) The %s Module', 'wp-simple-firewall' ), $sModName );
119
+ $desc = sprintf( __( 'Un-Checking this option will completely disable the %s module.', 'wp-simple-firewall' ), $sModName );
120
  break;
121
 
122
  case 'transgression_limit' :
123
+ $name = __( 'Offense Limit', 'wp-simple-firewall' );
124
+ $summary = __( 'Visitor IP address will be Black Listed after X bad actions on your site', 'wp-simple-firewall' );
125
+ $desc = [
126
  sprintf( __( 'An offense is registered against an IP address each time a visitor trips the defenses of the %s plugin.', 'wp-simple-firewall' ), $sPlugName ),
127
  __( 'When the number of these offenses exceeds the limit, they are automatically blocked from accessing the site.', 'wp-simple-firewall' ),
128
  sprintf( __( 'Set this to "0" to turn off the %s feature.', 'wp-simple-firewall' ), __( 'Automatic IP Black List', 'wp-simple-firewall' ) )
130
  break;
131
 
132
  case 'auto_expire' :
133
+ $name = __( 'Auto Block Expiration', 'wp-simple-firewall' );
134
+ $summary = __( 'After 1 "X" a black listed IP will be removed from the black list', 'wp-simple-firewall' );
135
+ $desc = [
136
  __( 'Blocked IP addresses are eventually removed.', 'wp-simple-firewall' )
137
  .'<br/>'.__( 'This option lets you specify how long they should be kept.', 'wp-simple-firewall' ),
138
  __( 'Large, permanent IP Block Lists will degrade site performance.', 'wp-simple-firewall' ),
141
  break;
142
 
143
  case 'user_auto_recover' :
144
+ $name = __( 'User Auto Unblock', 'wp-simple-firewall' );
145
+ $summary = __( 'Allow Visitors To Unblock Their IP', 'wp-simple-firewall' );
146
+ $desc = __( 'Allow visitors blocked by the plugin to automatically unblock themselves.', 'wp-simple-firewall' );
147
  break;
148
 
149
  case 'request_whitelist' :
150
+ $name = __( 'Request Path Whitelist', 'wp-simple-firewall' );
151
+ $summary = __( 'Request Path Whitelist', 'wp-simple-firewall' );
152
+ $desc = __( 'A list of request paths that will never trigger an offense.', 'wp-simple-firewall' )
153
+ .'<br />- '.__( 'This is an advanced option and should be used with great care.', 'wp-simple-firewall' )
154
+ .'<br />- '.__( 'Take a new line for each whitelisted path.', 'wp-simple-firewall' )
155
+ .'<br />- '.__( "All characters will be treated as case-insensitive.", 'wp-simple-firewall' )
156
+ .'<br />- '.__( "The paths are compared against only the request path, not the query portion.", 'wp-simple-firewall' )
157
+ .'<br />- '.__( "If a path you add matches your website root (/), it'll be removed automatically.", 'wp-simple-firewall' );
158
 
159
  break;
160
 
161
+ case 'antibot_minimum' :
162
+ $name = __( 'AntiBot Minimum Score', 'wp-simple-firewall' );
163
+ $summary = __( 'AntiBot Minimum Score (Percentage)', 'wp-simple-firewall' );
164
+ $desc = [
165
+ __( "Every IP address accessing your site gets its own unique visitor score - the higher the score, the better the visitor i.e. the more likely it's human.", 'wp-simple-firewall' ),
166
+ __( "A score of '100' would mean it's almost certainly good, a score of '0' means it's highly likely to be a bad bot.", 'wp-simple-firewall' ),
167
+ __( 'When a bot tries to login, or post a comment, we test its visitor score.', 'wp-simple-firewall' )
168
+ .' '.__( 'If the visitor score fails to meet your Minimum AntiBot Score, we prevent the request. If its higher, we allow it.', 'wp-simple-firewall' ),
169
+ __( "This means: choose a higher minimum score to be more strict and capture more bots (but potentially block someone that appears to be a bot, but isn't).", 'wp-simple-firewall' )
170
+ .' '.__( "Or choose a lower minimum score to perhaps allow through more bots (but reduce the chances of accidentally blocking legitimate visitors).", 'wp-simple-firewall' ),
171
+ ];
172
+ break;
173
+
174
  case 'text_loginfailed' :
175
+ $name = __( 'Login Failed', 'wp-simple-firewall' );
176
+ $summary = __( 'Visitor Triggers The IP Offense System Through A Failed Login', 'wp-simple-firewall' );
177
+ $desc = __( 'This message is displayed if the visitor fails a login attempt.', 'wp-simple-firewall' );
178
  break;
179
 
180
  case 'text_remainingtrans' :
181
+ $name = __( 'Remaining Offenses', 'wp-simple-firewall' );
182
+ $summary = __( 'Visitor Triggers The IP Offenses System Through A Firewall Block', 'wp-simple-firewall' );
183
+ $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' );
184
  break;
185
 
186
  case 'track_404' :
187
+ $name = __( '404 Detect', 'wp-simple-firewall' );
188
+ $summary = __( 'Identify A Bot When It Hits A 404', 'wp-simple-firewall' );
189
+ $desc = __( "Detect when a visitor tries to load a non-existent page.", 'wp-simple-firewall' )
190
+ .'<br/>'.__( "Care should be taken to ensure you don't have legitimate links on your site that are 404s.", 'wp-simple-firewall' );
191
  break;
192
 
193
  case 'track_xmlrpc' :
194
+ $name = __( 'XML-RPC Access', 'wp-simple-firewall' );
195
+ $summary = __( 'Identify A Bot When It Accesses XML-RPC', 'wp-simple-firewall' );
196
+ $desc = __( "If you don't use XML-RPC, there's no reason anything should be accessing it.", 'wp-simple-firewall' )
197
+ .'<br/>'.__( "Be careful the ensure you don't block legitimate XML-RPC traffic if your site needs it.", 'wp-simple-firewall' )
198
+ .'<br/>'.__( "We recommend logging here in-case of blocking valid request unless you're sure.", 'wp-simple-firewall' );
199
  break;
200
 
201
  case 'track_linkcheese' :
202
+ $name = __( 'Link Cheese', 'wp-simple-firewall' );
203
+ $summary = __( 'Tempt A Bot With A Fake Link To Follow', 'wp-simple-firewall' );
204
+ $desc = __( "Detect a bot when it follows a fake 'no-follow' link.", 'wp-simple-firewall' )
205
+ .'<br/>'.__( "This works because legitimate web crawlers respect 'robots.txt' and 'nofollow' directives.", 'wp-simple-firewall' );
206
  break;
207
 
208
  case 'track_logininvalid' :
209
+ $name = __( 'Invalid Usernames', 'wp-simple-firewall' );
210
+ $summary = __( "Detect Attempted Logins With Usernames That Don't Exist", 'wp-simple-firewall' );
211
+ $desc = __( "Identify a Bot when it tries to login with a non-existent username.", 'wp-simple-firewall' )
212
+ .'<br/>'.__( "This includes the default 'admin' if you've removed that account.", 'wp-simple-firewall' );
213
  break;
214
 
215
  case 'track_loginfailed' :
216
+ $name = __( 'Failed Login', 'wp-simple-firewall' );
217
+ $summary = __( 'Detect Failed Login Attempts For Users That Exist', 'wp-simple-firewall' );
218
+ $desc = __( "Penalise a visitor when they try to login using a valid username, but it fails.", 'wp-simple-firewall' );
219
+ break;
220
+
221
+ case 'track_invalidscript' :
222
+ $name = __( 'Invalid Script Load', 'wp-simple-firewall' );
223
+ $summary = __( 'Identify Bot Attempts To Load WordPress In A Non-Standard Way', 'wp-simple-firewall' );
224
+ $desc = [
225
+ __( "Detect when a bot tries to load WordPress directly from a file that isn't normally used to load WordPress.", 'wp-simple-firewall' ),
226
+ __( 'WordPress should only be loaded in a limited number of ways.', 'wp-simple-firewall' ),
227
+ sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ),
228
+ sprintf( __( 'Set this option to "%s" and monitor the Audit Trail, since some plugins, themes, or custom integrations may trigger this.', 'wp-simple-firewall' ), __( 'Audit Log Only', 'wp-simple-firewall' ) ) )
229
+ ];
230
  break;
231
 
232
  case 'track_fakewebcrawler' :
233
+ $name = __( 'Fake Web Crawler', 'wp-simple-firewall' );
234
+ $summary = __( 'Detect Fake Search Engine Crawlers', 'wp-simple-firewall' );
235
+ $desc = [
236
  __( "Identify a visitor as a Bot when it presents as an official web crawler, but analysis shows it's fake.", 'wp-simple-firewall' ),
237
  __( "Many bots pretend to be a Google Bot.", 'wp-simple-firewall' )
238
  .'<br/>'.__( "We can then know that a bot isn't here for anything good and block them.", 'wp-simple-firewall' ),
240
  break;
241
 
242
  case 'track_useragent' :
243
+ $name = __( 'Empty User Agents', 'wp-simple-firewall' );
244
+ $summary = __( 'Detect Requests With Empty User Agents', 'wp-simple-firewall' );
245
+ $desc = [
246
  __( "Identify a bot when the user agent is not provided.", 'wp-simple-firewall' ),
247
  sprintf( '%s:<br/><code>%s</code>',
248
  __( 'For example, your browser user agent is', 'wp-simple-firewall' ), Services::Request()
255
  }
256
 
257
  return [
258
+ 'name' => $name,
259
+ 'summary' => $summary,
260
+ 'description' => $desc,
261
+ ];
262
+ }
263
+
264
+ public function getBotSignalName( $field ) :string {
265
+ return $this->getBotSignalNames()[ str_replace( '_at', '', $field ) ] ?? 'Unknown';
266
+ }
267
+
268
+ /**
269
+ * @return string[]
270
+ */
271
+ public function getBotSignalNames() :array {
272
+ return [
273
+ 'known' => __( 'A Known Service Provider/Bot', 'wp-simple-firewall' ),
274
+ 'notbot' => __( '"Not Bot" Registration', 'wp-simple-firewall' ),
275
+ 'frontpage' => __( 'Normal Frontpage Visited', 'wp-simple-firewall' ),
276
+ 'bt404' => __( '404 Triggered', 'wp-simple-firewall' ),
277
+ 'btfake' => __( 'Fake Web Crawler', 'wp-simple-firewall' ),
278
+ 'btcheese' => __( 'Link Cheese', 'wp-simple-firewall' ),
279
+ 'btloginfail' => __( 'Login Fail', 'wp-simple-firewall' ),
280
+ 'btua' => __( 'Invalid User Agent', 'wp-simple-firewall' ),
281
+ 'btxml' => __( 'XMLRPC Access', 'wp-simple-firewall' ),
282
+ 'btlogininvalid' => __( 'Invalid Login Username', 'wp-simple-firewall' ),
283
+ 'btinvalidscript' => __( 'Invalid Script Access', 'wp-simple-firewall' ),
284
+ 'cooldown' => __( 'Cooldown Triggered', 'wp-simple-firewall' ),
285
+ 'humanspam' => __( 'Comment Triggered Human SPAM Detection', 'wp-simple-firewall' ),
286
+ 'markspam' => __( 'Comment Marked As SPAM', 'wp-simple-firewall' ),
287
+ 'unmarkspam' => __( 'Comment Unmarked As SPAM', 'wp-simple-firewall' ),
288
+ 'auth' => __( 'Authenticated With Site', 'wp-simple-firewall' ),
289
+ 'ratelimit' => __( 'Rate Limit Exceeded', 'wp-simple-firewall' ),
290
+ 'captchapass' => __( 'Captcha Verification Passed', 'wp-simple-firewall' ),
291
+ 'captchafail' => __( 'Captcha Verification Failed', 'wp-simple-firewall' ),
292
+ 'firewall' => __( 'Firewall Triggered', 'wp-simple-firewall' ),
293
+ 'offense' => __( 'Offense Triggered', 'wp-simple-firewall' ),
294
+ 'blocked' => __( 'IP Blocked', 'wp-simple-firewall' ),
295
+ 'unblocked' => __( 'IP Unblocked', 'wp-simple-firewall' ),
296
+ 'bypass' => __( 'IP Bypassed', 'wp-simple-firewall' ),
297
  ];
298
  }
299
 
325
  __( '404 detected at "%s".', 'wp-simple-firewall' )
326
  ],
327
  'bottrack_fakewebcrawler' => [
328
+ __( 'Fake Web Crawler detected at "%s".', 'wp-simple-firewall' ),
329
+ __( 'Fake Crawler misrepresented itself as "%s".', 'wp-simple-firewall' ),
330
  ],
331
  'bottrack_linkcheese' => [
332
  __( 'Link cheese access detected at "%s".', 'wp-simple-firewall' )
343
  'bottrack_xmlrpc' => [
344
  __( 'Access to XML-RPC detected at "%s".', 'wp-simple-firewall' )
345
  ],
346
+ 'bottrack_invalidscript' => [
347
+ __( 'Tried to load an invalid WordPress PHP script "%s".', 'wp-simple-firewall' )
348
+ ],
349
  ];
350
  }
351
  }
src/lib/src/Modules/IPs/UI.php CHANGED
@@ -66,6 +66,11 @@ class UI extends BaseShield\UI {
66
  'href' => $mod->getUrl_AdminPage(),
67
  'title' => __( 'IP Block Settings', 'wp-simple-firewall' ),
68
  ],
 
 
 
 
 
69
  ],
70
  'unique_ips_black' => ( new RetrieveIpsForLists() )
71
  ->setDbHandler( $mod->getDbHandler_IPs() )
66
  'href' => $mod->getUrl_AdminPage(),
67
  'title' => __( 'IP Block Settings', 'wp-simple-firewall' ),
68
  ],
69
+ [
70
+ 'href' => $mod->createFileDownloadLink( 'db_ip' ),
71
+ 'classes' => [ 'shield_file_download' ],
72
+ 'title' => sprintf( __( 'Download (as %s)', 'wp-simple-firewall' ), 'CSV' ),
73
+ ],
74
  ],
75
  'unique_ips_black' => ( new RetrieveIpsForLists() )
76
  ->setDbHandler( $mod->getDbHandler_IPs() )
src/lib/src/Modules/IPs/Upgrade.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
 
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
 
src/lib/src/Modules/IPs/WpCli/BaseAddRemove.php CHANGED
@@ -9,7 +9,7 @@ class BaseAddRemove extends BaseWpCliCmd {
9
  /**
10
  * @return array[]
11
  */
12
- protected function getCommonIpCmdArgs() {
13
  return [
14
  [
15
  'type' => 'assoc',
@@ -22,6 +22,8 @@ class BaseAddRemove extends BaseWpCliCmd {
22
  'name' => 'list',
23
  'optional' => false,
24
  'options' => [
 
 
25
  'white',
26
  'black',
27
  ],
9
  /**
10
  * @return array[]
11
  */
12
+ protected function getCommonIpCmdArgs() :array {
13
  return [
14
  [
15
  'type' => 'assoc',
22
  'name' => 'list',
23
  'optional' => false,
24
  'options' => [
25
+ 'bypass',
26
+ 'block',
27
  'white',
28
  'black',
29
  ],
src/lib/src/Modules/IPs/WpCli/Remove.php CHANGED
@@ -29,7 +29,7 @@ class Remove extends BaseAddRemove {
29
  $mod = $this->getMod();
30
 
31
  $oDel = ( new IPs\Lib\Ops\DeleteIp() )
32
- ->setDbHandler( $mod->getDbHandler_IPs() )
33
  ->setIP( $aA[ 'ip' ] );
34
  if ( $aA[ 'list' ] === 'white' ) {
35
  $bSuccess = $oDel->fromWhiteList();
29
  $mod = $this->getMod();
30
 
31
  $oDel = ( new IPs\Lib\Ops\DeleteIp() )
32
+ ->setMod( $mod )
33
  ->setIP( $aA[ 'ip' ] );
34
  if ( $aA[ 'list' ] === 'white' ) {
35
  $bSuccess = $oDel->fromWhiteList();
src/lib/src/Modules/Insights/ModCon.php CHANGED
@@ -22,8 +22,8 @@ class ModCon extends BaseShield\ModCon {
22
 
23
  private function maybeRedirectToAdmin() {
24
  $con = $this->getCon();
25
- $nActiveFor = $con->getModule_Plugin()->getActivateLength();
26
- if ( !Services::WpGeneral()->isAjax() && is_admin() && !$con->isModulePage() && $nActiveFor < 4 ) {
27
  Services::Response()->redirect( $this->getUrl_AdminPage() );
28
  }
29
  }
@@ -39,7 +39,7 @@ class ModCon extends BaseShield\ModCon {
39
  );
40
  }
41
 
42
- protected function renderModulePage( array $aData = [] ) :string {
43
  /** @var UI $UI */
44
  $UI = $this->getUIHandler();
45
  return $UI->renderPages();
@@ -79,7 +79,6 @@ class ModCon extends BaseShield\ModCon {
79
  $iNav = Services::Request()->query( 'inav' );
80
 
81
  if ( $con->getIsPage_PluginAdmin() && !empty( $iNav ) ) {
82
- $oTourManager = $con->getModule_Plugin()->getTourManager();
83
  switch ( $iNav ) {
84
 
85
  case 'importexport':
@@ -87,25 +86,24 @@ class ModCon extends BaseShield\ModCon {
87
  break;
88
 
89
  case 'overview':
90
- case 'reports':
91
-
92
  $enq[ Enqueue::JS ] = [
93
- 'chartist.min',
94
- 'chartist-plugin-legend',
95
- 'charts',
96
  'shuffle',
97
- 'shield-card-shuffle',
98
  'ip_detect'
99
  ];
 
 
 
 
 
 
 
 
100
  $enq[ Enqueue::CSS ] = [
101
- 'chartist.min',
102
- 'chartist-plugin-legend'
 
103
  ];
104
-
105
- if ( $oTourManager->canShow( 'insights_overview' ) ) {
106
- $enq[ Enqueue::JS ][] = 'introjs.min';
107
- $enq[ Enqueue::CSS ][] = 'introjs.min';
108
- }
109
  break;
110
 
111
  case 'notes':
@@ -134,10 +132,4 @@ class ModCon extends BaseShield\ModCon {
134
 
135
  return $enq;
136
  }
137
-
138
- /**
139
- * @deprecated 10.2
140
- */
141
- private function includeScriptIpDetect() {
142
- }
143
  }
22
 
23
  private function maybeRedirectToAdmin() {
24
  $con = $this->getCon();
25
+ $activeFor = $con->getModule_Plugin()->getActivateLength();
26
+ if ( !Services::WpGeneral()->isAjax() && is_admin() && !$con->isModulePage() && $activeFor < 4 ) {
27
  Services::Response()->redirect( $this->getUrl_AdminPage() );
28
  }
29
  }
39
  );
40
  }
41
 
42
+ protected function renderModulePage( array $data = [] ) :string {
43
  /** @var UI $UI */
44
  $UI = $this->getUIHandler();
45
  return $UI->renderPages();
79
  $iNav = Services::Request()->query( 'inav' );
80
 
81
  if ( $con->getIsPage_PluginAdmin() && !empty( $iNav ) ) {
 
82
  switch ( $iNav ) {
83
 
84
  case 'importexport':
86
  break;
87
 
88
  case 'overview':
 
 
89
  $enq[ Enqueue::JS ] = [
 
 
 
90
  'shuffle',
91
+ 'shield/shuffle',
92
  'ip_detect'
93
  ];
94
+ break;
95
+
96
+ case 'reports':
97
+ $enq[ Enqueue::JS ] = [
98
+ 'chartist',
99
+ 'chartist-plugin-legend',
100
+ 'shield/charts',
101
+ ];
102
  $enq[ Enqueue::CSS ] = [
103
+ 'chartist',
104
+ 'chartist-plugin-legend',
105
+ 'shield/charts'
106
  ];
 
 
 
 
 
107
  break;
108
 
109
  case 'notes':
132
 
133
  return $enq;
134
  }
 
 
 
 
 
 
135
  }
src/lib/src/Modules/Insights/Strings.php CHANGED
@@ -36,6 +36,7 @@ class Strings extends Base\Strings {
36
  'conn_kill' => __( 'Connection killed for blocked IP address', 'wp-simple-firewall' ),
37
  'ip_offense' => __( 'Offense registered against IP address', 'wp-simple-firewall' ),
38
  'ip_blocked' => __( 'IP address blocked after too many offenses', 'wp-simple-firewall' ),
 
39
  'spam_block_bot' => __( 'Detected comment SPAM from bot', 'wp-simple-firewall' ),
40
  'spam_block_recaptcha' => __( 'Detected comment SPAM from failed reCAPTCHA', 'wp-simple-firewall' ),
41
  'spam_block_human' => __( 'Detected human comment SPAM with suspicious content', 'wp-simple-firewall' ),
@@ -56,12 +57,12 @@ class Strings extends Base\Strings {
56
  * @inheritDoc
57
  */
58
  protected function getAdditionalDisplayStrings() :array {
59
- $sName = $this->getCon()->getHumanName();
60
  return [
61
- 'page_title' => sprintf( __( '%s Security Insights', 'wp-simple-firewall' ), $sName ),
62
  'recommendation' => ucfirst( __( 'recommendation', 'wp-simple-firewall' ) ),
63
  'suggestion' => ucfirst( __( 'suggestion', 'wp-simple-firewall' ) ),
64
- 'box_welcome_title' => sprintf( __( 'Welcome To %s Security Insights Dashboard', 'wp-simple-firewall' ), $sName ),
65
  'options' => __( 'Options', 'wp-simple-firewall' ),
66
  'not_available' => __( 'Sorry, this feature is included with Pro subscriptions.', 'wp-simple-firewall' ),
67
  'not_enabled' => __( "This feature isn't currently enabled.", 'wp-simple-firewall' ),
36
  'conn_kill' => __( 'Connection killed for blocked IP address', 'wp-simple-firewall' ),
37
  'ip_offense' => __( 'Offense registered against IP address', 'wp-simple-firewall' ),
38
  'ip_blocked' => __( 'IP address blocked after too many offenses', 'wp-simple-firewall' ),
39
+ 'spam_block_antibot' => __( 'Detected comment SPAM using AntiBot', 'wp-simple-firewall' ),
40
  'spam_block_bot' => __( 'Detected comment SPAM from bot', 'wp-simple-firewall' ),
41
  'spam_block_recaptcha' => __( 'Detected comment SPAM from failed reCAPTCHA', 'wp-simple-firewall' ),
42
  'spam_block_human' => __( 'Detected human comment SPAM with suspicious content', 'wp-simple-firewall' ),
57
  * @inheritDoc
58
  */
59
  protected function getAdditionalDisplayStrings() :array {
60
+ $name = $this->getCon()->getHumanName();
61
  return [
62
+ 'page_title' => sprintf( __( '%s Security Insights', 'wp-simple-firewall' ), $name ),
63
  'recommendation' => ucfirst( __( 'recommendation', 'wp-simple-firewall' ) ),
64
  'suggestion' => ucfirst( __( 'suggestion', 'wp-simple-firewall' ) ),
65
+ 'box_welcome_title' => sprintf( __( 'Welcome To %s Security Insights Dashboard', 'wp-simple-firewall' ), $name ),
66
  'options' => __( 'Options', 'wp-simple-firewall' ),
67
  'not_available' => __( 'Sorry, this feature is included with Pro subscriptions.', 'wp-simple-firewall' ),
68
  'not_enabled' => __( "This feature isn't currently enabled.", 'wp-simple-firewall' ),
src/lib/src/Modules/Insights/UI.php CHANGED
@@ -56,16 +56,13 @@ class UI extends BaseShield\UI {
56
  $subNavSection = $req->query( 'subnav' );
57
 
58
  $modPlugin = $con->getModule_Plugin();
59
- $oTourManager = $modPlugin->getTourManager();
60
- if ( !$oTourManager->isCompleted( 'insights_overview' ) && $modPlugin->getActivateLength() > 600 ) {
61
- $oTourManager->setCompleted( 'insights_overview' );
62
- }
63
 
64
  switch ( $sNavSection ) {
65
 
66
  case 'audit':
 
67
  /** @var Shield\Modules\AuditTrail\UI $auditUI */
68
- $auditUI = $con->getModule_AuditTrail()->getUIHandler();
69
  $data = [
70
  'content' => [
71
  'table_audit' => $auditUI->renderAuditTrailTable(),
@@ -85,14 +82,20 @@ class UI extends BaseShield\UI {
85
  'href' => $mod->getUrl_SubInsightsPage( 'traffic' ),
86
  'title' => __( 'Traffic Log', 'wp-simple-firewall' ),
87
  ],
 
 
 
 
 
88
  ]
89
  ]
90
  ];
91
  break;
92
 
93
  case 'traffic':
 
94
  /** @var Shield\Modules\Traffic\UI $trafficUI */
95
- $trafficUI = $con->getModule_Traffic()->getUIHandler();
96
  $data = [
97
  'content' => [
98
  'table_traffic' => $trafficUI->renderTrafficTable(),
@@ -107,6 +110,11 @@ class UI extends BaseShield\UI {
107
  'href' => $mod->getUrl_SubInsightsPage( 'audit' ),
108
  'title' => __( 'Audit Trail', 'wp-simple-firewall' ),
109
  ],
 
 
 
 
 
110
  ]
111
  ]
112
  ];
@@ -231,18 +239,14 @@ class UI extends BaseShield\UI {
231
  'page_container' => 'page-insights page-'.$sNavSection
232
  ],
233
  'flags' => [
234
- 'is_dashboard' => $sNavSection === 'dashboard',
235
- 'show_guided_tour' => $modPlugin->getIfShowIntroVideo(),
236
- 'tours' => [
237
- 'insights_overview' => false && $oTourManager->canShow( 'insights_overview' )
238
- ],
239
- 'is_advanced' => $modPlugin->isShowAdvanced()
240
  ],
241
  'hrefs' => [
242
  'back_to_dash' => $mod->getUrl_SubInsightsPage( 'dashboard' ),
243
  'go_pro' => 'https://shsec.io/shieldgoprofeature',
244
  'nav_home' => $mod->getUrl_AdminPage(),
245
- 'img_banner' => $con->getPluginUrl_Image( 'pluginlogo_banner-170x40.png' )
246
  ],
247
  'strings' => [
248
  'page_title' => $pageTitle
56
  $subNavSection = $req->query( 'subnav' );
57
 
58
  $modPlugin = $con->getModule_Plugin();
 
 
 
 
59
 
60
  switch ( $sNavSection ) {
61
 
62
  case 'audit':
63
+ $modAudit = $con->getModule_AuditTrail();
64
  /** @var Shield\Modules\AuditTrail\UI $auditUI */
65
+ $auditUI = $modAudit->getUIHandler();
66
  $data = [
67
  'content' => [
68
  'table_audit' => $auditUI->renderAuditTrailTable(),
82
  'href' => $mod->getUrl_SubInsightsPage( 'traffic' ),
83
  'title' => __( 'Traffic Log', 'wp-simple-firewall' ),
84
  ],
85
+ [
86
+ 'href' => $modAudit->createFileDownloadLink( 'db_audit' ),
87
+ 'classes' => [ 'shield_file_download' ],
88
+ 'title' => sprintf( __( 'Download (as %s)', 'wp-simple-firewall' ), 'CSV' ),
89
+ ],
90
  ]
91
  ]
92
  ];
93
  break;
94
 
95
  case 'traffic':
96
+ $modTraffic = $con->getModule_Traffic();
97
  /** @var Shield\Modules\Traffic\UI $trafficUI */
98
+ $trafficUI = $modTraffic->getUIHandler();
99
  $data = [
100
  'content' => [
101
  'table_traffic' => $trafficUI->renderTrafficTable(),
110
  'href' => $mod->getUrl_SubInsightsPage( 'audit' ),
111
  'title' => __( 'Audit Trail', 'wp-simple-firewall' ),
112
  ],
113
+ [
114
+ 'href' => $modTraffic->createFileDownloadLink( 'db_traffic' ),
115
+ 'classes' => [ 'shield_file_download' ],
116
+ 'title' => sprintf( __( 'Download (as %s)', 'wp-simple-firewall' ), 'CSV' ),
117
+ ],
118
  ]
119
  ]
120
  ];
239
  'page_container' => 'page-insights page-'.$sNavSection
240
  ],
241
  'flags' => [
242
+ 'is_dashboard' => $sNavSection === 'dashboard',
243
+ 'is_advanced' => $modPlugin->isShowAdvanced()
 
 
 
 
244
  ],
245
  'hrefs' => [
246
  'back_to_dash' => $mod->getUrl_SubInsightsPage( 'dashboard' ),
247
  'go_pro' => 'https://shsec.io/shieldgoprofeature',
248
  'nav_home' => $mod->getUrl_AdminPage(),
249
+ 'img_banner' => $con->urls->forImage( 'pluginlogo_banner-170x40.png' )
250
  ],
251
  'strings' => [
252
  'page_title' => $pageTitle
src/lib/src/Modules/Integrations/Lib/MainWP/Common/MWPExtensionVO.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
  use MainWP\Dashboard\MainWP_Extensions_Handler;
7
 
8
  /**
@@ -10,29 +10,25 @@ use MainWP\Dashboard\MainWP_Extensions_Handler;
10
  * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common
11
  * @property string $page - e.g. Extensions-Wp-Simple-Firewall
12
  */
13
- class MWPExtensionVO {
14
-
15
- use StdClassAdapter {
16
- __get as __adapterGet;
17
- }
18
 
19
  /**
20
- * @param string $property
21
  * @return mixed
22
  */
23
- public function __get( $property ) {
24
 
25
- $mValue = $this->__adapterGet( $property );
26
 
27
- switch ( $property ) {
28
  case 'official_extension_data':
29
- $mValue = $this->findOfficialExtensionData();
30
  break;
31
  default:
32
  break;
33
  }
34
 
35
- return $mValue;
36
  }
37
 
38
  private function findOfficialExtensionData() :array {
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
  use MainWP\Dashboard\MainWP_Extensions_Handler;
7
 
8
  /**
10
  * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common
11
  * @property string $page - e.g. Extensions-Wp-Simple-Firewall
12
  */
13
+ class MWPExtensionVO extends DynPropertiesClass {
 
 
 
 
14
 
15
  /**
16
+ * @param string $key
17
  * @return mixed
18
  */
19
+ public function __get( string $key ) {
20
 
21
+ $value = parent::__get( $key );
22
 
23
+ switch ( $key ) {
24
  case 'official_extension_data':
25
+ $value = $this->findOfficialExtensionData();
26
  break;
27
  default:
28
  break;
29
  }
30
 
31
+ return $value;
32
  }
33
 
34
  private function findOfficialExtensionData() :array {
src/lib/src/Modules/Integrations/Lib/MainWP/Common/MWPSiteVO.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
  use FernleafSystems\Wordpress\Services\Services;
7
  use MainWP\Dashboard\MainWP_DB;
8
 
@@ -13,11 +13,7 @@ use MainWP\Dashboard\MainWP_DB;
13
  * @property array[] $plugins
14
  * @property array[] $themes
15
  */
16
- class MWPSiteVO {
17
-
18
- use StdClassAdapter {
19
- __get as __adapterGet;
20
- }
21
 
22
  /**
23
  * @param int $siteID
@@ -35,25 +31,25 @@ class MWPSiteVO {
35
  }
36
 
37
  /**
38
- * @param string $property
39
  * @return mixed
40
  */
41
- public function __get( $property ) {
42
 
43
- $mValue = $this->__adapterGet( $property );
44
 
45
- switch ( $property ) {
46
  case 'siteobj':
47
- $mValue = Services::DataManipulation()->convertArrayToStdClass( $this->getRawDataAsArray() );
48
  break;
49
  case 'plugins':
50
  case 'themes':
51
- $mValue = json_decode( $mValue ?? '[]', true );
52
  break;
53
  default:
54
  break;
55
  }
56
 
57
- return $mValue;
58
  }
59
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
  use FernleafSystems\Wordpress\Services\Services;
7
  use MainWP\Dashboard\MainWP_DB;
8
 
13
  * @property array[] $plugins
14
  * @property array[] $themes
15
  */
16
+ class MWPSiteVO extends DynPropertiesClass {
 
 
 
 
17
 
18
  /**
19
  * @param int $siteID
31
  }
32
 
33
  /**
34
+ * @param string $key
35
  * @return mixed
36
  */
37
+ public function __get( string $key ) {
38
 
39
+ $value = parent::__get( $key );
40
 
41
+ switch ( $key ) {
42
  case 'siteobj':
43
+ $value = Services::DataManipulation()->convertArrayToStdClass( $this->getRawData() );
44
  break;
45
  case 'plugins':
46
  case 'themes':
47
+ $value = json_decode( $value ?? '[]', true );
48
  break;
49
  default:
50
  break;
51
  }
52
 
53
+ return $value;
54
  }
55
  }
src/lib/src/Modules/Integrations/Lib/MainWP/Common/MainWPVO.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
  use MainWP\Dashboard\MainWP_Extensions_Handler;
7
 
8
  /**
@@ -15,32 +15,28 @@ use MainWP\Dashboard\MainWP_Extensions_Handler;
15
  * @property array $official_extension_data
16
  * @property MWPExtensionVO $extension
17
  */
18
- class MainWPVO {
19
-
20
- use StdClassAdapter {
21
- __get as __adapterGet;
22
- }
23
 
24
  /**
25
- * @param string $property
26
  * @return mixed
27
  */
28
- public function __get( $property ) {
29
 
30
- $mValue = $this->__adapterGet( $property );
31
 
32
- switch ( $property ) {
33
  case 'official_extension_data':
34
- $mValue = $this->findOfficialExtensionData();
35
  break;
36
  case 'extension':
37
- $mValue = ( new MWPExtensionVO() )->applyFromArray( $this->official_extension_data );
38
  break;
39
  default:
40
  break;
41
  }
42
 
43
- return $mValue;
44
  }
45
 
46
  private function findOfficialExtensionData() :array {
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
  use MainWP\Dashboard\MainWP_Extensions_Handler;
7
 
8
  /**
15
  * @property array $official_extension_data
16
  * @property MWPExtensionVO $extension
17
  */
18
+ class MainWPVO extends DynPropertiesClass {
 
 
 
 
19
 
20
  /**
21
+ * @param string $key
22
  * @return mixed
23
  */
24
+ public function __get( string $key ) {
25
 
26
+ $value = parent::__get( $key );
27
 
28
+ switch ( $key ) {
29
  case 'official_extension_data':
30
+ $value = $this->findOfficialExtensionData();
31
  break;
32
  case 'extension':
33
+ $value = ( new MWPExtensionVO() )->applyFromArray( $this->official_extension_data );
34
  break;
35
  default:
36
  break;
37
  }
38
 
39
+ return $value;
40
  }
41
 
42
  private function findOfficialExtensionData() :array {
src/lib/src/Modules/Integrations/Lib/MainWP/Common/SyncMetaVO.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
 
7
  /**
8
  * Class SyncVO - property should align with Sync Meta
@@ -16,5 +16,5 @@ use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
16
  */
17
  class SyncMetaVO {
18
 
19
- use StdClassAdapter;
20
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynProperties;
6
 
7
  /**
8
  * Class SyncVO - property should align with Sync Meta
16
  */
17
  class SyncMetaVO {
18
 
19
+ use DynProperties;
20
  }
src/lib/src/Modules/Integrations/Lib/MainWP/Common/SyncVO.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
 
7
  /**
8
  * Class SyncVO
@@ -10,21 +10,17 @@ use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
10
  * @property array[] $modules
11
  * @property SyncMetaVO $meta
12
  */
13
- class SyncVO {
14
-
15
- use StdClassAdapter {
16
- __get as __adapterGet;
17
- }
18
 
19
  /**
20
- * @param string $property
21
  * @return mixed
22
  */
23
- public function __get( $property ) {
24
 
25
- $mValue = $this->__adapterGet( $property );
26
 
27
- switch ( $property ) {
28
  case 'meta':
29
  $mValue = ( new SyncMetaVO() )->applyFromArray( $mValue );
30
  break;
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
 
7
  /**
8
  * Class SyncVO
10
  * @property array[] $modules
11
  * @property SyncMetaVO $meta
12
  */
13
+ class SyncVO extends DynPropertiesClass {
 
 
 
 
14
 
15
  /**
16
+ * @param string $key
17
  * @return mixed
18
  */
19
+ public function __get( string $key ) {
20
 
21
+ $mValue = parent::__get( $key );
22
 
23
+ switch ( $key ) {
24
  case 'meta':
25
  $mValue = ( new SyncMetaVO() )->applyFromArray( $mValue );
26
  break;
src/lib/src/Modules/Integrations/Lib/MainWP/Controller.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Client;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common\MainWPVO;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server;
@@ -11,7 +11,7 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
11
  class Controller {
12
 
13
  use ModConsumer;
14
- use OneTimeExecute;
15
 
16
  const MIN_VERSION_MAINWP = '4.1';
17
 
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Client;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common\MainWPVO;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server;
11
  class Controller {
12
 
13
  use ModConsumer;
14
+ use ExecOnce;
15
 
16
  const MIN_VERSION_MAINWP = '4.1';
17
 
src/lib/src/Modules/Integrations/Lib/MainWP/Server/Data/ClientPluginStatus.php CHANGED
@@ -34,7 +34,7 @@ class ClientPluginStatus {
34
 
35
  if ( $this->isActive() ) {
36
 
37
- if ( empty( $sync->getRawDataAsArray() ) ) {
38
  $status = self::NEED_SYNC;
39
  }
40
  elseif ( empty( $m->is_pro ) ) {
34
 
35
  if ( $this->isActive() ) {
36
 
37
+ if ( empty( $sync->getRawData() ) ) {
38
  $status = self::NEED_SYNC;
39
  }
40
  elseif ( empty( $m->is_pro ) ) {
src/lib/src/Modules/Integrations/Lib/MainWP/Server/Data/LoadShieldSyncData.php CHANGED
@@ -12,7 +12,7 @@ class LoadShieldSyncData {
12
 
13
  public static function Load( MWPSiteVO $site ) :SyncVO {
14
  $data = MainWP_DB::instance()->get_website_option(
15
- $site->getRawDataAsArray(),
16
  Controller::GetInstance()->prefix( 'mainwp-sync' )
17
  );
18
  if ( empty( $data ) ) {
12
 
13
  public static function Load( MWPSiteVO $site ) :SyncVO {
14
  $data = MainWP_DB::instance()->get_website_option(
15
+ $site->getRawData(),
16
  Controller::GetInstance()->prefix( 'mainwp-sync' )
17
  );
18
  if ( empty( $data ) ) {
src/lib/src/Modules/Integrations/Lib/MainWP/Server/Data/SyncHandler.php CHANGED
@@ -2,14 +2,14 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\Data;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use MainWP\Dashboard\MainWP_DB;
8
 
9
  class SyncHandler {
10
 
11
  use ModConsumer;
12
- use OneTimeExecute;
13
 
14
  protected function run() {
15
  add_action( 'mainwp_sync_others_data', function ( $othersData, $website ) {
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\Data;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use MainWP\Dashboard\MainWP_DB;
8
 
9
  class SyncHandler {
10
 
11
  use ModConsumer;
12
+ use ExecOnce;
13
 
14
  protected function run() {
15
  add_action( 'mainwp_sync_others_data', function ( $othersData, $website ) {
src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/ExtensionSettingsPage.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Controller\Assets\Enqueue;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Controller;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI\PageRender;
@@ -12,7 +12,7 @@ use FernleafSystems\Wordpress\Services\Services;
12
  class ExtensionSettingsPage {
13
 
14
  use ModConsumer;
15
- use OneTimeExecute;
16
 
17
  protected function run() {
18
 
@@ -41,7 +41,7 @@ class ExtensionSettingsPage {
41
  // wp_enqueue_style( 'semantic-ui-datatables-select' );
42
  }
43
  return $enqueues;
44
- }, 10,2 );
45
  }
46
 
47
  /**
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Controller\Assets\Enqueue;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Controller;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI\PageRender;
12
  class ExtensionSettingsPage {
13
 
14
  use ModConsumer;
15
+ use ExecOnce;
16
 
17
  protected function run() {
18
 
41
  // wp_enqueue_style( 'semantic-ui-datatables-select' );
42
  }
43
  return $enqueues;
44
+ }, 10, 2 );
45
  }
46
 
47
  /**
src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/ManageSites/SitesListTableHandler.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI\ManageSites;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common\MWPSiteVO;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\Data\ClientPluginStatus;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\Data\LoadShieldSyncData;
@@ -13,7 +13,7 @@ use FernleafSystems\Wordpress\Services\Services;
13
  class SitesListTableHandler extends BaseRender {
14
 
15
  use ModConsumer;
16
- use OneTimeExecute;
17
 
18
  /**
19
  * @var MWPSiteVO
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI\ManageSites;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common\MWPSiteVO;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\Data\ClientPluginStatus;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\Data\LoadShieldSyncData;
13
  class SitesListTableHandler extends BaseRender {
14
 
15
  use ModConsumer;
16
+ use ExecOnce;
17
 
18
  /**
19
  * @var MWPSiteVO
src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/SitesList.php CHANGED
@@ -28,7 +28,7 @@ class SitesList extends BaseRender {
28
  $sync = LoadShieldSyncData::Load( $mwpSite );
29
  $meta = $sync->meta;
30
 
31
- $shd = $sync->getRawDataAsArray();
32
  $status = ( new ClientPluginStatus() )
33
  ->setMod( $this->getMod() )
34
  ->setMwpSite( $mwpSite )
28
  $sync = LoadShieldSyncData::Load( $mwpSite );
29
  $meta = $sync->meta;
30
 
31
+ $shd = $sync->getRawData();
32
  $status = ( new ClientPluginStatus() )
33
  ->setMod( $this->getMod() )
34
  ->setMwpSite( $mwpSite )
src/lib/src/Modules/Integrations/Lib/Spam/Handlers/Base.php ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Spam\Handlers;
4
+
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+
9
+ abstract class Base {
10
+
11
+ use ModConsumer;
12
+ use ExecOnce;
13
+
14
+ const SLUG = '';
15
+
16
+ protected function canRun() :bool {
17
+ return $this->getCon()->isPremiumActive() && $this->isEnabled() && $this->isProviderAvailable();
18
+ }
19
+
20
+ public function isSpam() :bool {
21
+ $isSpam = $this->isSpam_Bot();
22
+ $this->getCon()->fireEvent(
23
+ sprintf( 'spam_form_%s', $isSpam ? 'fail' : 'pass' ),
24
+ [
25
+ 'audit' => [
26
+ 'form_provider' => $this->getProviderName(),
27
+ ]
28
+ ]
29
+ );
30
+ return $isSpam;
31
+ }
32
+
33
+ protected function isSpam_Bot() :bool {
34
+ return $this->getCon()
35
+ ->getModule_IPs()
36
+ ->getBotSignalsController()
37
+ ->isBot( Services::IP()->getRequestIp() );
38
+ }
39
+
40
+ protected function isSpam_Human() :bool {
41
+ return false;
42
+ }
43
+
44
+ protected function isEnabled() :bool {
45
+ return in_array( $this->getProviderSlug(), $this->getOptions()->getOpt( 'form_spam_providers', [] ) );
46
+ }
47
+
48
+ protected function isProviderAvailable() :bool {
49
+ return false;
50
+ }
51
+
52
+ protected function getProviderName() :string {
53
+ return '';
54
+ }
55
+
56
+ protected function getProviderSlug() :string {
57
+ try {
58
+ $slug = strtolower( ( new \ReflectionClass( $this ) )->getShortName() );
59
+ }
60
+ catch ( \Exception $e ) {
61
+ $slug = '';
62
+ }
63
+ return $slug;
64
+ }
65
+ }
src/lib/src/Modules/Integrations/Lib/Spam/Handlers/ContactForm7.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Spam\Handlers;
4
+
5
+ class ContactForm7 extends Base {
6
+
7
+ protected function run() {
8
+ add_filter( 'wpcf7_spam', function ( $wasSpam, $submission ) {
9
+ return $wasSpam || $this->isSpam();
10
+ }, 1000, 2 );
11
+ }
12
+
13
+ protected function getProviderName() :string {
14
+ return 'Contact Form 7';
15
+ }
16
+
17
+ protected function isProviderAvailable() :bool {
18
+ return defined( 'WPCF7_TEXT_DOMAIN' ) && WPCF7_TEXT_DOMAIN === 'contact-form-7';
19
+ }
20
+ }
src/lib/src/Modules/Integrations/Lib/Spam/Handlers/ElementorPro.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Spam\Handlers;
4
+
5
+ class ElementorPro extends Base {
6
+
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 = sprintf( __( "This appears to be spam - failed %s AntiBot protection checks.", 'wp-simple-firewall' ),
12
+ $this->getCon()->getHumanName() );
13
+ $ajax_handler->add_error( 'shield-antibot', $msg );
14
+ $ajax_handler->add_error_message( $msg );
15
+ }
16
+ }, 1000, 2 );
17
+ }
18
+
19
+ protected function getProviderName() :string {
20
+ return 'Elementor Pro';
21
+ }
22
+
23
+ protected function isProviderAvailable() :bool {
24
+ return defined( 'ELEMENTOR_PRO_VERSION' ) && @function_exists( 'elementor_pro_load_plugin' );
25
+ }
26
+ }
src/lib/src/Modules/Integrations/Lib/Spam/Handlers/FluentForms.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Spam\Handlers;
4
+
5
+ /**
6
+ * This form only provides a filter on the "Akismet" spam result, not a general spam result.
7
+ *
8
+ * Luckily the error message within the plugin is non-Akismet specific.
9
+ *
10
+ * Class FluentForms
11
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Spam\Handlers
12
+ */
13
+ class FluentForms extends Base {
14
+
15
+ protected function run() {
16
+ add_filter( 'fluentform_akismet_spam_result', function ( $wasSpam ) {
17
+ return $wasSpam || $this->isSpam();
18
+ }, 1000 );
19
+ }
20
+
21
+ protected function getProviderName() :string {
22
+ return 'Fluent Forms';
23
+ }
24
+
25
+ protected function isProviderAvailable() :bool {
26
+ return defined( 'FLUENTFORM' ) && @class_exists( '\FluentForm\Framework\Foundation\Bootstrap' );
27
+ }
28
+ }
src/lib/src/Modules/Integrations/Lib/Spam/Handlers/FormidableForms.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Spam\Handlers;
4
+
5
+ class FormidableForms extends Base {
6
+
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
+ }
14
+ // string taken from Formidable forms FrmEntryValidate.php
15
+ $errors[ 'spam' ] = __( 'Your entry appears to be spam!', 'formidable' );
16
+ }
17
+ }
18
+ return $errors;
19
+ }, 1000 );
20
+ }
21
+
22
+ protected function getProviderName() :string {
23
+ return 'Formidable Forms';
24
+ }
25
+
26
+ protected function isProviderAvailable() :bool {
27
+ return function_exists( 'load_formidable_forms' ) && @class_exists( '\FrmHooksController' );
28
+ }
29
+ }
src/lib/src/Modules/Integrations/Lib/Spam/Handlers/Forminator.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Spam\Handlers;
4
+
5
+ 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
+
13
+ protected function getProviderName() :string {
14
+ return 'Forminator';
15
+ }
16
+
17
+ protected function isProviderAvailable() :bool {
18
+ return defined( 'FORMINATOR_VERSION' ) && @class_exists( '\Forminator' );
19
+ }
20
+ }
src/lib/src/Modules/Integrations/Lib/Spam/Handlers/GravityForms.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Spam\Handlers;
4
+
5
+ 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
+
13
+ protected function getProviderName() :string {
14
+ return 'Gravity Forms';
15
+ }
16
+
17
+ protected function isProviderAvailable() :bool {
18
+ return @class_exists( 'GFForms' ) && isset( GFForms::$version )
19
+ && version_compare( GFForms::$version, '2.4.17', '>=' );
20
+ }
21
+ }
src/lib/src/Modules/Integrations/Lib/Spam/Handlers/Helpers/NinjaForms_ShieldSpamAction.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Spam\Handlers\Helpers;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Spam\Handlers\NinjaForms;
6
+
7
+ final class NinjaForms_ShieldSpamAction extends \NF_Abstracts_Action {
8
+
9
+ /**
10
+ * @var string
11
+ */
12
+ protected $_name = 'shieldantibot';
13
+
14
+ /**
15
+ * @var NinjaForms
16
+ */
17
+ private $shieldNinjaFormsHandler;
18
+
19
+ /**
20
+ * @var array
21
+ */
22
+ protected $_tags = [ 'spam', 'filtering', 'shield' ];
23
+
24
+ public function __construct() {
25
+ parent::__construct();
26
+ $this->_nicename = esc_html__( 'Shield Anti-Spam', 'ninja-forms' );
27
+ }
28
+
29
+ /**
30
+ * @param NinjaForms $handler
31
+ * @return $this
32
+ */
33
+ public function setHandler( NinjaForms $handler ) {
34
+ $this->shieldNinjaFormsHandler = $handler;
35
+ return $this;
36
+ }
37
+
38
+ /**
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;
46
+ }
47
+ }
src/lib/src/Modules/Integrations/Lib/Spam/Handlers/KaliForms.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Spam\Handlers;
4
+
5
+ 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' ] = __( 'Your entry appears to be spam!', 'wp-simple-firewall' );
12
+ $data[ 'error_bag' ] = [
13
+ __( 'SPAM Bot detected.', 'wp-simple-firewall' )
14
+ ];
15
+ }
16
+ return $data;
17
+ }, 1000 );
18
+ }
19
+
20
+ protected function getProviderName() :string {
21
+ return 'Kali Forms';
22
+ }
23
+
24
+ protected function isProviderAvailable() :bool {
25
+ return defined( 'KALIFORMS_PLUGIN_FILE' ) && @class_exists( '\KaliForms\Inc\KaliForms' );
26
+ }
27
+ }
src/lib/src/Modules/Integrations/Lib/Spam/Handlers/NinjaForms.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Spam\Handlers;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Spam\Handlers\Helpers\NinjaForms_ShieldSpamAction;
6
+
7
+ /**
8
+ * A rather convoluted way to integrate. First you must add your custom "action" to the list
9
+ * of actions to be executed on a submission.
10
+ *
11
+ * Then you must create a Custom Action class which will handle the action and add it to the
12
+ * registered action.
13
+ *
14
+ * Unfortunately the action register is executed early and so hooking to Init breaks it.
15
+ *
16
+ * Class NinjaForms
17
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Spam\Handlers
18
+ */
19
+ class NinjaForms extends Base {
20
+
21
+ protected function run() {
22
+
23
+ add_filter( 'ninja_forms_register_actions', function ( $actions ) {
24
+ $actions[ 'shieldantibot' ] = ( new NinjaForms_ShieldSpamAction() )
25
+ ->setHandler( $this );
26
+ return $actions;
27
+ }, 1000 );
28
+
29
+ add_filter( 'ninja_forms_submission_actions', function ( $actions ) {
30
+ $actions[] = [
31
+ 'id' => 'shieldantibot',
32
+ 'settings' => [
33
+ 'active' => true,
34
+ 'type' => 'shieldantibot',
35
+ ]
36
+ ];
37
+ return $actions;
38
+ }, 1000 );
39
+ }
40
+
41
+ protected function getProviderName() :string {
42
+ return 'Ninja Forms';
43
+ }
44
+
45
+ protected function isProviderAvailable() :bool {
46
+ return @class_exists( '\Ninja_Forms' );
47
+ }
48
+ }
src/lib/src/Modules/Integrations/Lib/Spam/Handlers/WPForms.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Spam\Handlers;
4
+
5
+ class WPForms extends Base {
6
+
7
+ private $workingFormID = null;
8
+
9
+ protected function run() {
10
+ add_filter( 'wpforms_process_before_form_data',
11
+ function ( $formData, $formEntry ) {
12
+ $this->workingFormID = absint( $formEntry[ 'id' ] );
13
+ return $formData;
14
+ },
15
+ 1000, 2
16
+ );
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' => __( 'Shield detected this as a SPAM Bot submission.' ),
23
+ ];
24
+ }
25
+
26
+ return $errors;
27
+ }, 1000, 2 );
28
+ }
29
+
30
+ protected function getProviderName() :string {
31
+ return 'WP Forms';
32
+ }
33
+
34
+ protected function isProviderAvailable() :bool {
35
+ return defined( 'WPFORMS_VERSION' ) && function_exists( 'wpforms' );
36
+ }
37
+ }
src/lib/src/Modules/Integrations/Lib/Spam/Handlers/WpForo.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Spam\Handlers;
4
+
5
+ class WpForo extends Base {
6
+
7
+ protected function run() {
8
+ foreach ( $this->getFiltersToMonitor() as $filter ) {
9
+ add_filter( $filter, function ( array $args, $forum ) {
10
+
11
+ $status = $args[ 'status' ] ?? null;
12
+ if ( $status !== 1 && $this->isSpam() ) {
13
+ if ( !empty( WPF()->current_userid ) ) {
14
+ WPF()->moderation->ban_for_spam( WPF()->current_userid );
15
+ }
16
+ $args[ 'status' ] = 1; // 1 signifies not approved
17
+ }
18
+
19
+ return $args;
20
+ }, 1000, 2 );
21
+ }
22
+ }
23
+
24
+ private function getFiltersToMonitor() :array {
25
+ return [
26
+ 'wpforo_add_topic_data_filter',
27
+ 'wpforo_edit_topic_data_filter',
28
+ 'wpforo_add_post_data_filter',
29
+ 'wpforo_edit_post_data_filter',
30
+ ];
31
+ }
32
+
33
+ protected function getProviderName() :string {
34
+ return 'wpForo';
35
+ }
36
+
37
+ protected function isProviderAvailable() :bool {
38
+ return function_exists( 'WPF' ) && @class_exists( 'wpForo' ) && !empty( WPF()->tools_antispam[ 'spam_filter' ] );
39
+ }
40
+ }
src/lib/src/Modules/Integrations/Lib/Spam/SpamController.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Spam;
4
+
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
+
8
+ class SpamController {
9
+
10
+ use ModConsumer;
11
+ use ExecOnce;
12
+
13
+ protected function canRun() :bool {
14
+ return $this->isEnabledSpamDetect();
15
+ }
16
+
17
+ protected function run() {
18
+ foreach ( $this->enumProviders() as $provider ) {
19
+ $provider->setMod( $this->getMod() )->execute();
20
+ }
21
+ }
22
+
23
+ private function isEnabledSpamDetect() :bool {
24
+ $opts = $this->getOptions();
25
+ return ( $opts->isOpt( 'enable_spam_antibot', 'Y' ) || $opts->isOpt( 'enable_spam_human', 'Y' ) )
26
+ && !empty( $opts->getOpt( 'form_spam_providers' ) );
27
+ }
28
+
29
+ /**
30
+ * @return Handlers\Base[]
31
+ */
32
+ private function enumProviders() :array {
33
+ return [
34
+ new Handlers\ContactForm7(),
35
+ new Handlers\ElementorPro(),
36
+ new Handlers\FormidableForms(),
37
+ new Handlers\FluentForms(),
38
+ new Handlers\Forminator(),
39
+ new Handlers\GravityForms(),
40
+ new Handlers\KaliForms(),
41
+ new Handlers\NinjaForms(),
42
+ new Handlers\WPForms(),
43
+ new Handlers\WpForo(),
44
+ ];
45
+ }
46
+ }
src/lib/src/Modules/Integrations/Processor.php CHANGED
@@ -7,9 +7,12 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
  class Processor extends BaseShield\Processor {
8
 
9
  protected function run() {
10
- $this->getCon()
11
- ->getModule_Integrations()
12
- ->getControllerMWP()
13
- ->execute();
 
 
 
14
  }
15
  }
7
  class Processor extends BaseShield\Processor {
8
 
9
  protected function run() {
10
+ /** @var ModCon $mod */
11
+ $mod = $this->getMod();
12
+ $mod->getControllerMWP()->execute();
13
+
14
+ ( new Lib\Spam\SpamController() )
15
+ ->setMod( $this->getMod() )
16
+ ->execute();
17
  }
18
  }
src/lib/src/Modules/Integrations/Strings.php CHANGED
@@ -6,6 +6,20 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
 
7
  class Strings extends Base\Strings {
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  /**
10
  * @param string $section
11
  * @return array
6
 
7
  class Strings extends Base\Strings {
8
 
9
+ /**
10
+ * @inheritDoc
11
+ */
12
+ protected function getAuditMessages() :array {
13
+ return [
14
+ 'spam_form_pass' => [
15
+ __( '"%s" submission passed SPAM check.', 'wp-simple-firewall' ),
16
+ ],
17
+ 'spam_form_fail' => [
18
+ __( '"%s" submission failed SPAM check.', 'wp-simple-firewall' )
19
+ ],
20
+ ];
21
+ }
22
+
23
  /**
24
  * @param string $section
25
  * @return array
src/lib/src/Modules/License/Lib/LicenseHandler.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License\Lib;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\License\EddLicenseVO;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\License\ModCon;
@@ -12,7 +12,7 @@ use FernleafSystems\Wordpress\Services\Services;
12
  class LicenseHandler {
13
 
14
  use Modules\ModConsumer;
15
- use OneTimeExecute;
16
 
17
  protected function run() {
18
  add_action( $this->getCon()->prefix( 'shield_action' ), function ( $action ) {
@@ -21,12 +21,12 @@ class LicenseHandler {
21
 
22
  case 'keyless_handshake':
23
  case 'snapi_handshake':
24
- $sNonce = Services::Request()->query( 'nonce' );
25
- if ( !empty( $sNonce ) ) {
26
  die( json_encode( [
27
  'success' => ( new HandshakingNonce() )
28
  ->setMod( $this->getMod() )
29
- ->verify( $sNonce )
30
  ] ) );
31
  }
32
  break;
@@ -85,33 +85,21 @@ class LicenseHandler {
85
  add_filter( $this->getCon()->prefix( 'force_options_resave' ), '__return_true' );
86
  }
87
 
88
- /**
89
- * @return int
90
- */
91
- protected function getActivatedAt() {
92
- return $this->getOptions()->getOpt( 'license_activated_at' );
93
  }
94
 
95
- /**
96
- * @return int
97
- */
98
- protected function getDeactivatedAt() {
99
- return $this->getOptions()->getOpt( 'license_deactivated_at' );
100
  }
101
 
102
- /**
103
- * @return EddLicenseVO
104
- */
105
- public function getLicense() {
106
- $aData = $this->getOptions()->getOpt( 'license_data', [] );
107
- return ( new EddLicenseVO() )->applyFromArray( is_array( $aData ) ? $aData : [] );
108
  }
109
 
110
- /**
111
- * @return int
112
- */
113
- public function getLicenseNotCheckedForInterval() {
114
- return Services::Request()->ts() - $this->getOptions()->getOpt( 'license_last_checked_at' );
115
  }
116
 
117
  /**
@@ -121,7 +109,7 @@ class LicenseHandler {
121
  * plus the grace period.
122
  * @return int
123
  */
124
- public function getRegistrationExpiresAt() {
125
  /** @var ModCon $mod */
126
  $mod = $this->getMod();
127
  $opts = $this->getOptions();
@@ -151,36 +139,24 @@ class LicenseHandler {
151
  return $oLic->isValid() && $this->isActive();
152
  }
153
 
154
- /**
155
- * @return bool
156
- */
157
- public function isActive() {
158
  return ( $this->getActivatedAt() > 0 )
159
  && ( $this->getDeactivatedAt() < $this->getActivatedAt() );
160
  }
161
 
162
- /**
163
- * @return bool
164
- */
165
- public function isLastVerifiedExpired() {
166
  return ( Services::Request()->ts() - $this->getLicense()->last_verified_at )
167
  > $this->getOptions()->getDef( 'lic_verify_expire_days' )*DAY_IN_SECONDS;
168
  }
169
 
170
- /**
171
- * @return bool
172
- */
173
- public function isLastVerifiedGraceExpired() {
174
  $oOpts = $this->getOptions();
175
  $nGracePeriod = ( $oOpts->getDef( 'lic_verify_expire_days' )
176
  + $oOpts->getDef( 'lic_verify_expire_grace_days' ) )*DAY_IN_SECONDS;
177
  return ( Services::Request()->ts() - $this->getLicense()->last_verified_at ) > $nGracePeriod;
178
  }
179
 
180
- /**
181
- * @return bool
182
- */
183
- private function isMaybeExpiring() {
184
  return $this->isActive() &&
185
  (
186
  abs( Services::Request()->ts() - $this->getLicense()->getExpiresAt() )
@@ -188,17 +164,11 @@ class LicenseHandler {
188
  );
189
  }
190
 
191
- /**
192
- * @return bool
193
- */
194
- public function isWithinVerifiedGraceExpired() {
195
  return $this->isLastVerifiedExpired() && !$this->isLastVerifiedGraceExpired();
196
  }
197
 
198
- /**
199
- * @return bool
200
- */
201
- private function isVerifyRequired() {
202
  return ( $this->isMaybeExpiring() && $this->getIsLicenseNotCheckedFor( HOUR_IN_SECONDS*4 ) )
203
  || ( $this->isActive()
204
  && !$this->getLicense()->isReady() && $this->getIsLicenseNotCheckedFor( HOUR_IN_SECONDS ) )
@@ -220,20 +190,13 @@ class LicenseHandler {
220
  return $this;
221
  }
222
 
223
- /**
224
- * @param int $nTimePeriod
225
- * @return bool
226
- */
227
- private function getIsLicenseNotCheckedFor( $nTimePeriod ) {
228
  return $this->getLicenseNotCheckedForInterval() > $nTimePeriod;
229
  }
230
 
231
- /**
232
- * @return bool
233
- */
234
- private function canLicenseCheck_FileFlag() {
235
  $nMtime = (int)Services::WpFs()->getModifiedTime(
236
- $this->getCon()->getPath_Flags( 'license_check' )
237
  );
238
  return ( Services::Request()->ts() - $nMtime ) > MINUTE_IN_SECONDS;
239
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License\Lib;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\License\EddLicenseVO;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\License\ModCon;
12
  class LicenseHandler {
13
 
14
  use Modules\ModConsumer;
15
+ use ExecOnce;
16
 
17
  protected function run() {
18
  add_action( $this->getCon()->prefix( 'shield_action' ), function ( $action ) {
21
 
22
  case 'keyless_handshake':
23
  case 'snapi_handshake':
24
+ $nonce = Services::Request()->query( 'nonce' );
25
+ if ( !empty( $nonce ) ) {
26
  die( json_encode( [
27
  'success' => ( new HandshakingNonce() )
28
  ->setMod( $this->getMod() )
29
+ ->verify( $nonce )
30
  ] ) );
31
  }
32
  break;
85
  add_filter( $this->getCon()->prefix( 'force_options_resave' ), '__return_true' );
86
  }
87
 
88
+ protected function getActivatedAt() :int {
89
+ return (int)$this->getOptions()->getOpt( 'license_activated_at' );
 
 
 
90
  }
91
 
92
+ protected function getDeactivatedAt() :int {
93
+ return (int)$this->getOptions()->getOpt( 'license_deactivated_at' );
 
 
 
94
  }
95
 
96
+ public function getLicense() :EddLicenseVO {
97
+ $data = $this->getOptions()->getOpt( 'license_data', [] );
98
+ return ( new EddLicenseVO() )->applyFromArray( is_array( $data ) ? $data : [] );
 
 
 
99
  }
100
 
101
+ public function getLicenseNotCheckedForInterval() :int {
102
+ return (int)( Services::Request()->ts() - $this->getOptions()->getOpt( 'license_last_checked_at' ) );
 
 
 
103
  }
104
 
105
  /**
109
  * plus the grace period.
110
  * @return int
111
  */
112
+ public function getRegistrationExpiresAt() :int {
113
  /** @var ModCon $mod */
114
  $mod = $this->getMod();
115
  $opts = $this->getOptions();
139
  return $oLic->isValid() && $this->isActive();
140
  }
141
 
142
+ public function isActive() :bool {
 
 
 
143
  return ( $this->getActivatedAt() > 0 )
144
  && ( $this->getDeactivatedAt() < $this->getActivatedAt() );
145
  }
146
 
147
+ public function isLastVerifiedExpired() :bool {
 
 
 
148
  return ( Services::Request()->ts() - $this->getLicense()->last_verified_at )
149
  > $this->getOptions()->getDef( 'lic_verify_expire_days' )*DAY_IN_SECONDS;
150
  }
151
 
152
+ public function isLastVerifiedGraceExpired() :bool {
 
 
 
153
  $oOpts = $this->getOptions();
154
  $nGracePeriod = ( $oOpts->getDef( 'lic_verify_expire_days' )
155
  + $oOpts->getDef( 'lic_verify_expire_grace_days' ) )*DAY_IN_SECONDS;
156
  return ( Services::Request()->ts() - $this->getLicense()->last_verified_at ) > $nGracePeriod;
157
  }
158
 
159
+ private function isMaybeExpiring() :bool {
 
 
 
160
  return $this->isActive() &&
161
  (
162
  abs( Services::Request()->ts() - $this->getLicense()->getExpiresAt() )
164
  );
165
  }
166
 
167
+ public function isWithinVerifiedGraceExpired() :bool {
 
 
 
168
  return $this->isLastVerifiedExpired() && !$this->isLastVerifiedGraceExpired();
169
  }
170
 
171
+ private function isVerifyRequired() :bool {
 
 
 
172
  return ( $this->isMaybeExpiring() && $this->getIsLicenseNotCheckedFor( HOUR_IN_SECONDS*4 ) )
173
  || ( $this->isActive()
174
  && !$this->getLicense()->isReady() && $this->getIsLicenseNotCheckedFor( HOUR_IN_SECONDS ) )
190
  return $this;
191
  }
192
 
193
+ private function getIsLicenseNotCheckedFor( $nTimePeriod ) :bool {
 
 
 
 
194
  return $this->getLicenseNotCheckedForInterval() > $nTimePeriod;
195
  }
196
 
197
+ private function canLicenseCheck_FileFlag() :bool {
 
 
 
198
  $nMtime = (int)Services::WpFs()->getModifiedTime(
199
+ $this->getCon()->paths->forFlag( 'license_check' )
200
  );
201
  return ( Services::Request()->ts() - $nMtime ) > MINUTE_IN_SECONDS;
202
  }
src/lib/src/Modules/License/Lib/PluginNameSuffix.php CHANGED
@@ -2,16 +2,16 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License\Lib;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin;
8
 
9
  class PluginNameSuffix {
10
 
11
  use Modules\ModConsumer;
12
- use OneTimeExecute;
13
 
14
- protected function canRun() {
15
  $con = $this->getCon();
16
  /** @var SecurityAdmin\Options $optsSecAdmin */
17
  $optsSecAdmin = $con->getModule_SecAdmin()->getOptions();
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License\Lib;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin;
8
 
9
  class PluginNameSuffix {
10
 
11
  use Modules\ModConsumer;
12
+ use ExecOnce;
13
 
14
+ protected function canRun() :bool {
15
  $con = $this->getCon();
16
  /** @var SecurityAdmin\Options $optsSecAdmin */
17
  $optsSecAdmin = $con->getModule_SecAdmin()->getOptions();
src/lib/src/Modules/License/Lib/Verify.php CHANGED
@@ -85,10 +85,10 @@ class Verify {
85
  }
86
 
87
  private function preVerify() {
88
- /** @var License\Options $oOpts */
89
- $oOpts = $this->getOptions();
90
- Services::WpFs()->touch( $this->getCon()->getPath_Flags( 'license_check' ) );
91
- $oOpts->setOptAt( 'license_last_checked_at' );
92
  $this->getMod()->saveModOptions();
93
  }
94
  }
85
  }
86
 
87
  private function preVerify() {
88
+ /** @var License\Options $opts */
89
+ $opts = $this->getOptions();
90
+ Services::WpFs()->touch( $this->getCon()->paths->forFlag( 'license_check' ) );
91
+ $opts->setOptAt( 'license_last_checked_at' );
92
  $this->getMod()->saveModOptions();
93
  }
94
  }
src/lib/src/Modules/License/Lib/WpHashes/ApiTokenManager.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License\Lib\WpHashes;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
7
  use FernleafSystems\Wordpress\Services\Services;
8
  use FernleafSystems\Wordpress\Services\Utilities\Integrations\WpHashes\Token;
@@ -10,7 +10,7 @@ use FernleafSystems\Wordpress\Services\Utilities\Integrations\WpHashes\Token;
10
  class ApiTokenManager {
11
 
12
  use Modules\ModConsumer;
13
- use OneTimeExecute;
14
 
15
  /**
16
  * @var bool
@@ -18,8 +18,8 @@ class ApiTokenManager {
18
  private $bCanRequestOverride = false;
19
 
20
  protected function run() {
21
- add_action( $this->getCon()->prefix( 'event' ), function ( $sEventTag ) {
22
- switch ( $sEventTag ) {
23
  case 'lic_check_success':
24
  $this->setCanRequestOverride( true )->getToken();
25
  break;
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License\Lib\WpHashes;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
7
  use FernleafSystems\Wordpress\Services\Services;
8
  use FernleafSystems\Wordpress\Services\Utilities\Integrations\WpHashes\Token;
10
  class ApiTokenManager {
11
 
12
  use Modules\ModConsumer;
13
+ use ExecOnce;
14
 
15
  /**
16
  * @var bool
18
  private $bCanRequestOverride = false;
19
 
20
  protected function run() {
21
+ add_action( $this->getCon()->prefix( 'event' ), function ( $eventTag ) {
22
+ switch ( $eventTag ) {
23
  case 'lic_check_success':
24
  $this->setCanRequestOverride( true )->getToken();
25
  break;
src/lib/src/Modules/License/WpCli/License.php CHANGED
@@ -117,8 +117,7 @@ class License extends Base\WpCli\BaseWpCliCmd {
117
  * or you're premium and you haven't switched it off (parent).
118
  * @inheritDoc
119
  */
120
- protected function canRun() {
121
- return !$this->getCon()->isPremiumActive()
122
- || parent::canRun();
123
  }
124
  }
117
  * or you're premium and you haven't switched it off (parent).
118
  * @inheritDoc
119
  */
120
+ protected function canRun() :bool {
121
+ return !$this->getCon()->isPremiumActive() || parent::canRun();
 
122
  }
123
  }
src/lib/src/Modules/Lockdown/Lib/CleanRubbish.php CHANGED
@@ -2,33 +2,29 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown\Lib;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class CleanRubbish {
10
 
11
  use ModConsumer;
12
- use OneTimeExecute;
 
 
 
 
13
 
14
  protected function run() {
15
- if ( Services::WpGeneral()->isCron() && $this->getOptions()->isOpt( 'clean_wp_rubbish', 'Y' ) ) {
16
- add_action( $this->getCon()->prefix( 'daily_cron' ), function () {
17
- $FS = Services::WpFs();
18
- $allFiles = $FS->getAllFilesInDir( ABSPATH, false );
19
- foreach ( $allFiles as $file ) {
20
- if ( in_array( basename( $file ), $this->getFilesToClean() ) ) {
21
- $FS->deleteFile( $file );
22
- }
23
- }
24
- } );
25
  }
26
  }
27
 
28
- /**
29
- * @return string[]
30
- */
31
- private function getFilesToClean() {
32
  return [
33
  'wp-config-sample.php',
34
  'readme.html',
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown\Lib;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class CleanRubbish {
10
 
11
  use ModConsumer;
12
+ use ExecOnce;
13
+
14
+ protected function canRun() :bool {
15
+ return $this->getOptions()->isOpt( 'clean_wp_rubbish', 'Y' );
16
+ }
17
 
18
  protected function run() {
19
+ $FS = Services::WpFs();
20
+ foreach ( $FS->getAllFilesInDir( ABSPATH, false ) as $file ) {
21
+ if ( in_array( basename( $file ), $this->getFilesToClean() ) ) {
22
+ $FS->deleteFile( $file );
23
+ }
 
 
 
 
 
24
  }
25
  }
26
 
27
+ private function getFilesToClean() :array {
 
 
 
28
  return [
29
  'wp-config-sample.php',
30
  'readme.html',
src/lib/src/Modules/Lockdown/Processor.php CHANGED
@@ -26,18 +26,18 @@ class Processor extends BaseShield\Processor {
26
  remove_action( 'wp_head', 'wp_generator' );
27
  }
28
 
29
- if ( $opts->isOpt( 'clean_wp_rubbish', 'Y' ) ) {
30
- ( new Lib\CleanRubbish() )
31
- ->setMod( $this->getMod() )
32
- ->execute();
33
- }
34
-
35
  if ( $opts->isXmlrpcDisabled() ) {
36
  add_filter( 'xmlrpc_enabled', [ $this, 'disableXmlrpc' ], 1000, 0 );
37
  add_filter( 'xmlrpc_methods', [ $this, 'disableXmlrpc' ], 1000, 0 );
38
  }
39
  }
40
 
 
 
 
 
 
 
41
  private function blockFileEditing() {
42
  if ( !defined( 'DISALLOW_FILE_EDIT' ) ) {
43
  define( 'DISALLOW_FILE_EDIT', true );
26
  remove_action( 'wp_head', 'wp_generator' );
27
  }
28
 
 
 
 
 
 
 
29
  if ( $opts->isXmlrpcDisabled() ) {
30
  add_filter( 'xmlrpc_enabled', [ $this, 'disableXmlrpc' ], 1000, 0 );
31
  add_filter( 'xmlrpc_methods', [ $this, 'disableXmlrpc' ], 1000, 0 );
32
  }
33
  }
34
 
35
+ public function runDailyCron() {
36
+ ( new Lib\CleanRubbish() )
37
+ ->setMod( $this->getMod() )
38
+ ->execute();
39
+ }
40
+
41
  private function blockFileEditing() {
42
  if ( !defined( 'DISALLOW_FILE_EDIT' ) ) {
43
  define( 'DISALLOW_FILE_EDIT', true );
src/lib/src/Modules/LoginGuard/Insights/OverviewCards.php CHANGED
@@ -25,31 +25,31 @@ class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
25
  $cards[ 'mod' ] = $this->getModDisabledCard();
26
  }
27
  else {
28
- $bHasBotCheck = $opts->isEnabledGaspCheck() || $mod->isEnabledCaptcha();
29
 
30
- $bBotLogin = $bHasBotCheck && $opts->isProtectLogin();
31
- $bBotRegister = $bHasBotCheck && $opts->isProtectRegister();
32
- $bBotPassword = $bHasBotCheck && $opts->isProtectLostPassword();
33
  $cards[ 'bot_login' ] = [
34
  'name' => __( 'Brute Force Login', 'wp-simple-firewall' ),
35
- 'state' => $bBotLogin ? 1 : -1,
36
- 'summary' => $bBotLogin ?
37
  __( 'Login forms are protected against bot attacks', 'wp-simple-firewall' )
38
  : __( "Login forms aren't protected against brute force bot attacks", 'wp-simple-firewall' ),
39
  'href' => $mod->getUrl_DirectLinkToOption( 'bot_protection_locations' ),
40
  ];
41
  $cards[ 'bot_register' ] = [
42
  'name' => __( 'Bot User Register', 'wp-simple-firewall' ),
43
- 'state' => $bBotRegister ? 1 : -1,
44
- 'summary' => $bBotRegister ?
45
  __( 'Registration forms are protected against bot attacks', 'wp-simple-firewall' )
46
  : __( "Registration forms aren't protected against automated bots", 'wp-simple-firewall' ),
47
  'href' => $mod->getUrl_DirectLinkToOption( 'bot_protection_locations' ),
48
  ];
49
  $cards[ 'bot_password' ] = [
50
  'name' => __( 'Brute Force Lost Password', 'wp-simple-firewall' ),
51
- 'state' => $bBotPassword ? 1 : -1,
52
- 'summary' => $bBotPassword ?
53
  __( 'Lost Password forms are protected against bot attacks', 'wp-simple-firewall' )
54
  : __( "Lost Password forms aren't protected against automated bots", 'wp-simple-firewall' ),
55
  'href' => $mod->getUrl_DirectLinkToOption( 'bot_protection_locations' ),
25
  $cards[ 'mod' ] = $this->getModDisabledCard();
26
  }
27
  else {
28
+ $hasBotCheck = $opts->isEnabledAntiBot() || $opts->isEnabledGaspCheck() || $mod->isEnabledCaptcha();
29
 
30
+ $boLogin = $hasBotCheck && $opts->isProtectLogin();
31
+ $botReg = $hasBotCheck && $opts->isProtectRegister();
32
+ $botPassword = $hasBotCheck && $opts->isProtectLostPassword();
33
  $cards[ 'bot_login' ] = [
34
  'name' => __( 'Brute Force Login', 'wp-simple-firewall' ),
35
+ 'state' => $boLogin ? 1 : -1,
36
+ 'summary' => $boLogin ?
37
  __( 'Login forms are protected against bot attacks', 'wp-simple-firewall' )
38
  : __( "Login forms aren't protected against brute force bot attacks", 'wp-simple-firewall' ),
39
  'href' => $mod->getUrl_DirectLinkToOption( 'bot_protection_locations' ),
40
  ];
41
  $cards[ 'bot_register' ] = [
42
  'name' => __( 'Bot User Register', 'wp-simple-firewall' ),
43
+ 'state' => $botReg ? 1 : -1,
44
+ 'summary' => $botReg ?
45
  __( 'Registration forms are protected against bot attacks', 'wp-simple-firewall' )
46
  : __( "Registration forms aren't protected against automated bots", 'wp-simple-firewall' ),
47
  'href' => $mod->getUrl_DirectLinkToOption( 'bot_protection_locations' ),
48
  ];
49
  $cards[ 'bot_password' ] = [
50
  'name' => __( 'Brute Force Lost Password', 'wp-simple-firewall' ),
51
+ 'state' => $botPassword ? 1 : -1,
52
+ 'summary' => $botPassword ?
53
  __( 'Lost Password forms are protected against bot attacks', 'wp-simple-firewall' )
54
  : __( "Lost Password forms aren't protected against automated bots", 'wp-simple-firewall' ),
55
  'href' => $mod->getUrl_DirectLinkToOption( 'bot_protection_locations' ),
src/lib/src/Modules/LoginGuard/Lib/AntiBot/AntibotSetup.php CHANGED
@@ -2,6 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
@@ -11,49 +12,51 @@ use FernleafSystems\Wordpress\Services\Services;
11
  class AntibotSetup {
12
 
13
  use ModConsumer;
 
14
 
15
- public function __construct() {
16
- add_action( 'init', [ $this, 'onWpInit' ], -100 );
17
  }
18
 
19
- public function onWpInit() {
20
- if ( !Services::WpUsers()->isUserLoggedIn() ) {
21
- $this->run();
22
- }
23
- }
24
-
25
- private function run() {
26
  /** @var LoginGuard\ModCon $mod */
27
  $mod = $this->getMod();
28
  /** @var LoginGuard\Options $opts */
29
  $opts = $this->getOptions();
30
 
31
- $aProtectionProviders = [];
32
  if ( $opts->isEnabledCooldown() && $mod->canCacheDirWrite() ) {
33
- $aProtectionProviders[] = ( new AntiBot\ProtectionProviders\CoolDown() )
34
  ->setMod( $mod );
35
  }
36
 
37
- if ( $opts->isEnabledGaspCheck() ) {
38
- $aProtectionProviders[] = ( new AntiBot\ProtectionProviders\GaspJs() )
39
  ->setMod( $mod );
40
  }
 
41
 
42
- if ( $mod->isEnabledCaptcha() ) {
43
- $cfg = $mod->getCaptchaCfg();
44
- if ( $cfg->provider === CaptchaConfigVO::PROV_GOOGLE_RECAP2 ) {
45
- $aProtectionProviders[] = ( new AntiBot\ProtectionProviders\GoogleRecaptcha() )
46
  ->setMod( $mod );
47
  }
48
- elseif ( $cfg->provider === CaptchaConfigVO::PROV_HCAPTCHA ) {
49
- $aProtectionProviders[] = ( new AntiBot\ProtectionProviders\HCaptcha() )
50
- ->setMod( $mod );
 
 
 
 
 
 
 
 
51
  }
52
  }
53
 
54
- if ( !empty( $aProtectionProviders ) ) {
55
 
56
- AntiBot\FormProviders\WordPress::SetProviders( $aProtectionProviders );
57
  /** @var AntiBot\FormProviders\BaseFormProvider[] $aFormProviders */
58
  $aFormProviders = [
59
  new AntiBot\FormProviders\WordPress()
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
12
  class AntibotSetup {
13
 
14
  use ModConsumer;
15
+ use ExecOnce;
16
 
17
+ protected function canRun() :bool {
18
+ return !Services::WpUsers()->isUserLoggedIn();
19
  }
20
 
21
+ protected function run() {
 
 
 
 
 
 
22
  /** @var LoginGuard\ModCon $mod */
23
  $mod = $this->getMod();
24
  /** @var LoginGuard\Options $opts */
25
  $opts = $this->getOptions();
26
 
27
+ $providers = [];
28
  if ( $opts->isEnabledCooldown() && $mod->canCacheDirWrite() ) {
29
+ $providers[] = ( new AntiBot\ProtectionProviders\CoolDown() )
30
  ->setMod( $mod );
31
  }
32
 
33
+ if ( $opts->isEnabledAntiBot() ) {
34
+ $providers[] = ( new AntiBot\ProtectionProviders\AntiBot() )
35
  ->setMod( $mod );
36
  }
37
+ else {
38
 
39
+ if ( $opts->isEnabledGaspCheck() ) {
40
+ $providers[] = ( new AntiBot\ProtectionProviders\GaspJs() )
 
 
41
  ->setMod( $mod );
42
  }
43
+
44
+ if ( $mod->isEnabledCaptcha() ) {
45
+ $cfg = $mod->getCaptchaCfg();
46
+ if ( $cfg->provider === CaptchaConfigVO::PROV_GOOGLE_RECAP2 ) {
47
+ $providers[] = ( new AntiBot\ProtectionProviders\GoogleRecaptcha() )
48
+ ->setMod( $mod );
49
+ }
50
+ elseif ( $cfg->provider === CaptchaConfigVO::PROV_HCAPTCHA ) {
51
+ $providers[] = ( new AntiBot\ProtectionProviders\HCaptcha() )
52
+ ->setMod( $mod );
53
+ }
54
  }
55
  }
56
 
57
+ if ( !empty( $providers ) ) {
58
 
59
+ AntiBot\FormProviders\WordPress::SetProviders( $providers );
60
  /** @var AntiBot\FormProviders\BaseFormProvider[] $aFormProviders */
61
  $aFormProviders = [
62
  new AntiBot\FormProviders\WordPress()
src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/BaseFormProvider.php CHANGED
@@ -79,27 +79,22 @@ abstract class BaseFormProvider {
79
  * @return string
80
  */
81
  public function formInsertsAppend( $sToAppend ) {
82
- return $sToAppend.$this->formInsertsBuild();
83
  }
84
 
85
- /**
86
- * @return string
87
- */
88
- public function formInsertsBuild() {
89
  $aInserts = [];
90
  if ( is_array( self::$aProtectionProviders ) ) {
91
  foreach ( self::$aProtectionProviders as $oProvider ) {
92
  $aInserts[] = $oProvider->buildFormInsert( $this );
 
93
  }
94
  }
95
  return implode( "\n", $aInserts );
96
  }
97
 
98
- /**
99
- * @return void
100
- */
101
- public function formInsertsPrint() {
102
- echo $this->formInsertsBuild();
103
  }
104
 
105
  /**
79
  * @return string
80
  */
81
  public function formInsertsAppend( $sToAppend ) {
82
+ return $sToAppend.$this->buildFormInsert();
83
  }
84
 
85
+ public function buildFormInsert() :string {
 
 
 
86
  $aInserts = [];
87
  if ( is_array( self::$aProtectionProviders ) ) {
88
  foreach ( self::$aProtectionProviders as $oProvider ) {
89
  $aInserts[] = $oProvider->buildFormInsert( $this );
90
+ $oProvider->setAsInsertBuilt();
91
  }
92
  }
93
  return implode( "\n", $aInserts );
94
  }
95
 
96
+ public function printFormInsert() {
97
+ echo $this->buildFormInsert();
 
 
 
98
  }
99
 
100
  /**
src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/BuddyPress.php CHANGED
@@ -5,7 +5,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot
5
  class BuddyPress extends BaseFormProvider {
6
 
7
  protected function register() {
8
- add_action( 'bp_before_registration_submit_buttons', [ $this, 'formInsertsPrint' ], 10 );
9
  add_action( 'bp_signup_validate', [ $this, 'checkRegister' ], 10 );
10
  }
11
 
5
  class BuddyPress extends BaseFormProvider {
6
 
7
  protected function register() {
8
+ add_action( 'bp_before_registration_submit_buttons', [ $this, 'printFormInsert' ], 10 );
9
  add_action( 'bp_signup_validate', [ $this, 'checkRegister' ], 10 );
10
  }
11
 
src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/EasyDigitalDownloads.php CHANGED
@@ -5,11 +5,11 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot
5
  class EasyDigitalDownloads extends BaseFormProvider {
6
 
7
  protected function login() {
8
- add_action( 'edd_login_fields_after', [ $this, 'formInsertsPrint' ], 10 );
9
  }
10
 
11
  protected function register() {
12
- add_action( 'edd_register_form_fields_before_submit', [ $this, 'formInsertsPrint' ], 10 );
13
  add_action( 'edd_process_register_form', [ $this, 'checkRegister' ], 10 );
14
  }
15
 
5
  class EasyDigitalDownloads extends BaseFormProvider {
6
 
7
  protected function login() {
8
+ add_action( 'edd_login_fields_after', [ $this, 'printFormInsert' ], 10 );
9
  }
10
 
11
  protected function register() {
12
+ add_action( 'edd_register_form_fields_before_submit', [ $this, 'printFormInsert' ], 10 );
13
  add_action( 'edd_process_register_form', [ $this, 'checkRegister' ], 10 );
14
  }
15
 
src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/LearnPress.php CHANGED
@@ -10,13 +10,13 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot
10
  class LearnPress extends BaseFormProvider {
11
 
12
  protected function login() {
13
- add_action( 'learn-press/after-form-login-fields', [ $this, 'formInsertsPrint' ], 100 );
14
- add_action( 'learn-press/before-checkout-form-login-button', [ $this, 'formInsertsPrint' ], 100 );
15
  add_filter( 'learn-press/login-validate-field', [ $this, 'checkLogin' ], 100 );
16
  }
17
 
18
  protected function register() {
19
- add_action( 'learn-press/after-form-register-fields', [ $this, 'formInsertsPrint' ], 100 );
20
  add_filter( 'learn-press/register-validate-field', [ $this, 'checkRegister' ], 100, 1 );
21
  }
22
 
10
  class LearnPress extends BaseFormProvider {
11
 
12
  protected function login() {
13
+ add_action( 'learn-press/after-form-login-fields', [ $this, 'printFormInsert' ], 100 );
14
+ add_action( 'learn-press/before-checkout-form-login-button', [ $this, 'printFormInsert' ], 100 );
15
  add_filter( 'learn-press/login-validate-field', [ $this, 'checkLogin' ], 100 );
16
  }
17
 
18
  protected function register() {
19
+ add_action( 'learn-press/after-form-register-fields', [ $this, 'printFormInsert' ], 100 );
20
  add_filter( 'learn-press/register-validate-field', [ $this, 'checkRegister' ], 100, 1 );
21
  }
22
 
src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/MemberPress.php CHANGED
@@ -5,23 +5,23 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot
5
  class MemberPress extends BaseFormProvider {
6
 
7
  protected function login() {
8
- add_action( 'mepr-login-form-before-submit', [ $this, 'formInsertsPrint' ], 100 );
9
  add_filter( 'mepr-validate-login', [ $this, 'checkLogin' ], 100 );
10
  /**
11
  * We have to add a checkbox to the password reset form because MemberPress attempts to
12
  * login the given user upon success of password update. Without this checkbox being present
13
  * the login will fail (though the password update will not).
14
  */
15
- add_action( 'mepr-reset-password-after-password-fields', [ $this, 'formInsertsPrint' ], 100 );
16
  }
17
 
18
  protected function register() {
19
- add_action( 'mepr-checkout-before-submit', [ $this, 'formInsertsPrint' ], 10 );
20
  add_filter( 'mepr-validate-signup', [ $this, 'checkRegister' ], 10, 2 );
21
  }
22
 
23
  protected function lostpassword() {
24
- add_action( 'mepr-forgot-password-form', [ $this, 'formInsertsPrint' ], 100 );
25
  add_filter( 'mepr-validate-forgot-password', [ $this, 'checkLostPassword' ], 100 );
26
  }
27
 
5
  class MemberPress extends BaseFormProvider {
6
 
7
  protected function login() {
8
+ add_action( 'mepr-login-form-before-submit', [ $this, 'printFormInsert' ], 100 );
9
  add_filter( 'mepr-validate-login', [ $this, 'checkLogin' ], 100 );
10
  /**
11
  * We have to add a checkbox to the password reset form because MemberPress attempts to
12
  * login the given user upon success of password update. Without this checkbox being present
13
  * the login will fail (though the password update will not).
14
  */
15
+ add_action( 'mepr-reset-password-after-password-fields', [ $this, 'printFormInsert' ], 100 );
16
  }
17
 
18
  protected function register() {
19
+ add_action( 'mepr-checkout-before-submit', [ $this, 'printFormInsert' ], 10 );
20
  add_filter( 'mepr-validate-signup', [ $this, 'checkRegister' ], 10, 2 );
21
  }
22
 
23
  protected function lostpassword() {
24
+ add_action( 'mepr-forgot-password-form', [ $this, 'printFormInsert' ], 100 );
25
  add_filter( 'mepr-validate-forgot-password', [ $this, 'checkLostPassword' ], 100 );
26
  }
27
 
src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/PaidMemberSubscriptions.php CHANGED
@@ -10,7 +10,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot
10
  class PaidMemberSubscriptions extends BaseFormProvider {
11
 
12
  protected function register() {
13
- add_action( 'pms_register_form_after_fields', [ $this, 'formInsertsPrint' ], 100 );
14
  add_filter( 'pms_register_form_validation', [ $this, 'checkRegister' ], 100 );
15
  }
16
 
10
  class PaidMemberSubscriptions extends BaseFormProvider {
11
 
12
  protected function register() {
13
+ add_action( 'pms_register_form_after_fields', [ $this, 'printFormInsert' ], 100 );
14
  add_filter( 'pms_register_form_validation', [ $this, 'checkRegister' ], 100 );
15
  }
16
 
src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/ProfileBuilder.php CHANGED
@@ -10,7 +10,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot
10
  class ProfileBuilder extends BaseFormProvider {
11
 
12
  protected function register() {
13
- add_action( 'wppb_form_before_submit_button', [ $this, 'formInsertsPrint' ], 100 );
14
  add_filter( 'wppb_output_field_errors_filter', [ $this, 'checkRegister' ], 100 );
15
  }
16
 
10
  class ProfileBuilder extends BaseFormProvider {
11
 
12
  protected function register() {
13
+ add_action( 'wppb_form_before_submit_button', [ $this, 'printFormInsert' ], 100 );
14
  add_filter( 'wppb_output_field_errors_filter', [ $this, 'checkRegister' ], 100 );
15
  }
16
 
src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/UltimateMember.php CHANGED
@@ -10,17 +10,17 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot
10
  class UltimateMember extends BaseFormProvider {
11
 
12
  protected function login() {
13
- add_action( 'um_after_login_fields', [ $this, 'formInsertsPrint' ], 100 );
14
  add_action( 'um_submit_form_login', [ $this, 'checkLogin' ], 100 );
15
  }
16
 
17
  protected function register() {
18
- add_action( 'um_after_register_fields', [ $this, 'formInsertsPrint' ], 100 );
19
  add_action( 'um_submit_form_register', [ $this, 'checkRegister' ], 5, 0 );
20
  }
21
 
22
  protected function lostpassword() {
23
- add_action( 'um_after_password_reset_fields', [ $this, 'formInsertsPrint' ], 100 );
24
  add_action( 'um_submit_form_password_reset', [ $this, 'checkLostPassword' ], 5, 0 );
25
  }
26
 
10
  class UltimateMember extends BaseFormProvider {
11
 
12
  protected function login() {
13
+ add_action( 'um_after_login_fields', [ $this, 'printFormInsert' ], 100 );
14
  add_action( 'um_submit_form_login', [ $this, 'checkLogin' ], 100 );
15
  }
16
 
17
  protected function register() {
18
+ add_action( 'um_after_register_fields', [ $this, 'printFormInsert' ], 100 );
19
  add_action( 'um_submit_form_register', [ $this, 'checkRegister' ], 5, 0 );
20
  }
21
 
22
  protected function lostpassword() {
23
+ add_action( 'um_after_password_reset_fields', [ $this, 'printFormInsert' ], 100 );
24
  add_action( 'um_submit_form_password_reset', [ $this, 'checkLostPassword' ], 5, 0 );
25
  }
26
 
src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/UserRegistration.php CHANGED
@@ -11,16 +11,16 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot
11
  class UserRegistration extends BaseFormProvider {
12
 
13
  protected function register() {
14
- add_action( 'user_registration_after_form_fields', [ $this, 'formInsertsPrint' ], 100 );
15
  add_action( 'user_registration_response_array', [ $this, 'checkRegister' ], 5, 3 );
16
  }
17
 
18
  /**
19
  * @return void
20
  */
21
- public function formInsertsPrint() {
22
  echo '<div class="ur-form-grid">';
23
- echo preg_replace( '#class="(.*)"#i', 'class="\\1 ur-frontend-field"', $this->formInsertsBuild() );
24
  echo '</div>';
25
  }
26
 
11
  class UserRegistration extends BaseFormProvider {
12
 
13
  protected function register() {
14
+ add_action( 'user_registration_after_form_fields', [ $this, 'printFormInsert' ], 100 );
15
  add_action( 'user_registration_response_array', [ $this, 'checkRegister' ], 5, 3 );
16
  }
17
 
18
  /**
19
  * @return void
20
  */
21
+ public function printFormInsert() {
22
  echo '<div class="ur-form-grid">';
23
+ echo preg_replace( '#class="(.*)"#i', 'class="\\1 ur-frontend-field"', $this->buildFormInsert() );
24
  echo '</div>';
25
  }
26
 
src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WooCommerce.php CHANGED
@@ -28,7 +28,7 @@ class WooCommerce extends BaseFormProvider {
28
  }
29
 
30
  protected function lostpassword() {
31
- add_action( 'woocommerce_lostpassword_form', [ $this, 'formInsertsPrint' ] );
32
  }
33
 
34
  protected function woocheckout() {
@@ -52,7 +52,7 @@ class WooCommerce extends BaseFormProvider {
52
  public function formInsertsPrint_WooLogin() {
53
  /** @var LoginGuard\ModCon $mod */
54
  $mod = $this->getMod();
55
- $sInserts = $this->formInsertsBuild();
56
  if ( $mod->getCaptchaCfg()->invisible ) {
57
  $sInserts .= '<input type="hidden" name="login" value="Log in" />';
58
  }
@@ -65,7 +65,7 @@ class WooCommerce extends BaseFormProvider {
65
  public function formInsertsPrint_WooRegister() {
66
  /** @var LoginGuard\ModCon $mod */
67
  $mod = $this->getMod();
68
- $sInserts = $this->formInsertsBuild();
69
  if ( $mod->getCaptchaCfg()->invisible ) {
70
  $sInserts .= '<input type="hidden" name="register" value="Register" />';
71
  }
@@ -79,7 +79,7 @@ class WooCommerce extends BaseFormProvider {
79
  */
80
  public function formInsertsPrintCheckout( $oCheckout ) {
81
  if ( $oCheckout instanceof \WC_Checkout && $oCheckout->is_registration_enabled() ) {
82
- $this->formInsertsPrint();
83
  }
84
  }
85
 
28
  }
29
 
30
  protected function lostpassword() {
31
+ add_action( 'woocommerce_lostpassword_form', [ $this, 'printFormInsert' ] );
32
  }
33
 
34
  protected function woocheckout() {
52
  public function formInsertsPrint_WooLogin() {
53
  /** @var LoginGuard\ModCon $mod */
54
  $mod = $this->getMod();
55
+ $sInserts = $this->buildFormInsert();
56
  if ( $mod->getCaptchaCfg()->invisible ) {
57
  $sInserts .= '<input type="hidden" name="login" value="Log in" />';
58
  }
65
  public function formInsertsPrint_WooRegister() {
66
  /** @var LoginGuard\ModCon $mod */
67
  $mod = $this->getMod();
68
+ $sInserts = $this->buildFormInsert();
69
  if ( $mod->getCaptchaCfg()->invisible ) {
70
  $sInserts .= '<input type="hidden" name="register" value="Register" />';
71
  }
79
  */
80
  public function formInsertsPrintCheckout( $oCheckout ) {
81
  if ( $oCheckout instanceof \WC_Checkout && $oCheckout->is_registration_enabled() ) {
82
+ $this->printFormInsert();
83
  }
84
  }
85
 
src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WordPress.php CHANGED
@@ -7,21 +7,19 @@ use FernleafSystems\Wordpress\Services\Services;
7
  class WordPress extends BaseFormProvider {
8
 
9
  protected function login() {
10
- add_action( 'login_form', [ $this, 'formInsertsPrint' ], 100 );
11
  add_filter( 'login_form_middle', [ $this, 'formInsertsAppend' ], 100 );
12
-
13
  // We give it a priority of 10 so that we can jump in before WordPress does its own validation.
14
  add_filter( 'authenticate', [ $this, 'checkLogin' ], 10, 2 );
15
  }
16
 
17
  protected function register() {
18
- add_action( 'register_form', [ $this, 'formInsertsPrint' ] );
19
-
20
  add_filter( 'registration_errors', [ $this, 'checkRegister' ], 10, 2 );
21
  }
22
 
23
  protected function lostpassword() {
24
- add_action( 'lostpassword_form', [ $this, 'formInsertsPrint' ] );
25
  add_action( 'lostpassword_post', [ $this, 'checkLostPassword' ], 10, 1 );
26
  }
27
 
@@ -48,37 +46,37 @@ class WordPress extends BaseFormProvider {
48
  }
49
 
50
  /**
51
- * @param \WP_Error $oWpError
52
  * @return \WP_Error
53
  */
54
- public function checkLostPassword( $oWpError ) {
55
  try {
56
  $this->setUserToAudit( sanitize_user( Services::Request()->post( 'user_login', '' ) ) )
57
  ->setActionToAudit( 'reset-password' )
58
  ->checkProviders();
59
  }
60
  catch ( \Exception $e ) {
61
- $oWpError = $this->giveMeWpError( $oWpError );
62
- $oWpError->add( $this->getCon()->prefix( rand() ), $e->getMessage() );
63
  }
64
- return $oWpError;
65
  }
66
 
67
  /**
68
- * @param \WP_Error $oWpError
69
  * @param string $sUsername
70
  * @return \WP_Error
71
  */
72
- public function checkRegister( $oWpError, $sUsername ) {
73
  try {
74
  $this->setUserToAudit( $sUsername )
75
  ->setActionToAudit( 'register' )
76
  ->checkProviders();
77
  }
78
  catch ( \Exception $e ) {
79
- $oWpError = $this->giveMeWpError( $oWpError );
80
- $oWpError->add( $this->getCon()->prefix( rand() ), $e->getMessage() );
81
  }
82
- return $oWpError;
83
  }
84
  }
7
  class WordPress extends BaseFormProvider {
8
 
9
  protected function login() {
10
+ add_action( 'login_form', [ $this, 'printFormInsert' ], 100 );
11
  add_filter( 'login_form_middle', [ $this, 'formInsertsAppend' ], 100 );
 
12
  // We give it a priority of 10 so that we can jump in before WordPress does its own validation.
13
  add_filter( 'authenticate', [ $this, 'checkLogin' ], 10, 2 );
14
  }
15
 
16
  protected function register() {
17
+ add_action( 'register_form', [ $this, 'printFormInsert' ] );
 
18
  add_filter( 'registration_errors', [ $this, 'checkRegister' ], 10, 2 );
19
  }
20
 
21
  protected function lostpassword() {
22
+ add_action( 'lostpassword_form', [ $this, 'printFormInsert' ] );
23
  add_action( 'lostpassword_post', [ $this, 'checkLostPassword' ], 10, 1 );
24
  }
25
 
46
  }
47
 
48
  /**
49
+ * @param \WP_Error $wpError
50
  * @return \WP_Error
51
  */
52
+ public function checkLostPassword( $wpError ) {
53
  try {
54
  $this->setUserToAudit( sanitize_user( Services::Request()->post( 'user_login', '' ) ) )
55
  ->setActionToAudit( 'reset-password' )
56
  ->checkProviders();
57
  }
58
  catch ( \Exception $e ) {
59
+ $wpError = $this->giveMeWpError( $wpError );
60
+ $wpError->add( $this->getCon()->prefix( rand() ), $e->getMessage() );
61
  }
62
+ return $wpError;
63
  }
64
 
65
  /**
66
+ * @param \WP_Error $wpError
67
  * @param string $sUsername
68
  * @return \WP_Error
69
  */
70
+ public function checkRegister( $wpError, $sUsername ) {
71
  try {
72
  $this->setUserToAudit( $sUsername )
73
  ->setActionToAudit( 'register' )
74
  ->checkProviders();
75
  }
76
  catch ( \Exception $e ) {
77
+ $wpError = $this->giveMeWpError( $wpError );
78
+ $wpError->add( $this->getCon()->prefix( rand() ), $e->getMessage() );
79
  }
80
+ return $wpError;
81
  }
82
  }
src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/AntiBot.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot\ProtectionProviders;
4
+
5
+ use FernleafSystems\Wordpress\Services\Services;
6
+
7
+ class AntiBot extends BaseProtectionProvider {
8
+
9
+ /**
10
+ * @inheritDoc
11
+ */
12
+ public function performCheck( $oForm ) {
13
+ if ( $this->isFactorTested() ) {
14
+ return;
15
+ }
16
+ $isBot = $this->getCon()
17
+ ->getModule_IPs()
18
+ ->getBotSignalsController()
19
+ ->isBot( Services::IP()->getRequestIp() );
20
+ if ( $isBot ) {
21
+ $this->processFailure();
22
+ throw new \Exception( __( 'Failed AntiBot Test', 'wp-simple-firewall' ) );
23
+ }
24
+ }
25
+
26
+ public function buildFormInsert( $oFormProvider ) {
27
+ return '';
28
+ }
29
+ }
src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/BaseProtectionProvider.php CHANGED
@@ -9,23 +9,25 @@ abstract class BaseProtectionProvider {
9
 
10
  use ModConsumer;
11
 
 
 
 
 
12
  /**
13
- * @var bool
14
  */
15
- private $bFactorTested;
16
 
17
  public function __construct() {
18
  add_action( 'wp_loaded', [ $this, 'setup' ], 0 ); // 0 to ensure WPS Hide Login doesn't fire before us.
 
19
  }
20
 
21
  public function setup() {
22
  }
23
 
24
- /**
25
- * @return bool
26
- */
27
- public function isFactorTested() {
28
- return (bool)$this->bFactorTested;
29
  }
30
 
31
  /**
@@ -34,6 +36,11 @@ abstract class BaseProtectionProvider {
34
  */
35
  abstract public function buildFormInsert( $oFormProvider );
36
 
 
 
 
 
 
37
  /**
38
  * @param LoginGuard\Lib\AntiBot\FormProviders\BaseFormProvider $oForm
39
  * @throws \Exception
@@ -41,11 +48,11 @@ abstract class BaseProtectionProvider {
41
  abstract public function performCheck( $oForm );
42
 
43
  /**
44
- * @param bool $bFactorTested
45
  * @return $this
46
  */
47
- public function setFactorTested( $bFactorTested ) {
48
- $this->bFactorTested = $bFactorTested;
49
  return $this;
50
  }
51
 
@@ -58,4 +65,16 @@ abstract class BaseProtectionProvider {
58
  $this->getCon()->fireEvent( 'login_block' );
59
  return $this;
60
  }
 
 
 
 
 
 
 
 
 
 
 
 
61
  }
9
 
10
  use ModConsumer;
11
 
12
+ private $factorTested = false;
13
+
14
+ protected $factorBuilt = false;
15
+
16
  /**
17
+ * @var string[]
18
  */
19
+ protected $enqueueHandles = [];
20
 
21
  public function __construct() {
22
  add_action( 'wp_loaded', [ $this, 'setup' ], 0 ); // 0 to ensure WPS Hide Login doesn't fire before us.
23
+ add_action( 'wp_footer', [ $this, 'maybeDequeueScript' ] );
24
  }
25
 
26
  public function setup() {
27
  }
28
 
29
+ public function isFactorTested() :bool {
30
+ return $this->factorTested;
 
 
 
31
  }
32
 
33
  /**
36
  */
37
  abstract public function buildFormInsert( $oFormProvider );
38
 
39
+ public function setAsInsertBuilt() :self {
40
+ $this->factorBuilt = true;
41
+ return $this;
42
+ }
43
+
44
  /**
45
  * @param LoginGuard\Lib\AntiBot\FormProviders\BaseFormProvider $oForm
46
  * @throws \Exception
48
  abstract public function performCheck( $oForm );
49
 
50
  /**
51
+ * @param bool $tested
52
  * @return $this
53
  */
54
+ public function setFactorTested( bool $tested ) {
55
+ $this->factorTested = $tested;
56
  return $this;
57
  }
58
 
65
  $this->getCon()->fireEvent( 'login_block' );
66
  return $this;
67
  }
68
+
69
+ public function maybeDequeueScript() {
70
+ if ( !$this->isFactorJsRequired() ) {
71
+ foreach ( $this->enqueueHandles as $handle ) {
72
+ wp_dequeue_script( $handle );
73
+ }
74
+ }
75
+ }
76
+
77
+ protected function isFactorJsRequired() :bool {
78
+ return $this->factorBuilt;
79
+ }
80
  }
src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/GaspJs.php CHANGED
@@ -2,6 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot\ProtectionProviders;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
@@ -9,11 +10,51 @@ class GaspJs extends BaseProtectionProvider {
9
 
10
  public function setup() {
11
  if ( Services::Request()->query( 'wp_service_worker', 0 ) != 1 ) {
12
- add_action( 'wp_enqueue_scripts', [ $this, 'onWpEnqueueJs' ] );
13
- add_action( 'login_enqueue_scripts', [ $this, 'onWpEnqueueJs' ] );
14
  }
15
  }
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  /**
18
  * @inheritDoc
19
  */
@@ -24,88 +65,51 @@ class GaspJs extends BaseProtectionProvider {
24
 
25
  /** @var LoginGuard\ModCon $mod */
26
  $mod = $this->getMod();
 
 
27
  $this->setFactorTested( true );
28
 
29
- $req = Services::Request();
30
- $sGaspCheckBox = $req->post( $mod->getGaspKey() );
31
- $sHoney = $req->post( 'icwp_wpsf_login_email' );
32
 
33
- $sUsername = $oForm->getUserToAudit();
34
- $sActionAttempted = $oForm->getActionToAudit();
35
 
36
- $bValid = false;
37
- $sError = '';
38
- if ( empty( $sGaspCheckBox ) ) {
39
  $this->getCon()->fireEvent(
40
  'botbox_fail',
41
  [
42
  'audit' => [
43
- 'user_login' => $sUsername,
44
- 'action' => $sActionAttempted,
45
  ]
46
  ]
47
  );
48
- $sError = __( "Please check that box to say you're human, and not a bot.", 'wp-simple-firewall' );
49
  }
50
- elseif ( !empty( $sHoney ) ) {
51
  $this->getCon()->fireEvent(
52
  'honeypot_fail',
53
  [
54
  'audit' => [
55
- 'user_login' => $sUsername,
56
- 'action' => $sActionAttempted,
57
  ]
58
  ]
59
  );
60
- $sError = __( 'You appear to be a bot.', 'wp-simple-firewall' );
61
  }
62
  else {
63
- $bValid = true;
64
  }
65
 
66
- if ( !$bValid ) {
67
  $this->processFailure();
68
- throw new \Exception( $sError );
69
  }
70
  }
71
 
72
- public function onWpEnqueueJs() {
73
- $con = $this->getCon();
74
- /** @var LoginGuard\ModCon $mod */
75
- $mod = $this->getMod();
76
- /** @var LoginGuard\Options $opts */
77
- $opts = $this->getOptions();
78
-
79
- $asset = 'shield-antibot';
80
- $uniq = $con->prefix( $asset );
81
- wp_register_script(
82
- $uniq,
83
- $con->getPluginUrl_Js( $asset ),
84
- [ 'jquery' ],
85
- $con->getVersion()
86
- );
87
- wp_enqueue_script( $uniq );
88
-
89
- wp_localize_script(
90
- $uniq,
91
- 'icwp_wpsf_vars_lpantibot',
92
- [
93
- 'form_selectors' => implode( ',', $opts->getAntiBotFormSelectors() ),
94
- 'uniq' => preg_replace( '#[^a-zA-Z0-9]#', '', apply_filters( 'icwp_shield_lp_gasp_uniqid', uniqid() ) ),
95
- 'cbname' => $mod->getGaspKey(),
96
- 'strings' => [
97
- 'label' => $mod->getTextImAHuman(),
98
- 'alert' => $mod->getTextPleaseCheckBox(),
99
- 'loading' => __( 'Loading', 'wp-simple-firewall' )
100
- ],
101
- 'flags' => [
102
- 'gasp' => $opts->isEnabledGaspCheck(),
103
- 'captcha' => $mod->isEnabledCaptcha(),
104
- ]
105
- ]
106
- );
107
- }
108
-
109
  /**
110
  * @inheritDoc
111
  */
@@ -119,4 +123,10 @@ class GaspJs extends BaseProtectionProvider {
119
  ]
120
  );
121
  }
 
 
 
 
 
 
122
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot\ProtectionProviders;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Controller\Assets\Enqueue;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
10
 
11
  public function setup() {
12
  if ( Services::Request()->query( 'wp_service_worker', 0 ) != 1 ) {
13
+ add_action( 'wp', [ $this, 'enqueueJS' ] );
14
+ add_action( 'login_init', [ $this, 'enqueueJS' ] );
15
  }
16
  }
17
 
18
+ public function enqueueJS() {
19
+ add_filter( 'shield/custom_enqueues', function ( array $enqueues ) {
20
+ $enqueues[ Enqueue::JS ][] = 'shield/loginbot';
21
+
22
+ add_filter( 'shield/custom_localisations', function ( array $localz ) {
23
+ /** @var LoginGuard\ModCon $mod */
24
+ $mod = $this->getMod();
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(),
42
+ 'alert' => $mod->getTextPleaseCheckBox(),
43
+ 'loading' => __( 'Loading', 'wp-simple-firewall' )
44
+ ],
45
+ 'flags' => [
46
+ 'gasp' => $opts->isEnabledGaspCheck(),
47
+ 'captcha' => $mod->isEnabledCaptcha(),
48
+ ]
49
+ ]
50
+ ];
51
+ return $localz;
52
+ } );
53
+
54
+ return $enqueues;
55
+ } );
56
+ }
57
+
58
  /**
59
  * @inheritDoc
60
  */
65
 
66
  /** @var LoginGuard\ModCon $mod */
67
  $mod = $this->getMod();
68
+ $req = Services::Request();
69
+
70
  $this->setFactorTested( true );
71
 
72
+ $gasp = $req->post( $mod->getGaspKey() );
 
 
73
 
74
+ $username = $oForm->getUserToAudit();
75
+ $action = $oForm->getActionToAudit();
76
 
77
+ $valid = false;
78
+ $errorMsg = '';
79
+ if ( empty( $gasp ) ) {
80
  $this->getCon()->fireEvent(
81
  'botbox_fail',
82
  [
83
  'audit' => [
84
+ 'user_login' => $username,
85
+ 'action' => $action,
86
  ]
87
  ]
88
  );
89
+ $errorMsg = __( "Please check that box to say you're human, and not a bot.", 'wp-simple-firewall' );
90
  }
91
+ elseif ( !empty( $req->post( 'icwp_wpsf_login_email' ) ) ) {
92
  $this->getCon()->fireEvent(
93
  'honeypot_fail',
94
  [
95
  'audit' => [
96
+ 'user_login' => $username,
97
+ 'action' => $action,
98
  ]
99
  ]
100
  );
101
+ $errorMsg = __( 'You appear to be a bot.', 'wp-simple-firewall' );
102
  }
103
  else {
104
+ $valid = true;
105
  }
106
 
107
+ if ( !$valid ) {
108
  $this->processFailure();
109
+ throw new \Exception( $errorMsg );
110
  }
111
  }
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  /**
114
  * @inheritDoc
115
  */
123
  ]
124
  );
125
  }
126
+
127
+ protected function isFactorJsRequired() :bool {
128
+ /** @var LoginGuard\Options $opts */
129
+ $opts = $this->getOptions();
130
+ return parent::isFactorJsRequired() || !empty( $opts->getAntiBotFormSelectors() );
131
+ }
132
  }
src/lib/src/Modules/LoginGuard/Lib/CooldownFlagFile.php CHANGED
@@ -5,10 +5,6 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib;
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
- /**
9
- * Class CooldownFlagFile
10
- * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib
11
- */
12
  class CooldownFlagFile {
13
 
14
  use Modules\ModConsumer;
@@ -33,16 +29,16 @@ class CooldownFlagFile {
33
  * @return string
34
  */
35
  public function getFlagFilePath() {
36
- return path_join( $this->getCon()->getPluginCachePath(), 'mode.login_throttled' );
37
  }
38
 
39
  /**
40
  * @return int
41
  */
42
  public function getSecondsSinceLastLogin() {
43
- $oFS = Services::WpFs();
44
- $sFile = $this->getFlagFilePath();
45
- $nLastLogin = $oFS->exists( $sFile ) ? $oFS->getModifiedTime( $sFile ) : 0;
46
  return ( Services::Request()->ts() - $nLastLogin );
47
  }
48
 
@@ -50,10 +46,10 @@ class CooldownFlagFile {
50
  * @return $this
51
  */
52
  public function updateCooldownFlag() {
53
- $oFS = Services::WpFs();
54
- $sFile = $this->getFlagFilePath();
55
- $oFS->deleteFile( $sFile );
56
- $oFS->touch( $sFile, Services::Request()->ts() );
57
  return $this;
58
  }
59
  }
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
 
 
 
 
8
  class CooldownFlagFile {
9
 
10
  use Modules\ModConsumer;
29
  * @return string
30
  */
31
  public function getFlagFilePath() {
32
+ return $this->getCon()->getPluginCachePath( 'mode.login_throttled' );
33
  }
34
 
35
  /**
36
  * @return int
37
  */
38
  public function getSecondsSinceLastLogin() {
39
+ $FS = Services::WpFs();
40
+ $file = $this->getFlagFilePath();
41
+ $nLastLogin = $FS->exists( $file ) ? $FS->getModifiedTime( $file ) : 0;
42
  return ( Services::Request()->ts() - $nLastLogin );
43
  }
44
 
46
  * @return $this
47
  */
48
  public function updateCooldownFlag() {
49
+ $FS = Services::WpFs();
50
+ $file = $this->getFlagFilePath();
51
+ $FS->deleteFile( $file );
52
+ $FS->touch( $file, Services::Request()->ts() );
53
  return $this;
54
  }
55
  }
src/lib/src/Modules/LoginGuard/Lib/Rename/RenameLogin.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\Rename;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Options;
@@ -11,13 +11,9 @@ use FernleafSystems\Wordpress\Services\Services;
11
  class RenameLogin {
12
 
13
  use Modules\ModConsumer;
14
- use OneTimeExecute;
15
 
16
- protected function run() {
17
- add_action( 'init', [ $this, 'onWpInit' ], 9 );
18
- }
19
-
20
- protected function canRun() {
21
  /** @var Options $opts */
22
  $opts = $this->getOptions();
23
  return !Services::IP()->isLoopback()
@@ -25,6 +21,10 @@ class RenameLogin {
25
  && !$this->hasPluginConflict() && !$this->hasUnsupportedConfiguration();
26
  }
27
 
 
 
 
 
28
  public function onWpInit() {
29
  /** @var LoginGuard\ModCon $mod */
30
  $mod = $this->getMod();
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\Rename;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Options;
11
  class RenameLogin {
12
 
13
  use Modules\ModConsumer;
14
+ use ExecOnce;
15
 
16
+ protected function canRun() :bool {
 
 
 
 
17
  /** @var Options $opts */
18
  $opts = $this->getOptions();
19
  return !Services::IP()->isLoopback()
21
  && !$this->hasPluginConflict() && !$this->hasUnsupportedConfiguration();
22
  }
23
 
24
+ protected function run() {
25
+ add_action( 'init', [ $this, 'onWpInit' ], 9 );
26
+ }
27
+
28
  public function onWpInit() {
29
  /** @var LoginGuard\ModCon $mod */
30
  $mod = $this->getMod();
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/LoginIntentPage.php CHANGED
@@ -96,7 +96,7 @@ class LoginIntentPage {
96
  ],
97
  'flags' => [
98
  'can_skip_mfa' => $opts->isMfaSkip(),
99
- 'show_branded_links' => !$con->getModule_SecAdmin()->isWlEnabled(), // white label mitigation
100
  ]
101
  ];
102
 
@@ -130,8 +130,8 @@ class LoginIntentPage {
130
  'time_remaining' => $nTimeRemaining,
131
  ],
132
  'hrefs' => [
133
- 'css_bootstrap' => $con->urls->forCss( 'bootstrap4.min' ),
134
- 'js_bootstrap' => $con->urls->forJs( 'bootstrap4.bundle.min' ),
135
  'shield_logo' => 'https://ps.w.org/wp-simple-firewall/assets/banner-772x250.png',
136
  'what_is_this' => 'https://support.getshieldsecurity.com/support/solutions/articles/3000064840',
137
  ],
@@ -140,7 +140,7 @@ class LoginIntentPage {
140
  'favicon' => $con->urls->forImage( 'pluginlogo_24x24.png' ),
141
  ],
142
  'flags' => [
143
- 'show_branded_links' => !$con->getModule_SecAdmin()->isWlEnabled(), // white label mitigation
144
  'has_u2f' => isset( $oIC->getProvidersForUser(
145
  Services::WpUsers()->getCurrentWpUser(), true )[ LoginGuard\Lib\TwoFactor\Provider\U2F::SLUG ] )
146
  ],
96
  ],
97
  'flags' => [
98
  'can_skip_mfa' => $opts->isMfaSkip(),
99
+ 'show_branded_links' => !$mod->isEnabledWhitelabel(), // white label mitigation
100
  ]
101
  ];
102
 
130
  'time_remaining' => $nTimeRemaining,
131
  ],
132
  'hrefs' => [
133
+ 'css_bootstrap' => $con->urls->forCss( 'bootstrap' ),
134
+ 'js_bootstrap' => $con->urls->forJs( 'bootstrap' ),
135
  'shield_logo' => 'https://ps.w.org/wp-simple-firewall/assets/banner-772x250.png',
136
  'what_is_this' => 'https://support.getshieldsecurity.com/support/solutions/articles/3000064840',
137
  ],
140
  'favicon' => $con->urls->forImage( 'pluginlogo_24x24.png' ),
141
  ],
142
  'flags' => [
143
+ 'show_branded_links' => !$mod->isEnabledWhitelabel(), // white label mitigation
144
  'has_u2f' => isset( $oIC->getProvidersForUser(
145
  Services::WpUsers()->getCurrentWpUser(), true )[ LoginGuard\Lib\TwoFactor\Provider\U2F::SLUG ] )
146
  ],
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Profiles/CustomForms.php CHANGED
@@ -2,14 +2,14 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\TwoFactor\Profiles;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\TwoFactor\MfaControllerConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\TwoFactor\Provider\BaseProvider;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Consumer\WpUserConsumer;
9
 
10
  class CustomForms {
11
 
12
- use OneTimeExecute;
13
  use MfaControllerConsumer;
14
  use WpUserConsumer;
15
 
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\TwoFactor\Profiles;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\TwoFactor\MfaControllerConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\TwoFactor\Provider\BaseProvider;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Consumer\WpUserConsumer;
9
 
10
  class CustomForms {
11
 
12
+ use ExecOnce;
13
  use MfaControllerConsumer;
14
  use WpUserConsumer;
15
 
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/Yubikey.php CHANGED
@@ -13,7 +13,6 @@ class Yubikey extends BaseProvider {
13
  const URL_YUBIKEY_VERIFY = 'https://api.yubico.com/wsapi/2.0/verify';
14
 
15
  public function setupProfile() {
16
-
17
  add_filter( 'shield/custom_enqueues', function ( array $enqueues, $hook ) {
18
  if ( in_array( $hook, [ 'profile.php', ] ) ) {
19
  $enqueues[ Enqueue::JS ][] = 'shield/userprofile';
13
  const URL_YUBIKEY_VERIFY = 'https://api.yubico.com/wsapi/2.0/verify';
14
 
15
  public function setupProfile() {
 
16
  add_filter( 'shield/custom_enqueues', function ( array $enqueues, $hook ) {
17
  if ( in_array( $hook, [ 'profile.php', ] ) ) {
18
  $enqueues[ Enqueue::JS ][] = 'shield/userprofile';
src/lib/src/Modules/LoginGuard/ModCon.php CHANGED
@@ -29,20 +29,25 @@ class ModCon extends BaseShield\ModCon {
29
  ->sendEmailVerifyCanSend();
30
  }
31
 
32
- $aIds = $opts->getOpt( 'antibot_form_ids', [] );
33
- foreach ( $aIds as $nKey => $sId ) {
34
- $sId = trim( strip_tags( $sId ) );
35
- if ( empty( $sId ) ) {
36
- unset( $aIds[ $nKey ] );
37
  }
38
  else {
39
- $aIds[ $nKey ] = $sId;
40
  }
41
  }
42
- $opts->setOpt( 'antibot_form_ids', array_values( array_unique( $aIds ) ) );
43
 
44
  $this->cleanLoginUrlPath();
45
  $this->ensureCorrectCaptchaConfig();
 
 
 
 
 
46
  }
47
 
48
  public function ensureCorrectCaptchaConfig() {
@@ -69,6 +74,7 @@ class ModCon extends BaseShield\ModCon {
69
  $this->processEmailSendVerify();
70
  break;
71
  default:
 
72
  break;
73
  }
74
  }
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 ) ) );
43
 
44
  $this->cleanLoginUrlPath();
45
  $this->ensureCorrectCaptchaConfig();
46
+
47
+ if ( $opts->isEnabledAntiBot() ) {
48
+ $opts->setOpt( 'enable_google_recaptcha_login', 'disabled' );
49
+ $opts->setOpt( 'enable_login_gasp_check', 'N' );
50
+ }
51
  }
52
 
53
  public function ensureCorrectCaptchaConfig() {
74
  $this->processEmailSendVerify();
75
  break;
76
  default:
77
+ parent::handleModAction( $action );
78
  break;
79
  }
80
  }
src/lib/src/Modules/LoginGuard/Options.php CHANGED
@@ -26,10 +26,7 @@ class Options extends BaseShield\Options {
26
  return (string)$this->getOpt( 'rename_wplogin_path', '' );
27
  }
28
 
29
- /**
30
- * @return array
31
- */
32
- public function getEmail2FaRoles() {
33
  /** @var ModCon $mod */
34
  $mod = $this->getMod();
35
  $roles = $this->getOpt( 'two_factor_auth_user_roles', [] );
@@ -76,7 +73,12 @@ class Options extends BaseShield\Options {
76
  }
77
 
78
  public function isEnabledGaspCheck() :bool {
79
- return $this->isOpt( 'enable_login_gasp_check', 'Y' );
 
 
 
 
 
80
  }
81
 
82
  public function isEnabledEmailAuthAnyUserSet() :bool {
@@ -103,17 +105,11 @@ class Options extends BaseShield\Options {
103
  return $this->isProtect( 'login' );
104
  }
105
 
106
- /**
107
- * @return bool
108
- */
109
- public function isProtectLostPassword() {
110
  return $this->isProtect( 'password' );
111
  }
112
 
113
- /**
114
- * @return bool
115
- */
116
- public function isProtectRegister() {
117
  return $this->isProtect( 'register' );
118
  }
119
 
@@ -121,9 +117,9 @@ class Options extends BaseShield\Options {
121
  * @param string $location - see config for keys, e.g. login, register, password, checkout_woo
122
  * @return bool
123
  */
124
- public function isProtect( $location ) {
125
- $aLocs = $this->getOpt( 'bot_protection_locations' );
126
- return in_array( $location, is_array( $aLocs ) ? $aLocs : $this->getOptDefault( 'bot_protection_locations' ) );
127
  }
128
 
129
  public function isUseLoginIntentPage() :bool {
26
  return (string)$this->getOpt( 'rename_wplogin_path', '' );
27
  }
28
 
29
+ public function getEmail2FaRoles() :array {
 
 
 
30
  /** @var ModCon $mod */
31
  $mod = $this->getMod();
32
  $roles = $this->getOpt( 'two_factor_auth_user_roles', [] );
73
  }
74
 
75
  public function isEnabledGaspCheck() :bool {
76
+ return $this->isOpt( 'enable_login_gasp_check', 'Y' )
77
+ && !$this->isEnabledAntiBot();
78
+ }
79
+
80
+ public function isEnabledAntiBot() :bool {
81
+ return $this->isOpt( 'enable_antibot_check', 'Y' );
82
  }
83
 
84
  public function isEnabledEmailAuthAnyUserSet() :bool {
105
  return $this->isProtect( 'login' );
106
  }
107
 
108
+ public function isProtectLostPassword() :bool {
 
 
 
109
  return $this->isProtect( 'password' );
110
  }
111
 
112
+ public function isProtectRegister() :bool {
 
 
 
113
  return $this->isProtect( 'register' );
114
  }
115
 
117
  * @param string $location - see config for keys, e.g. login, register, password, checkout_woo
118
  * @return bool
119
  */
120
+ public function isProtect( $location ) :bool {
121
+ $locs = $this->getOpt( 'bot_protection_locations' );
122
+ return in_array( $location, is_array( $locs ) ? $locs : $this->getOptDefault( 'bot_protection_locations' ) );
123
  }
124
 
125
  public function isUseLoginIntentPage() :bool {
src/lib/src/Modules/LoginGuard/Processor.php CHANGED
@@ -21,8 +21,18 @@ class Processor extends BaseShield\Processor {
21
  ->execute();
22
 
23
  if ( !$mod->isVisitorWhitelisted() ) {
24
- ( new Lib\AntiBot\AntibotSetup() )->setMod( $mod );
 
 
 
 
25
  $mod->getLoginIntentController()->run();
26
  }
27
  }
 
 
 
 
 
 
28
  }
21
  ->execute();
22
 
23
  if ( !$mod->isVisitorWhitelisted() ) {
24
+
25
+ add_action( 'init', function () {
26
+ $this->launchAntiBot();
27
+ }, -100 );
28
+
29
  $mod->getLoginIntentController()->run();
30
  }
31
  }
32
+
33
+ private function launchAntiBot() {
34
+ ( new Lib\AntiBot\AntibotSetup() )
35
+ ->setMod( $this->getMod() )
36
+ ->execute();
37
+ }
38
  }
src/lib/src/Modules/LoginGuard/Strings.php CHANGED
@@ -218,6 +218,18 @@ class Strings extends Base\Strings {
218
  .'<br/><strong>'.sprintf( '%s - %s', __( 'Important', 'wp-simple-firewall' ), __( "Some forms are more dynamic than others so if you experience problems, please use non-Invisible CAPTCHA.", 'wp-simple-firewall' ) ).'</strong>';
219
  break;
220
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  case 'bot_protection_locations' :
222
  $name = __( 'Protection Locations', 'wp-simple-firewall' );
223
  $summary = __( 'Which Forms Should Be Protected', 'wp-simple-firewall' );
218
  .'<br/><strong>'.sprintf( '%s - %s', __( 'Important', 'wp-simple-firewall' ), __( "Some forms are more dynamic than others so if you experience problems, please use non-Invisible CAPTCHA.", 'wp-simple-firewall' ) ).'</strong>';
219
  break;
220
 
221
+ case 'enable_antibot_check' :
222
+ $name = __( 'AntiBot Detection Engine', 'wp-simple-firewall' );
223
+ $summary = __( "Use AntiBot Detection Engine To Detect Bots", 'wp-simple-firewall' );
224
+ $desc = [
225
+ sprintf( __( "AntiBot Detection Engine is %s's exclusive bot-detection technology that removes the needs for CAPTCHA and other challenges.", 'wp-simple-firewall' ),
226
+ $this->getCon()->getHumanName() ),
227
+ __( 'This feature is designed to replace the CAPTCHA and Bot Protection options.', 'wp-simple-firewall' ),
228
+ sprintf( '%s - %s', __( 'Important', 'wp-simple-firewall' ),
229
+ __( "Switching on this feature will disable the CAPTCHA and Bot Protection settings.", 'wp-simple-firewall' ) )
230
+ ];
231
+ break;
232
+
233
  case 'bot_protection_locations' :
234
  $name = __( 'Protection Locations', 'wp-simple-firewall' );
235
  $summary = __( 'Which Forms Should Be Protected', 'wp-simple-firewall' );
src/lib/src/Modules/Plugin/AjaxHandler.php CHANGED
@@ -12,58 +12,58 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
12
  protected function processAjaxAction( string $action ) :array {
13
  switch ( $action ) {
14
  case 'bulk_action':
15
- $aResponse = $this->ajaxExec_BulkItemAction();
16
  break;
17
 
18
  case 'delete_forceoff':
19
- $aResponse = $this->ajaxExec_DeleteForceOff();
20
  break;
21
 
22
  case 'render_table_adminnotes':
23
- $aResponse = $this->ajaxExec_RenderTableAdminNotes();
24
  break;
25
 
26
  case 'note_delete':
27
- $aResponse = $this->ajaxExec_AdminNotesDelete();
28
  break;
29
 
30
  case 'note_insert':
31
- $aResponse = $this->ajaxExec_AdminNotesInsert();
32
  break;
33
 
34
  case 'import_from_site':
35
- $aResponse = $this->ajaxExec_ImportFromSite();
36
  break;
37
 
38
  case 'plugin_badge_close':
39
- $aResponse = $this->ajaxExec_PluginBadgeClose();
40
  break;
41
 
42
  case 'set_plugin_tracking':
43
- $aResponse = $this->ajaxExec_SetPluginTrackingPerm();
44
  break;
45
 
46
  case 'send_deactivate_survey':
47
- $aResponse = $this->ajaxExec_SendDeactivateSurvey();
48
  break;
49
 
50
  case 'sgoptimizer_turnoff':
51
- $aResponse = $this->ajaxExec_TurnOffSiteGroundOptions();
52
  break;
53
 
54
  case 'ipdetect':
55
- $aResponse = $this->ajaxExec_IpDetect();
56
  break;
57
 
58
  case 'mark_tour_finished':
59
- $aResponse = $this->ajaxExec_MarkTourFinished();
60
  break;
61
 
62
  default:
63
- $aResponse = parent::processAjaxAction( $action );
64
  }
65
 
66
- return $aResponse;
67
  }
68
 
69
  private function ajaxExec_SendDeactivateSurvey() :array {
@@ -234,19 +234,19 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
234
  /** @var ModCon $mod */
235
  $mod = $this->getMod();
236
  $success = false;
237
- $aFormParams = $this->getAjaxFormParams();
238
 
239
- $sNote = isset( $aFormParams[ 'admin_note' ] ) ? $aFormParams[ 'admin_note' ] : '';
240
  if ( !$mod->getCanAdminNotes() ) {
241
  $msg = __( "Sorry, the Admin Notes feature isn't available.", 'wp-simple-firewall' );
242
  }
243
- elseif ( empty( $sNote ) ) {
244
  $msg = __( 'Sorry, but it appears your note was empty.', 'wp-simple-firewall' );
245
  }
246
  else {
247
- /** @var Shield\Databases\AdminNotes\Insert $oInserter */
248
- $oInserter = $mod->getDbHandler_Notes()->getQueryInserter();
249
- $success = $oInserter->create( $sNote );
250
  $msg = $success ? __( 'Note created successfully.', 'wp-simple-firewall' ) : __( 'Note could not be created.', 'wp-simple-firewall' );
251
  }
252
  return [
12
  protected function processAjaxAction( string $action ) :array {
13
  switch ( $action ) {
14
  case 'bulk_action':
15
+ $response = $this->ajaxExec_BulkItemAction();
16
  break;
17
 
18
  case 'delete_forceoff':
19
+ $response = $this->ajaxExec_DeleteForceOff();
20
  break;
21
 
22
  case 'render_table_adminnotes':
23
+ $response = $this->ajaxExec_RenderTableAdminNotes();
24
  break;
25
 
26
  case 'note_delete':
27
+ $response = $this->ajaxExec_AdminNotesDelete();
28
  break;
29
 
30
  case 'note_insert':
31
+ $response = $this->ajaxExec_AdminNotesInsert();
32
  break;
33
 
34
  case 'import_from_site':
35
+ $response = $this->ajaxExec_ImportFromSite();
36
  break;
37
 
38
  case 'plugin_badge_close':
39
+ $response = $this->ajaxExec_PluginBadgeClose();
40
  break;
41
 
42
  case 'set_plugin_tracking':
43
+ $response = $this->ajaxExec_SetPluginTrackingPerm();
44
  break;
45
 
46
  case 'send_deactivate_survey':
47
+ $response = $this->ajaxExec_SendDeactivateSurvey();
48
  break;
49
 
50
  case 'sgoptimizer_turnoff':
51
+ $response = $this->ajaxExec_TurnOffSiteGroundOptions();
52
  break;
53
 
54
  case 'ipdetect':
55
+ $response = $this->ajaxExec_IpDetect();
56
  break;
57
 
58
  case 'mark_tour_finished':
59
+ $response = $this->ajaxExec_MarkTourFinished();
60
  break;
61
 
62
  default:
63
+ $response = parent::processAjaxAction( $action );
64
  }
65
 
66
+ return $response;
67
  }
68
 
69
  private function ajaxExec_SendDeactivateSurvey() :array {
234
  /** @var ModCon $mod */
235
  $mod = $this->getMod();
236
  $success = false;
237
+ $formParams = $this->getAjaxFormParams();
238
 
239
+ $note = trim( $formParams[ 'admin_note' ] ?? '' );
240
  if ( !$mod->getCanAdminNotes() ) {
241
  $msg = __( "Sorry, the Admin Notes feature isn't available.", 'wp-simple-firewall' );
242
  }
243
+ elseif ( empty( $note ) ) {
244
  $msg = __( 'Sorry, but it appears your note was empty.', 'wp-simple-firewall' );
245
  }
246
  else {
247
+ /** @var Shield\Databases\AdminNotes\Insert $inserter */
248
+ $inserter = $mod->getDbHandler_Notes()->getQueryInserter();
249
+ $success = $inserter->create( $note );
250
  $msg = $success ? __( 'Note created successfully.', 'wp-simple-firewall' ) : __( 'Note could not be created.', 'wp-simple-firewall' );
251
  }
252
  return [
src/lib/src/Modules/Plugin/Components/PluginBadge.php CHANGED
@@ -74,7 +74,7 @@ class PluginBadge {
74
  else {
75
  $badgeUrl = 'https://shsec.io/wpsecurityfirewall';
76
  $name = $con->getHumanName();
77
- $logo = $con->getPluginUrl_Image( 'shield/shield-security-logo-colour-32px.png' );
78
 
79
  $lic = $con->getModule_License()
80
  ->getLicenseHandler()
74
  else {
75
  $badgeUrl = 'https://shsec.io/wpsecurityfirewall';
76
  $name = $con->getHumanName();
77
+ $logo = $con->urls->forImage( 'shield/shield-security-logo-colour-32px.png' );
78
 
79
  $lic = $con->getModule_License()
80
  ->getLicenseHandler()
src/lib/src/Modules/Plugin/Debug.php CHANGED
@@ -3,18 +3,10 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
6
- use FernleafSystems\Wordpress\Services\Utilities\Net\IpIdentify;
7
 
8
  class Debug extends Modules\Base\Debug {
9
 
10
  public function run() {
11
- $this->ipID();
12
  die();
13
  }
14
-
15
- private function ipID() {
16
- $id = ( new IpIdentify( '198.61.176.9' ) )
17
- ->run();
18
- var_dump( $id );
19
- }
20
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
 
6
 
7
  class Debug extends Modules\Base\Debug {
8
 
9
  public function run() {
 
10
  die();
11
  }
 
 
 
 
 
 
12
  }
src/lib/src/Modules/Plugin/Insights/DashboardCards.php CHANGED
@@ -48,14 +48,17 @@ class DashboardCards {
48
  /** @var Plugin\ModCon $mod */
49
  $mod = $this->getMod();
50
 
 
 
51
  return $mod->renderTemplate(
52
  '/wpadmin_pages/insights/dashboard/card_settings.twig',
53
  [
54
  'c' => [
55
  'title' => __( 'Shield Settings', 'wp-simple-firewall' ),
56
- 'img' => $con->getPluginUrl_Image( 'bootstrap/sliders.svg' ),
 
57
  'paras' => [
58
- sprintf( __( "%s settings are arranged into modules.", 'wp-simple-firewall' ), $con->getHumanName() )
59
  .' '.__( 'Choose the module you need from the dropdown.', 'wp-simple-firewall' )
60
  ],
61
  'actions' => [
@@ -81,7 +84,7 @@ class DashboardCards {
81
  );
82
  }
83
 
84
- protected function renderStandardCard( $card ) {
85
  /** @var Plugin\ModCon $mod */
86
  $mod = $this->getMod();
87
  return $mod->renderTemplate(
@@ -97,6 +100,8 @@ class DashboardCards {
97
  $modInsights = $con->getModule_Insights();
98
  $modPlugin = $con->getModule_Plugin();
99
 
 
 
100
  /** @var AdminNotes\EntryVO $note */
101
  $note = $modPlugin->getDbHandler_Notes()->getQuerySelector()->first();
102
  $latestNote = $note instanceof AdminNotes\EntryVO ?
@@ -107,9 +112,10 @@ class DashboardCards {
107
 
108
  'overview' => [
109
  'title' => __( 'Security Overview', 'wp-simple-firewall' ),
110
- 'img' => $con->getPluginUrl_Image( 'bootstrap/binoculars.svg' ),
 
111
  'paras' => [
112
- sprintf( __( "Review your entire Shield Security configuration at a glance to see what's working and what's not.", 'wp-simple-firewall' ), $con->getHumanName() ),
113
  ],
114
  'actions' => [
115
  [
@@ -121,10 +127,11 @@ class DashboardCards {
121
 
122
  'scans' => [
123
  'title' => __( 'Scans and Protection', 'wp-simple-firewall' ),
124
- 'img' => $con->getPluginUrl_Image( 'bootstrap/shield-shaded.svg' ),
 
125
  'paras' => [
126
- sprintf( __( "Use %s Scans to automatically detect and repair intrusions on your site.", 'wp-simple-firewall' ), $con->getHumanName() ),
127
- sprintf( __( "%s scans WordPress core files, plugins, themes and will detect Malware (ShieldPRO).", 'wp-simple-firewall' ), $con->getHumanName() ),
128
  ],
129
  'actions' => [
130
  [
@@ -140,9 +147,10 @@ class DashboardCards {
140
 
141
  'sec_admin' => [
142
  'title' => __( 'Security Admin', 'wp-simple-firewall' ),
143
- 'img' => $con->getPluginUrl_Image( 'bootstrap/person-badge.svg' ),
 
144
  'paras' => [
145
- sprintf( __( "Restrict access to %s itself and prevent unwanted changes to your site by other administrators.", 'wp-simple-firewall' ), $con->getHumanName() ),
146
  ],
147
  'actions' => [
148
  [
@@ -152,9 +160,24 @@ class DashboardCards {
152
  ]
153
  ],
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  'free_trial' => [
156
  'title' => __( 'Free ShieldPRO Trial', 'wp-simple-firewall' ),
157
- 'img' => $con->getPluginUrl_Image( 'bootstrap/emoji-smile.svg' ),
158
  'paras' => [
159
  __( "Full, unrestricted access to ShieldPRO with no obligation.", 'wp-simple-firewall' ),
160
  __( "Turn-on the ShieldPRO trial within 60 seconds.", 'wp-simple-firewall' )
@@ -171,7 +194,8 @@ class DashboardCards {
171
 
172
  'ips' => [
173
  'title' => __( 'IP Blocking and Bypass', 'wp-simple-firewall' ),
174
- 'img' => $con->getPluginUrl_Image( 'bootstrap/diagram-3.svg' ),
 
175
  'paras' => [
176
  __( "Shield automatically detects and blocks bad IP addresses based on your security settings.", 'wp-simple-firewall' ),
177
  __( "The IP Analysis Tool shows you all information for a given IP as it relates to your site.", 'wp-simple-firewall' ),
@@ -190,7 +214,8 @@ class DashboardCards {
190
 
191
  'audit_trail' => [
192
  'title' => __( 'Audit Trail', 'wp-simple-firewall' ),
193
- 'img' => $con->getPluginUrl_Image( 'bootstrap/person-lines-fill.svg' ),
 
194
  'paras' => [
195
  __( "Provides in-depth logging for all major WordPress events.", 'wp-simple-firewall' ),
196
  ],
@@ -208,7 +233,8 @@ class DashboardCards {
208
 
209
  'traffic' => [
210
  'title' => __( 'Traffic Logging', 'wp-simple-firewall' ),
211
- 'img' => $con->getPluginUrl_Image( 'bootstrap/stoplights.svg' ),
 
212
  'paras' => [
213
  __( "Use traffic logging to monitor visitor requests to your site.", 'wp-simple-firewall' ),
214
  __( "Traffic Rate Limiting lets you throttle requests from any single visitor.", 'wp-simple-firewall' ),
@@ -227,25 +253,27 @@ class DashboardCards {
227
 
228
  'users' => [
229
  'title' => __( 'WordPress Users', 'wp-simple-firewall' ),
230
- 'img' => $con->getPluginUrl_Image( 'bootstrap/people.svg' ),
 
231
  'paras' => [
232
  __( "Adds fine control over user sessions, account re-use, password strength and expiration, and user suspension.", 'wp-simple-firewall' ),
233
  ],
234
  'actions' => [
235
  [
236
- 'text' => __( "User Settings", 'wp-simple-firewall' ),
237
- 'href' => $con->getModule_UserManagement()->getUrl_AdminPage(),
238
  ],
239
  [
240
- 'text' => __( "Manage User Sessions", 'wp-simple-firewall' ),
241
- 'href' => $modInsights->getUrl_SubInsightsPage( 'users' ),
242
  ],
243
  ]
244
  ],
245
 
246
  'comments' => [
247
  'title' => __( 'Comment SPAM', 'wp-simple-firewall' ),
248
- 'img' => $con->getPluginUrl_Image( 'bootstrap/chat-right-dots-fill.svg' ),
 
249
  'paras' => [
250
  __( "Shield blocks 100% of all automated comments by bots (the most common type of SPAM).", 'wp-simple-firewall' ).
251
  ' '.__( "The Human SPAM filter will look for common spam words and content.", 'wp-simple-firewall' ),
@@ -268,7 +296,7 @@ class DashboardCards {
268
 
269
  'import' => [
270
  'title' => __( 'Import/Export', 'wp-simple-firewall' ),
271
- 'img' => $con->getPluginUrl_Image( 'bootstrap/arrow-down-up.svg' ),
272
  'paras' => [
273
  __( "Use the import/export feature to quickly setup a new site based on the settings of another site.", 'wp-simple-firewall' ),
274
  __( "You can also setup automatic syncing of settings between sites.", 'wp-simple-firewall' ),
@@ -287,7 +315,7 @@ class DashboardCards {
287
 
288
  'license' => [
289
  'title' => __( 'Go PRO!', 'wp-simple-firewall' ),
290
- 'img' => $con->getPluginUrl_Image( 'bootstrap/award.svg' ),
291
  'paras' => [
292
  __( "By upgrading to ShieldPRO, you support ongoing Shield development and get access to exclusive PRO features.", 'wp-simple-firewall' ),
293
  ],
@@ -307,7 +335,8 @@ class DashboardCards {
307
 
308
  'notes' => [
309
  'title' => __( 'Admin Notes', 'wp-simple-firewall' ),
310
- 'img' => $con->getPluginUrl_Image( 'bootstrap/pencil-square.svg' ),
 
311
  'paras' => [
312
  __( "Use these to keep note of important items or to-dos.", 'wp-simple-firewall' ),
313
  $latestNote
@@ -322,7 +351,7 @@ class DashboardCards {
322
 
323
  'whitelabel' => [
324
  'title' => __( 'Whitelabel', 'wp-simple-firewall' ),
325
- 'img' => $con->getPluginUrl_Image( 'bootstrap/sticky.svg' ),
326
  'paras' => [
327
  __( "Re-brand the Shield Security plugin your image.", 'wp-simple-firewall' ),
328
  __( "Use this to enhance and solidify your brand with your clients and visitors.", 'wp-simple-firewall' ),
@@ -338,7 +367,8 @@ class DashboardCards {
338
 
339
  'integrations' => [
340
  'title' => __( '3rd Party Integrations', 'wp-simple-firewall' ),
341
- 'img' => $con->getPluginUrl_Image( 'bootstrap/link-45deg.svg' ),
 
342
  'paras' => [
343
  __( "Shield integrates with 3rd party plugins and services.", 'wp-simple-firewall' ),
344
  __( "Determine what integrations Shield should use and manage the settings for them.", 'wp-simple-firewall' ),
@@ -353,9 +383,9 @@ class DashboardCards {
353
 
354
  'docs' => [
355
  'title' => __( 'Docs', 'wp-simple-firewall' ),
356
- 'img' => $con->getPluginUrl_Image( 'bootstrap/book-half.svg' ),
357
  'paras' => [
358
- sprintf( __( "Important information about %s releases and changes.", 'wp-simple-firewall' ), $con->getHumanName() ),
359
  ],
360
  'actions' => [
361
  [
@@ -367,7 +397,7 @@ class DashboardCards {
367
 
368
  'debug' => [
369
  'title' => __( 'Debug Info', 'wp-simple-firewall' ),
370
- 'img' => $con->getPluginUrl_Image( 'bootstrap/bug.svg' ),
371
  'paras' => [
372
  __( "If you contact support, they may ask you to show them your Debug Information page.", 'wp-simple-firewall' ),
373
  __( "It's also an interesting place to see a summary of your WordPress configuration in 1 place.", 'wp-simple-firewall' ),
@@ -393,6 +423,7 @@ class DashboardCards {
393
  'scans',
394
  'free_trial',
395
  'sec_admin',
 
396
  'ips',
397
  'audit_trail',
398
  'traffic',
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
+ 'introjs' => sprintf( __( "%s is a big plugin split into modules, and each with their own options - use these jump-off points to find the specific option you need.", 'wp-simple-firewall' ), $name ),
60
  'paras' => [
61
+ sprintf( __( "%s settings are arranged into modules.", 'wp-simple-firewall' ), $name )
62
  .' '.__( 'Choose the module you need from the dropdown.', 'wp-simple-firewall' )
63
  ],
64
  'actions' => [
84
  );
85
  }
86
 
87
+ protected function renderStandardCard( array $card ) :string {
88
  /** @var Plugin\ModCon $mod */
89
  $mod = $this->getMod();
90
  return $mod->renderTemplate(
100
  $modInsights = $con->getModule_Insights();
101
  $modPlugin = $con->getModule_Plugin();
102
 
103
+ $name = $con->getHumanName();
104
+
105
  /** @var AdminNotes\EntryVO $note */
106
  $note = $modPlugin->getDbHandler_Notes()->getQuerySelector()->first();
107
  $latestNote = $note instanceof AdminNotes\EntryVO ?
112
 
113
  'overview' => [
114
  'title' => __( 'Security Overview', 'wp-simple-firewall' ),
115
+ 'img' => $con->urls->forImage( 'bootstrap/binoculars.svg' ),
116
+ 'introjs' => sprintf( __( "Review your entire Shield Security configuration at a glance to see what's working and what's not.", 'wp-simple-firewall' ), $name ),
117
  'paras' => [
118
+ sprintf( __( "Review your entire %s security configuration at a glance to see what's working and what's not.", 'wp-simple-firewall' ), $name ),
119
  ],
120
  'actions' => [
121
  [
127
 
128
  'scans' => [
129
  'title' => __( 'Scans and Protection', 'wp-simple-firewall' ),
130
+ 'img' => $con->urls->forImage( 'bootstrap/shield-shaded.svg' ),
131
+ 'introjs' => sprintf( __( "Run a %s scan at any time, or view the results from the latest scan.", 'wp-simple-firewall' ), $name ),
132
  'paras' => [
133
+ sprintf( __( "Use %s Scans to automatically detect and repair intrusions on your site.", 'wp-simple-firewall' ), $name ),
134
+ sprintf( __( "%s scans WordPress core files, plugins, themes and will detect Malware (ShieldPRO).", 'wp-simple-firewall' ), $name ),
135
  ],
136
  'actions' => [
137
  [
147
 
148
  'sec_admin' => [
149
  'title' => __( 'Security Admin', 'wp-simple-firewall' ),
150
+ 'img' => $con->urls->forImage( 'bootstrap/person-badge.svg' ),
151
+ 'introjs' => sprintf( __( "Lock down access to %s itself to specific WP Administrators.", 'wp-simple-firewall' ), $name ),
152
  'paras' => [
153
+ sprintf( __( "Restrict access to %s itself and prevent unwanted changes to your site by other administrators.", 'wp-simple-firewall' ), $name ),
154
  ],
155
  'actions' => [
156
  [
160
  ]
161
  ],
162
 
163
+ 'reports' => [
164
+ 'title' => __( 'Reports and Stats', 'wp-simple-firewall' ),
165
+ 'img' => $con->urls->forImage( 'bootstrap/graph-up.svg' ),
166
+ 'introjs' => sprintf( __( "See the effect on your site security by %s in numbers", 'wp-simple-firewall' ), $name ),
167
+ 'paras' => [
168
+ 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 ),
169
+ ],
170
+ 'actions' => [
171
+ [
172
+ 'text' => __( "View Reports and Stats", 'wp-simple-firewall' ),
173
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'reports' ),
174
+ ],
175
+ ]
176
+ ],
177
+
178
  'free_trial' => [
179
  'title' => __( 'Free ShieldPRO Trial', 'wp-simple-firewall' ),
180
+ 'img' => $con->urls->forImage( 'bootstrap/emoji-smile.svg' ),
181
  'paras' => [
182
  __( "Full, unrestricted access to ShieldPRO with no obligation.", 'wp-simple-firewall' ),
183
  __( "Turn-on the ShieldPRO trial within 60 seconds.", 'wp-simple-firewall' )
194
 
195
  'ips' => [
196
  'title' => __( 'IP Blocking and Bypass', 'wp-simple-firewall' ),
197
+ 'img' => $con->urls->forImage( 'bootstrap/diagram-3.svg' ),
198
+ 'introjs' => __( "Protection begins by detecting bad bots - Review and Analyse all visitor IPs that have an impact on your site.", 'wp-simple-firewall' ),
199
  'paras' => [
200
  __( "Shield automatically detects and blocks bad IP addresses based on your security settings.", 'wp-simple-firewall' ),
201
  __( "The IP Analysis Tool shows you all information for a given IP as it relates to your site.", 'wp-simple-firewall' ),
214
 
215
  'audit_trail' => [
216
  'title' => __( 'Audit Trail', 'wp-simple-firewall' ),
217
+ 'img' => $con->urls->forImage( 'bootstrap/person-lines-fill.svg' ),
218
+ 'introjs' => __( "Track and review all important actions taken on your site - see the Who, What and When.", 'wp-simple-firewall' ),
219
  'paras' => [
220
  __( "Provides in-depth logging for all major WordPress events.", 'wp-simple-firewall' ),
221
  ],
233
 
234
  'traffic' => [
235
  'title' => __( 'Traffic Logging', 'wp-simple-firewall' ),
236
+ 'img' => $con->urls->forImage( 'bootstrap/stoplights.svg' ),
237
+ 'introjs' => __( "Monitor and watch traffic as it hits your site.", 'wp-simple-firewall' ),
238
  'paras' => [
239
  __( "Use traffic logging to monitor visitor requests to your site.", 'wp-simple-firewall' ),
240
  __( "Traffic Rate Limiting lets you throttle requests from any single visitor.", 'wp-simple-firewall' ),
253
 
254
  'users' => [
255
  'title' => __( 'WordPress Users', 'wp-simple-firewall' ),
256
+ 'img' => $con->urls->forImage( 'bootstrap/people.svg' ),
257
+ 'introjs' => __( "Set user session timeouts and passwords requirements.", 'wp-simple-firewall' ),
258
  'paras' => [
259
  __( "Adds fine control over user sessions, account re-use, password strength and expiration, and user suspension.", 'wp-simple-firewall' ),
260
  ],
261
  'actions' => [
262
  [
263
+ 'text' => __( 'View User Sessions', 'wp-simple-firewall' ),
264
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'users' ),
265
  ],
266
  [
267
+ 'text' => __( 'User Settings', 'wp-simple-firewall' ),
268
+ 'href' => $con->getModule_UserManagement()->getUrl_AdminPage(),
269
  ],
270
  ]
271
  ],
272
 
273
  'comments' => [
274
  'title' => __( 'Comment SPAM', 'wp-simple-firewall' ),
275
+ 'img' => $con->urls->forImage( 'bootstrap/chat-right-dots-fill.svg' ),
276
+ 'introjs' => __( "Block all Comment SPAM from bots and even detect SPAMMY human comments.", 'wp-simple-firewall' ),
277
  'paras' => [
278
  __( "Shield blocks 100% of all automated comments by bots (the most common type of SPAM).", 'wp-simple-firewall' ).
279
  ' '.__( "The Human SPAM filter will look for common spam words and content.", 'wp-simple-firewall' ),
296
 
297
  'import' => [
298
  'title' => __( 'Import/Export', 'wp-simple-firewall' ),
299
+ 'img' => $con->urls->forImage( 'bootstrap/arrow-down-up.svg' ),
300
  'paras' => [
301
  __( "Use the import/export feature to quickly setup a new site based on the settings of another site.", 'wp-simple-firewall' ),
302
  __( "You can also setup automatic syncing of settings between sites.", 'wp-simple-firewall' ),
315
 
316
  'license' => [
317
  'title' => __( 'Go PRO!', 'wp-simple-firewall' ),
318
+ 'img' => $con->urls->forImage( 'bootstrap/award.svg' ),
319
  'paras' => [
320
  __( "By upgrading to ShieldPRO, you support ongoing Shield development and get access to exclusive PRO features.", 'wp-simple-firewall' ),
321
  ],
335
 
336
  'notes' => [
337
  'title' => __( 'Admin Notes', 'wp-simple-firewall' ),
338
+ 'img' => $con->urls->forImage( 'bootstrap/pencil-square.svg' ),
339
+ 'introjs' => __( "Review and Analyse all visitor IPs that have an impact on your site.", 'wp-simple-firewall' ),
340
  'paras' => [
341
  __( "Use these to keep note of important items or to-dos.", 'wp-simple-firewall' ),
342
  $latestNote
351
 
352
  'whitelabel' => [
353
  'title' => __( 'Whitelabel', 'wp-simple-firewall' ),
354
+ 'img' => $con->urls->forImage( 'bootstrap/sticky.svg' ),
355
  'paras' => [
356
  __( "Re-brand the Shield Security plugin your image.", 'wp-simple-firewall' ),
357
  __( "Use this to enhance and solidify your brand with your clients and visitors.", 'wp-simple-firewall' ),
367
 
368
  'integrations' => [
369
  'title' => __( '3rd Party Integrations', 'wp-simple-firewall' ),
370
+ 'introjs' => __( "Integrate with your favourite plugins to block SPAM and manage Shield better.", 'wp-simple-firewall' ),
371
+ 'img' => $con->urls->forImage( 'bootstrap/link-45deg.svg' ),
372
  'paras' => [
373
  __( "Shield integrates with 3rd party plugins and services.", 'wp-simple-firewall' ),
374
  __( "Determine what integrations Shield should use and manage the settings for them.", 'wp-simple-firewall' ),
383
 
384
  'docs' => [
385
  'title' => __( 'Docs', 'wp-simple-firewall' ),
386
+ 'img' => $con->urls->forImage( 'bootstrap/book-half.svg' ),
387
  'paras' => [
388
+ sprintf( __( "Important information about %s releases and changes.", 'wp-simple-firewall' ), $name ),
389
  ],
390
  'actions' => [
391
  [
397
 
398
  'debug' => [
399
  'title' => __( 'Debug Info', 'wp-simple-firewall' ),
400
+ 'img' => $con->urls->forImage( 'bootstrap/bug.svg' ),
401
  'paras' => [
402
  __( "If you contact support, they may ask you to show them your Debug Information page.", 'wp-simple-firewall' ),
403
  __( "It's also an interesting place to see a summary of your WordPress configuration in 1 place.", 'wp-simple-firewall' ),
423
  'scans',
424
  'free_trial',
425
  'sec_admin',
426
+ 'reports',
427
  'ips',
428
  'audit_trail',
429
  'traffic',
src/lib/src/Modules/Plugin/Insights/OverviewCards.php CHANGED
@@ -46,11 +46,11 @@ class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
46
  'href' => $mod->getUrl_DirectLinkToOption( 'visitor_address_source' ),
47
  ];
48
 
49
- $bRecap = $mod->getCaptchaCfg()->ready;
50
  $cards[ 'recap' ] = [
51
  'name' => __( 'CAPTCHA', 'wp-simple-firewall' ),
52
- 'state' => $bRecap ? 1 : -1,
53
- 'summary' => $bRecap ?
54
  __( 'CAPTCHA keys have been provided', 'wp-simple-firewall' )
55
  : __( "CAPTCHA keys haven't been provided", 'wp-simple-firewall' ),
56
  'href' => $mod->getUrl_DirectLinkToSection( 'section_third_party_captcha' ),
46
  'href' => $mod->getUrl_DirectLinkToOption( 'visitor_address_source' ),
47
  ];
48
 
49
+ $captchaReady = $mod->getCaptchaCfg()->ready;
50
  $cards[ 'recap' ] = [
51
  'name' => __( 'CAPTCHA', 'wp-simple-firewall' ),
52
+ 'state' => $captchaReady ? 1 : 0,
53
+ 'summary' => $captchaReady ?
54
  __( 'CAPTCHA keys have been provided', 'wp-simple-firewall' )
55
  : __( "CAPTCHA keys haven't been provided", 'wp-simple-firewall' ),
56
  'href' => $mod->getUrl_DirectLinkToSection( 'section_third_party_captcha' ),
src/lib/src/Modules/Plugin/Lib/Captcha/CaptchaConfigVO.php CHANGED
@@ -1,8 +1,8 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Captcha;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
 
7
  /**
8
  * Class CaptchaConfigVO
@@ -16,32 +16,29 @@ use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
16
  * @property string $url_api
17
  * @property string $js_handle
18
  */
19
- class CaptchaConfigVO {
20
 
21
  const PROV_GOOGLE_RECAP2 = 'grecaptcha';
22
  const PROV_HCAPTCHA = 'hcaptcha';
23
- use StdClassAdapter {
24
- __get as __adapterGet;
25
- }
26
 
27
  /**
28
- * @param string $sProperty
29
  * @return mixed
30
  */
31
- public function __get( $sProperty ) {
32
 
33
- $mValue = $this->__adapterGet( $sProperty );
34
 
35
- switch ( $sProperty ) {
36
 
37
  case 'ready':
38
- $mValue = !empty( $this->key ) && !empty( $this->secret );
39
  break;
40
 
41
  default:
42
  break;
43
  }
44
 
45
- return $mValue;
46
  }
47
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Captcha;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
 
7
  /**
8
  * Class CaptchaConfigVO
16
  * @property string $url_api
17
  * @property string $js_handle
18
  */
19
+ class CaptchaConfigVO extends DynPropertiesClass {
20
 
21
  const PROV_GOOGLE_RECAP2 = 'grecaptcha';
22
  const PROV_HCAPTCHA = 'hcaptcha';
 
 
 
23
 
24
  /**
25
+ * @param string $key
26
  * @return mixed
27
  */
28
+ public function __get( string $key ) {
29
 
30
+ $value = parent::__get( $key );
31
 
32
+ switch ( $key ) {
33
 
34
  case 'ready':
35
+ $value = !empty( $this->key ) && !empty( $this->secret );
36
  break;
37
 
38
  default:
39
  break;
40
  }
41
 
42
+ return $value;
43
  }
44
  }
src/lib/src/Modules/Plugin/Lib/Debug/Collate.php CHANGED
@@ -65,7 +65,7 @@ class Collate {
65
  $diff = ( new WorldTimeApi() )->diffServerWithReal();
66
  }
67
  catch ( \Exception $e ) {
68
- $diff = 'failed';
69
  }
70
 
71
  return [
@@ -173,6 +173,11 @@ class Collate {
173
  sprintf( '%s (rows: ~%s)', 'Ready', $dbh->getQuerySelector()->count() )
174
  : 'Missing';
175
 
 
 
 
 
 
176
  $dbh = $con->getModule_HackGuard()->getDbHandler_ScanResults();
177
  $data[ 'DB Table: Scan' ] = $dbh->isReady() ?
178
  sprintf( '%s (rows: ~%s)', 'Ready', $dbh->getQuerySelector()->count() )
@@ -195,20 +200,25 @@ class Collate {
195
  $con = $this->getCon();
196
  $modPlug = $con->getModule_Plugin();
197
 
198
- $sHome = Services::WpGeneral()->getHomeUrl();
 
 
 
 
 
 
199
  $data = [
200
- sprintf( 'Loopback To %s', $sHome ) => $modPlug->getCanSiteCallToItself() ? 'Yes' : 'No',
201
- 'Handshake ShieldNET' => $modPlug->getShieldNetApiController()
202
- ->canHandshake() ? 'Yes' : 'No',
203
- 'WP Hashes Ping' => ( new ApiPing() )->ping() ? 'Yes' : 'No',
204
  ];
205
 
206
- $oPing = new Licenses\Keyless\Ping();
207
- $oPing->lookup_url_stub = $this->getOptions()->getDef( 'license_store_url_api' );
208
- $data[ 'Ping License Server' ] = $oPing->ping() ? 'Yes' : 'No';
209
 
210
- $sTmpPath = $con->getPluginCachePath();
211
- $data[ 'Write TMP DIR' ] = empty( $sTmpPath ) ? 'No' : 'Yes: '.$sTmpPath;
212
 
213
  return $data;
214
  }
65
  $diff = ( new WorldTimeApi() )->diffServerWithReal();
66
  }
67
  catch ( \Exception $e ) {
68
+ $diff = 'failed: '.$e->getMessage();
69
  }
70
 
71
  return [
173
  sprintf( '%s (rows: ~%s)', 'Ready', $dbh->getQuerySelector()->count() )
174
  : 'Missing';
175
 
176
+ $dbh = $con->getModule_IPs()->getDbHandler_BotSignals();
177
+ $data[ 'DB Table: Bot Signals' ] = $dbh->isReady() ?
178
+ sprintf( '%s (rows: ~%s)', 'Ready', $dbh->getQuerySelector()->count() )
179
+ : 'Missing';
180
+
181
  $dbh = $con->getModule_HackGuard()->getDbHandler_ScanResults();
182
  $data[ 'DB Table: Scan' ] = $dbh->isReady() ?
183
  sprintf( '%s (rows: ~%s)', 'Ready', $dbh->getQuerySelector()->count() )
200
  $con = $this->getCon();
201
  $modPlug = $con->getModule_Plugin();
202
 
203
+ try {
204
+ $loopback = $modPlug->canSiteLoopback() ? 'Yes' : 'No';
205
+ }
206
+ catch ( \Exception $e ) {
207
+ $loopback = 'Unknown - requires WP v5.4+';
208
+ }
209
+
210
  $data = [
211
+ 'Can Loopback Request' => $loopback,
212
+ 'Handshake ShieldNET' => $modPlug->getShieldNetApiController()
213
+ ->canHandshake() ? 'Yes' : 'No',
214
+ 'WP Hashes Ping' => ( new ApiPing() )->ping() ? 'Yes' : 'No',
215
  ];
216
 
217
+ $licPing = new Licenses\Keyless\Ping();
218
+ $licPing->lookup_url_stub = $con->getModule_License()->getOptions()->getDef( 'license_store_url_api' );
219
+ $data[ 'Ping License Server' ] = $licPing->ping() ? 'Yes' : 'No';
220
 
221
+ $data[ 'Write TMP/Cache DIR' ] = $con->hasCacheDir() ?'Yes: '.$con->getPluginCachePath() : 'No' ;
 
222
 
223
  return $data;
224
  }
src/lib/src/Modules/Plugin/Lib/ImportExport/Export.php CHANGED
@@ -106,9 +106,8 @@ class Export {
106
  }
107
 
108
  public function toFile() {
109
- $aData = $this->toStandardArray();
110
  Services::Response()->downloadStringAsFile(
111
- implode( "\n", $aData ),
112
  sprintf( 'shieldexport-%s-%s.json',
113
  Services::Data()->urlStripSchema( Services::WpGeneral()->getHomeUrl() ),
114
  date( 'Ymd_His' )
106
  }
107
 
108
  public function toFile() {
 
109
  Services::Response()->downloadStringAsFile(
110
+ implode( "\n", $this->toStandardArray() ),
111
  sprintf( 'shieldexport-%s-%s.json',
112
  Services::Data()->urlStripSchema( Services::WpGeneral()->getHomeUrl() ),
113
  date( 'Ymd_His' )
src/lib/src/Modules/Plugin/Lib/ImportExport/ImportExportController.php CHANGED
@@ -130,7 +130,7 @@ class ImportExportController {
130
  'can_importexport' => $this->getCon()->isPremiumActive(),
131
  ],
132
  'hrefs' => [
133
- 'export_file_download' => $this->createExportFileDownloadLink()
134
  ],
135
  'strings' => [
136
  'tab_by_file' => __( 'Import From File', 'wp-simple-firewall' ),
@@ -173,14 +173,4 @@ class ImportExportController {
173
  ]
174
  ];
175
  }
176
-
177
- /**
178
- * @return string
179
- */
180
- private function createExportFileDownloadLink() {
181
- return add_query_arg(
182
- $this->getMod()->getNonceActionData( 'export_file_download' ),
183
- $this->getMod()->getUrl_AdminPage()
184
- );
185
- }
186
  }
130
  'can_importexport' => $this->getCon()->isPremiumActive(),
131
  ],
132
  'hrefs' => [
133
+ 'export_file_download' => $mod->createFileDownloadLink( 'plugin_export' )
134
  ],
135
  'strings' => [
136
  'tab_by_file' => __( 'Import From File', 'wp-simple-firewall' ),
173
  ]
174
  ];
175
  }
 
 
 
 
 
 
 
 
 
 
176
  }
src/lib/src/Modules/Plugin/Lib/PluginTelemetry.php CHANGED
@@ -2,7 +2,8 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Events\Select;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\ModCon;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
@@ -12,9 +13,10 @@ use FernleafSystems\Wordpress\Services\Services;
12
  class PluginTelemetry {
13
 
14
  use ModConsumer;
15
- use OneTimeExecute;
 
16
 
17
- protected function canRun() {
18
  /** @var Plugin\Options $opts */
19
  $opts = $this->getOptions();
20
  return $opts->isTrackingEnabled() || !$opts->isTrackingPermissionSet();
@@ -36,7 +38,7 @@ class PluginTelemetry {
36
  break;
37
  }
38
 
39
- add_action( $this->getCon()->prefix( 'daily_cron' ), [ $this, 'runDailyCron' ] );
40
  }
41
 
42
  public function runDailyCron() {
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Crons\PluginCronsConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Events\Select;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\ModCon;
9
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
13
  class PluginTelemetry {
14
 
15
  use ModConsumer;
16
+ use ExecOnce;
17
+ use PluginCronsConsumer;
18
 
19
+ protected function canRun() :bool {
20
  /** @var Plugin\Options $opts */
21
  $opts = $this->getOptions();
22
  return $opts->isTrackingEnabled() || !$opts->isTrackingPermissionSet();
38
  break;
39
  }
40
 
41
+ $this->setupCronHooks();
42
  }
43
 
44
  public function runDailyCron() {
src/lib/src/Modules/Plugin/Lib/TestCacheDirWrite.php CHANGED
@@ -5,10 +5,6 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib;
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
- /**
9
- * Class TestCacheDirWrite
10
- * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib
11
- */
12
  class TestCacheDirWrite {
13
 
14
  use ModConsumer;
@@ -21,67 +17,67 @@ class TestCacheDirWrite {
21
  * @return $this
22
  */
23
  protected function run() {
24
- $aD = $this->getTestData();
25
- $nNow = Services::Request()->ts();
26
 
27
- if ( ( $aD[ 'last_success_at' ] === 0 || $nNow - WEEK_IN_SECONDS > $aD[ 'last_success_at' ] )
28
- && ( $nNow - HOUR_IN_SECONDS > $aD[ 'last_test_at' ] ) ) {
29
 
30
- $sRoot = $this->getCon()->getPath_PluginCache();
31
- $bCanWrite = !empty( $sRoot )
32
- && $this->canCreateWriteDeleteFile()
33
- && $this->canCreateWriteDeleteDir();
34
 
35
- $aD[ 'last_success_at' ] = $bCanWrite ? $nNow : 0;
36
- $aD[ 'last_test_at' ] = $nNow;
37
- $this->getOptions()->setOpt( 'cache_dir_write_test', $aD );
38
  }
39
  return $this;
40
  }
41
 
42
  private function canCreateWriteDeleteDir() :bool {
43
- $bCanWrite = false;
44
 
45
- $oFS = Services::WpFs();
46
 
47
- $sTestDir = $this->getCon()->getPluginCachePath( uniqid() );
48
- $oFS->mkdir( $sTestDir );
49
- if ( $oFS->isDir( $sTestDir ) ) {
50
- $sFile = path_join( $sTestDir, uniqid() );
51
- $oFS->touch( $sFile );
52
- $oFS->deleteDir( $sTestDir );
53
- $bCanWrite = !$oFS->isDir( $sTestDir );
54
  }
55
- return $bCanWrite;
56
  }
57
 
58
  private function canCreateWriteDeleteFile() :bool {
59
- $bCanWrite = false;
60
 
61
- $oFS = Services::WpFs();
62
 
63
- $sTestFile = $this->getCon()->getPluginCachePath( 'test_write_file.txt' );
64
- $oFS->touch( $sTestFile );
65
 
66
- if ( $oFS->exists( $sTestFile ) ) {
67
- $sUniq = uniqid();
68
- $oFS->putFileContent( $sTestFile, $sUniq );
69
- if ( $oFS->getFileContent( $sTestFile ) == $sUniq ) {
70
- $oFS->deleteFile( $sTestFile );
71
- $bCanWrite = !$oFS->exists( $sTestFile );
72
  }
73
  }
74
- return $bCanWrite;
75
  }
76
 
77
  private function getTestData() :array {
78
- $aD = $this->getOptions()->getOpt( 'cache_dir_write_test' );
79
  return array_merge(
80
  [
81
  'last_test_at' => 0,
82
  'last_success_at' => 0,
83
  ],
84
- is_array( $aD ) ? $aD : []
85
  );
86
  }
87
  }
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
 
 
 
 
8
  class TestCacheDirWrite {
9
 
10
  use ModConsumer;
17
  * @return $this
18
  */
19
  protected function run() {
20
+ $data = $this->getTestData();
21
+ $now = Services::Request()->ts();
22
 
23
+ if ( ( $data[ 'last_success_at' ] === 0 || $now - WEEK_IN_SECONDS > $data[ 'last_success_at' ] )
24
+ && ( $now - HOUR_IN_SECONDS > $data[ 'last_test_at' ] ) ) {
25
 
26
+ $rootDir = $this->getCon()->getPluginCachePath();
27
+ $canWrite = !empty( $rootDir )
28
+ && $this->canCreateWriteDeleteFile()
29
+ && $this->canCreateWriteDeleteDir();
30
 
31
+ $data[ 'last_success_at' ] = $canWrite ? $now : 0;
32
+ $data[ 'last_test_at' ] = $now;
33
+ $this->getOptions()->setOpt( 'cache_dir_write_test', $data );
34
  }
35
  return $this;
36
  }
37
 
38
  private function canCreateWriteDeleteDir() :bool {
39
+ $canWrite = false;
40
 
41
+ $FS = Services::WpFs();
42
 
43
+ $testDir = $this->getCon()->getPluginCachePath( uniqid() );
44
+ $FS->mkdir( $testDir );
45
+ if ( $FS->isDir( $testDir ) ) {
46
+ $sFile = path_join( $testDir, uniqid() );
47
+ $FS->touch( $sFile );
48
+ $FS->deleteDir( $testDir );
49
+ $canWrite = !$FS->isDir( $testDir );
50
  }
51
+ return $canWrite;
52
  }
53
 
54
  private function canCreateWriteDeleteFile() :bool {
55
+ $canWrite = false;
56
 
57
+ $FS = Services::WpFs();
58
 
59
+ $testFile = $this->getCon()->getPluginCachePath( 'test_write_file.txt' );
60
+ $FS->touch( $testFile );
61
 
62
+ if ( $FS->exists( $testFile ) ) {
63
+ $uniq = uniqid();
64
+ $FS->putFileContent( $testFile, $uniq );
65
+ if ( $FS->getFileContent( $testFile ) == $uniq ) {
66
+ $FS->deleteFile( $testFile );
67
+ $canWrite = !$FS->exists( $testFile );
68
  }
69
  }
70
+ return $canWrite;
71
  }
72
 
73
  private function getTestData() :array {
74
+ $data = $this->getOptions()->getOpt( 'cache_dir_write_test' );
75
  return array_merge(
76
  [
77
  'last_test_at' => 0,
78
  'last_success_at' => 0,
79
  ],
80
+ is_array( $data ) ? $data : []
81
  );
82
  }
83
  }
src/lib/src/Modules/Plugin/Lib/TourManager.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib;
4
 
@@ -13,22 +13,20 @@ class TourManager {
13
 
14
  use ModConsumer;
15
 
16
- /**
17
- * @param string $sTourKey
18
- * @return bool
19
- */
20
- public function canShow( $sTourKey ) {
21
- return !Services::WpGeneral()->isMobile() && !$this->isCompleted( $sTourKey );
22
  }
23
 
24
  /**
25
- * @param string $sTourKey
26
  * @return bool
27
  */
28
- public function isCompleted( $sTourKey ) {
29
  try {
30
- $aTrs = $this->getTours();
31
- $shown = isset( $aTrs[ $sTourKey ] ) && $aTrs[ $sTourKey ] > 0;
32
  }
33
  catch ( \Exception $e ) {
34
  $shown = true; // in-case there's a meta saving issue.
@@ -36,34 +34,38 @@ class TourManager {
36
  return $shown;
37
  }
38
 
39
- /**
40
- * @param string $sTourKey
41
- * @return $this
42
- */
43
- public function setCompleted( $sTourKey ) {
44
- $sTourKey = sanitize_key( $sTourKey );
45
- if ( !empty( $sTourKey ) ) {
46
  try {
47
- $aTrs = $this->getTours();
48
- $aTrs[ $sTourKey ] = Services::Request()->ts();
49
- $this->getCon()
50
- ->getCurrentUserMeta()->tours = $aTrs;
51
  }
52
  catch ( \Exception $e ) {
53
  }
54
  }
55
- return $this;
 
 
 
 
 
 
 
 
 
56
  }
57
 
58
  /**
59
  * @return array
60
  * @throws \Exception
61
  */
62
- protected function getTours() {
63
- $oMeta = $this->getCon()->getCurrentUserMeta();
64
- if ( empty( $oMeta ) ) {
65
  throw new \Exception( 'Not logged in or invalid user meta' );
66
  }
67
- return is_array( $oMeta->tours ) ? $oMeta->tours : [];
68
  }
69
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib;
4
 
13
 
14
  use ModConsumer;
15
 
16
+ public function getAllTours() :array {
17
+ return [
18
+ 'dashboard_v1'
19
+ ];
 
 
20
  }
21
 
22
  /**
23
+ * @param string $tourKey
24
  * @return bool
25
  */
26
+ public function isCompleted( string $tourKey ) :bool {
27
  try {
28
+ $tours = $this->loadUserTourStates();
29
+ $shown = isset( $tours[ $tourKey ] ) && $tours[ $tourKey ] > 0;
30
  }
31
  catch ( \Exception $e ) {
32
  $shown = true; // in-case there's a meta saving issue.
34
  return $shown;
35
  }
36
 
37
+ public function setCompleted( string $tourKey ) {
38
+ $tourKey = sanitize_key( $tourKey );
39
+ if ( !empty( $tourKey ) ) {
 
 
 
 
40
  try {
41
+ $tours = $this->loadUserTourStates();
42
+ $tours[ $tourKey ] = Services::Request()->ts();
43
+ $this->getCon()->getCurrentUserMeta()->tours = $tours;
 
44
  }
45
  catch ( \Exception $e ) {
46
  }
47
  }
48
+ }
49
+
50
+ public function getUserTourStates() :array {
51
+ try {
52
+ $tours = $this->loadUserTourStates();
53
+ }
54
+ catch ( \Exception $e ) {
55
+ $tours = [];
56
+ }
57
+ return $tours;
58
  }
59
 
60
  /**
61
  * @return array
62
  * @throws \Exception
63
  */
64
+ private function loadUserTourStates() :array {
65
+ $meta = $this->getCon()->getCurrentUserMeta();
66
+ if ( empty( $meta ) ) {
67
  throw new \Exception( 'Not logged in or invalid user meta' );
68
  }
69
+ return is_array( $meta->tours ) ? $meta->tours : [];
70
  }
71
  }
src/lib/src/Modules/Plugin/ModCon.php CHANGED
@@ -104,16 +104,19 @@ class ModCon extends BaseShield\ModCon {
104
  }
105
  }
106
 
107
- protected function handleModAction( string $action ) {
108
- switch ( $action ) {
109
-
110
- case 'export_file_download':
111
- header( 'Set-Cookie: fileDownload=true; path=/' );
112
  ( new Lib\ImportExport\Export() )
113
  ->setMod( $this )
114
  ->toFile();
115
  break;
 
 
116
 
 
 
 
117
  case 'import_file_upload':
118
  try {
119
  ( new Lib\ImportExport\Import() )
@@ -133,30 +136,42 @@ class ModCon extends BaseShield\ModCon {
133
  break;
134
 
135
  default:
 
136
  break;
137
  }
138
  }
139
 
140
- public function getCanSiteCallToItself() :bool {
141
- $oHttp = Services::HttpRequest();
142
- return $oHttp->get( Services::WpGeneral()->getHomeUrl(), [ 'timeout' => 20 ] )
143
- && $oHttp->lastResponse->getCode() < 400;
 
 
 
 
 
 
 
 
 
 
 
144
  }
145
 
146
  public function getActivePluginFeatures() :array {
147
- $aActiveFeatures = $this->getDef( 'active_plugin_features' );
148
 
149
- $aPluginFeatures = [];
150
- if ( !empty( $aActiveFeatures ) && is_array( $aActiveFeatures ) ) {
151
 
152
- foreach ( $aActiveFeatures as $nPosition => $aFeature ) {
153
- if ( isset( $aFeature[ 'hidden' ] ) && $aFeature[ 'hidden' ] ) {
154
  continue;
155
  }
156
- $aPluginFeatures[ $aFeature[ 'slug' ] ] = $aFeature;
157
  }
158
  }
159
- return $aPluginFeatures;
160
  }
161
 
162
  public function getLinkToTrackingDataDump() :string {
@@ -318,15 +333,6 @@ class ModCon extends BaseShield\ModCon {
318
  return Services::Request()->ts() - (int)$this->getOptions()->getOpt( 'activated_at', 0 );
319
  }
320
 
321
- /**
322
- * hidden 20200121
323
- * @return bool
324
- */
325
- public function getIfShowIntroVideo() :bool {
326
- return false && ( $this->getActivateLength() < 8 )
327
- && ( Services::Request()->ts() - $this->getInstallDate() < 15 );
328
- }
329
-
330
  public function getTourManager() :Lib\TourManager {
331
  return ( new Lib\TourManager() )->setMod( $this );
332
  }
@@ -503,10 +509,15 @@ class ModCon extends BaseShield\ModCon {
503
  ];
504
  }
505
 
 
506
  $locals[] = [
507
- 'plugin',
508
- 'icwp_wpsf_vars_tourmanager',
509
- [ 'ajax' => $this->getAjaxActionData( 'mark_tour_finished' ) ]
 
 
 
 
510
  ];
511
 
512
  $locals[] = [
104
  }
105
  }
106
 
107
+ protected function handleFileDownload( string $downloadID ) {
108
+ switch ( $downloadID ) {
109
+ case 'plugin_export':
 
 
110
  ( new Lib\ImportExport\Export() )
111
  ->setMod( $this )
112
  ->toFile();
113
  break;
114
+ }
115
+ }
116
 
117
+ protected function handleModAction( string $action ) {
118
+
119
+ switch ( $action ) {
120
  case 'import_file_upload':
121
  try {
122
  ( new Lib\ImportExport\Import() )
136
  break;
137
 
138
  default:
139
+ parent::handleModAction( $action );
140
  break;
141
  }
142
  }
143
 
144
+ /**
145
+ * @return bool
146
+ * @throws \Exception
147
+ */
148
+ public function canSiteLoopback() :bool {
149
+ $canLoopback = false;
150
+ if ( class_exists( '\WP_Site_Health' ) && method_exists( '\WP_Site_Health', 'get_instance' ) ) {
151
+ $canLoopback = \WP_Site_Health::get_instance()->get_test_loopback_requests()[ 'status' ] === 'good';
152
+ }
153
+ if ( !$canLoopback ) {
154
+ $canLoopback = Services::HttpRequest()->post( site_url( 'wp-cron.php' ), [
155
+ 'timeout' => 10
156
+ ] );
157
+ }
158
+ return $canLoopback;
159
  }
160
 
161
  public function getActivePluginFeatures() :array {
162
+ $features = $this->getDef( 'active_plugin_features' );
163
 
164
+ $available = [];
165
+ if ( is_array( $features ) ) {
166
 
167
+ foreach ( $features as $feature ) {
168
+ if ( isset( $feature[ 'hidden' ] ) && $feature[ 'hidden' ] ) {
169
  continue;
170
  }
171
+ $available[ $feature[ 'slug' ] ] = $feature;
172
  }
173
  }
174
+ return $available;
175
  }
176
 
177
  public function getLinkToTrackingDataDump() :string {
333
  return Services::Request()->ts() - (int)$this->getOptions()->getOpt( 'activated_at', 0 );
334
  }
335
 
 
 
 
 
 
 
 
 
 
336
  public function getTourManager() :Lib\TourManager {
337
  return ( new Lib\TourManager() )->setMod( $this );
338
  }
509
  ];
510
  }
511
 
512
+ $tourManager = $this->getTourManager();
513
  $locals[] = [
514
+ 'shield/tours',
515
+ 'shield_vars_tourmanager',
516
+ [
517
+ 'ajax' => $this->getAjaxActionData( 'mark_tour_finished' ),
518
+ 'tour_states' => $tourManager->getUserTourStates(),
519
+ 'tours' => $tourManager->getAllTours(),
520
+ ]
521
  ];
522
 
523
  $locals[] = [
src/lib/src/Modules/Plugin/Processor.php CHANGED
@@ -24,7 +24,7 @@ class Processor extends BaseShield\Processor {
24
  $mod->getPluginBadgeCon()->run();
25
 
26
  ( new PluginTelemetry() )
27
- ->setMod( $mod )
28
  ->execute();
29
 
30
  if ( $opts->isImportExportPermitted() ) {
@@ -62,13 +62,13 @@ class Processor extends BaseShield\Processor {
62
  public function runDailyCron() {
63
  $this->getCon()->fireEvent( 'test_cron_run' );
64
 
65
- /** @var Options $oOpts */
66
- $oOpts = $this->getOptions();
67
- if ( $oOpts->isImportExportPermitted() ) {
68
  try {
69
  ( new Lib\ImportExport\Import() )
70
  ->setMod( $this->getMod() )
71
- ->fromSite( $oOpts->getImportExportMasterImportUrl() );
72
  }
73
  catch ( \Exception $e ) {
74
  }
24
  $mod->getPluginBadgeCon()->run();
25
 
26
  ( new PluginTelemetry() )
27
+ ->setMod( $this->getMod() )
28
  ->execute();
29
 
30
  if ( $opts->isImportExportPermitted() ) {
62
  public function runDailyCron() {
63
  $this->getCon()->fireEvent( 'test_cron_run' );
64
 
65
+ /** @var Options $opts */
66
+ $opts = $this->getOptions();
67
+ if ( $opts->isImportExportPermitted() ) {
68
  try {
69
  ( new Lib\ImportExport\Import() )
70
  ->setMod( $this->getMod() )
71
+ ->fromSite( $opts->getImportExportMasterImportUrl() );
72
  }
73
  catch ( \Exception $e ) {
74
  }
src/lib/src/Modules/Plugin/Strings.php CHANGED
@@ -56,6 +56,12 @@ class Strings extends Base\Strings {
56
  'recaptcha_fail' => [
57
  __( 'CAPTCHA Test Fail', 'wp-simple-firewall' )
58
  ],
 
 
 
 
 
 
59
  ];
60
  }
61
 
@@ -72,7 +78,7 @@ class Strings extends Base\Strings {
72
  case 'section_global_security_options' :
73
  $title = __( 'Global Security Plugin Disable', 'wp-simple-firewall' );
74
  $titleShort = sprintf( __( 'Disable %s', 'wp-simple-firewall' ), $sPlugName );
75
- $aSummary = [
76
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Use this option to completely disable all active Shield Protection.', 'wp-simple-firewall' ) ),
77
  ];
78
  break;
@@ -80,18 +86,18 @@ class Strings extends Base\Strings {
80
  case 'section_defaults' :
81
  $title = __( 'Plugin Defaults', 'wp-simple-firewall' );
82
  $titleShort = __( 'Plugin Defaults', 'wp-simple-firewall' );
83
- $aSummary = [
84
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Important default settings used throughout the plugin.', 'wp-simple-firewall' ) ),
85
  ];
86
  break;
87
 
88
  case 'section_importexport' :
89
  $title = sprintf( '%s / %s', __( 'Import', 'wp-simple-firewall' ), __( 'Export', 'wp-simple-firewall' ) );
90
- $aSummary = [
 
91
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Automatically import options, and deploy configurations across your entire network.', 'wp-simple-firewall' ) ),
92
  sprintf( __( 'This is a Pro-only feature.', 'wp-simple-firewall' ) ),
93
  ];
94
- $titleShort = sprintf( '%s / %s', __( 'Import', 'wp-simple-firewall' ), __( 'Export', 'wp-simple-firewall' ) );
95
  break;
96
 
97
  case 'section_suresend' :
@@ -112,7 +118,7 @@ class Strings extends Base\Strings {
112
  case 'section_third_party_captcha' :
113
  $title = __( 'CAPTCHA', 'wp-simple-firewall' );
114
  $titleShort = __( 'CAPTCHA', 'wp-simple-firewall' );
115
- $aSummary = [
116
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), sprintf( __( 'Setup CAPTCHA for use across %s.', 'wp-simple-firewall' ), $sPlugName ) ),
117
  sprintf( '%s - %s',
118
  __( 'Recommendation', 'wp-simple-firewall' ),
@@ -138,7 +144,7 @@ class Strings extends Base\Strings {
138
  return [
139
  'title' => $title,
140
  'title_short' => $titleShort,
141
- 'summary' => ( isset( $aSummary ) && is_array( $aSummary ) ) ? $aSummary : [],
142
  ];
143
  }
144
 
56
  'recaptcha_fail' => [
57
  __( 'CAPTCHA Test Fail', 'wp-simple-firewall' )
58
  ],
59
+ 'antibot_pass' => [
60
+ __( 'Request passed the AntiBot Test with a Visitor Score of "%s" (minimum score: %s).', 'wp-simple-firewall' ),
61
+ ],
62
+ 'antibot_fail' => [
63
+ __( 'Request failed the AntiBot Test with a Visitor Score of "%s" (minimum score: %s).', 'wp-simple-firewall' ),
64
+ ],
65
  ];
66
  }
67
 
78
  case 'section_global_security_options' :
79
  $title = __( 'Global Security Plugin Disable', 'wp-simple-firewall' );
80
  $titleShort = sprintf( __( 'Disable %s', 'wp-simple-firewall' ), $sPlugName );
81
+ $summary = [
82
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Use this option to completely disable all active Shield Protection.', 'wp-simple-firewall' ) ),
83
  ];
84
  break;
86
  case 'section_defaults' :
87
  $title = __( 'Plugin Defaults', 'wp-simple-firewall' );
88
  $titleShort = __( 'Plugin Defaults', 'wp-simple-firewall' );
89
+ $summary = [
90
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Important default settings used throughout the plugin.', 'wp-simple-firewall' ) ),
91
  ];
92
  break;
93
 
94
  case 'section_importexport' :
95
  $title = sprintf( '%s / %s', __( 'Import', 'wp-simple-firewall' ), __( 'Export', 'wp-simple-firewall' ) );
96
+ $titleShort = sprintf( '%s / %s', __( 'Import', 'wp-simple-firewall' ), __( 'Export', 'wp-simple-firewall' ) );
97
+ $summary = [
98
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Automatically import options, and deploy configurations across your entire network.', 'wp-simple-firewall' ) ),
99
  sprintf( __( 'This is a Pro-only feature.', 'wp-simple-firewall' ) ),
100
  ];
 
101
  break;
102
 
103
  case 'section_suresend' :
118
  case 'section_third_party_captcha' :
119
  $title = __( 'CAPTCHA', 'wp-simple-firewall' );
120
  $titleShort = __( 'CAPTCHA', 'wp-simple-firewall' );
121
+ $summary = [
122
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), sprintf( __( 'Setup CAPTCHA for use across %s.', 'wp-simple-firewall' ), $sPlugName ) ),
123
  sprintf( '%s - %s',
124
  __( 'Recommendation', 'wp-simple-firewall' ),
144
  return [
145
  'title' => $title,
146
  'title_short' => $titleShort,
147
+ 'summary' => ( isset( $summary ) && is_array( $summary ) ) ? $summary : [],
148
  ];
149
  }
150
 
src/lib/src/Modules/Plugin/WpCli/Reset.php CHANGED
@@ -27,8 +27,8 @@ class Reset extends Base\WpCli\BaseWpCliCmd {
27
  ] ) );
28
  }
29
 
30
- public function cmdReset( $null, $aA ) {
31
- if ( !$this->isForceFlag( $aA ) ) {
32
  WP_CLI::confirm( __( 'Are you sure you want to reset the Shield plugin to defaults?', 'wp-simple-firewall' ) );
33
  }
34
  ( new ResetPlugin() )
27
  ] ) );
28
  }
29
 
30
+ public function cmdReset( $null, $args ) {
31
+ if ( !$this->isForceFlag( $args ) ) {
32
  WP_CLI::confirm( __( 'Are you sure you want to reset the Shield plugin to defaults?', 'wp-simple-firewall' ) );
33
  }
34
  ( new ResetPlugin() )
src/lib/src/Modules/Plugin/WpCli/ToggleDebug.php CHANGED
@@ -32,10 +32,10 @@ class ToggleDebug extends BaseWpCliCmd {
32
  ] ) );
33
  }
34
 
35
- public function cmdDebugMode( $null, $aA ) {
36
  $debugMode = ( new DebugMode() )->setCon( $this->getCon() );
37
 
38
- switch ( $aA[ 'action' ] ) {
39
  case 'query':
40
  if ( $debugMode->isActiveViaDefine() ) {
41
  WP_CLI::log( 'Debug mode is active using PHP constant.' );
32
  ] ) );
33
  }
34
 
35
+ public function cmdDebugMode( $null, $args ) {
36
  $debugMode = ( new DebugMode() )->setCon( $this->getCon() );
37
 
38
+ switch ( $args[ 'action' ] ) {
39
  case 'query':
40
  if ( $debugMode->isActiveViaDefine() ) {
41
  WP_CLI::log( 'Debug mode is active using PHP constant.' );
src/lib/src/Modules/Reporting/AjaxHandler.php ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+
7
+ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
8
+
9
+ protected function processAjaxAction( string $action ) :array {
10
+
11
+ switch ( $action ) {
12
+ case 'render_custom_chart':
13
+ $response = $this->ajaxExec_RenderCustomChart();
14
+ break;
15
+
16
+ case 'render_summary_chart':
17
+ $response = $this->ajaxExec_RenderSummaryChart();
18
+ break;
19
+
20
+ default:
21
+ $response = parent::processAjaxAction( $action );
22
+ }
23
+
24
+ return $response;
25
+ }
26
+
27
+ private function ajaxExec_RenderCustomChart() :array {
28
+ return $this->renderChart( $this->getAjaxFormParams() );
29
+ }
30
+
31
+ private function ajaxExec_RenderSummaryChart() :array {
32
+ return $this->renderChart( $_POST );
33
+ }
34
+
35
+ /**
36
+ * @param Shield\Modules\Reporting\Charts\ChartRequestVO $req
37
+ * @return array
38
+ */
39
+ private function renderChart( array $data ) :array {
40
+ /** @var ModCon $mod */
41
+ $mod = $this->getMod();
42
+ try {
43
+ $chartData = ( new Charts\CustomChartData() )
44
+ ->setMod( $mod )
45
+ ->setChartRequest( ( new Charts\CustomChartRequestVO() )->applyFromArray( $data ) )
46
+ ->build();
47
+ $msg = 'No message';
48
+ $success = true;
49
+ }
50
+ catch ( \Exception $e ) {
51
+ $msg = sprintf( '%s: %s', __( 'Error', 'wp-simple-firewall' ), $e->getMessage() );
52
+ $success = false;
53
+ $chartData = [];
54
+ }
55
+
56
+ return [
57
+ 'success' => $success,
58
+ 'message' => $msg,
59
+ 'chart' => $chartData
60
+ ];
61
+ }
62
+ }
src/lib/src/Modules/Reporting/Charts/BaseBuildChartData.php ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Charts;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases\Events;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Events\Strings;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
+ use FernleafSystems\Wordpress\Services\Services;
9
+
10
+ class BaseBuildChartData {
11
+
12
+ const LOCATION_SUMMARYCARD = 'insights-overview-statcard';
13
+ use ModConsumer;
14
+ use ChartRequestConsumer;
15
+
16
+ /**
17
+ * @var array
18
+ */
19
+ protected $labels;
20
+
21
+ /**
22
+ * @return array
23
+ * @throws \InvalidArgumentException
24
+ */
25
+ public function build() :array {
26
+ $req = $this->getChartRequest();
27
+ $this->preProcessRequest();
28
+
29
+ $allSeries = [];
30
+ if ( $req->combine_events ) {
31
+ $allSeries[] = $this->buildDataForEvents( $req->events );
32
+ }
33
+ else {
34
+ foreach ( $req->events as $event ) {
35
+ $allSeries[] = $this->buildDataForEvents( [ $event ] );
36
+ }
37
+ }
38
+
39
+ return [
40
+ 'title' => $this->buildTitle(),
41
+ 'legend' => $this->buildLegend(),
42
+ 'data' => [
43
+ 'labels' => $this->labels,
44
+ 'series' => $allSeries,
45
+ ],
46
+ ];
47
+ }
48
+
49
+ protected function buildLegend() :array {
50
+ $req = $this->getChartRequest();
51
+ $legend = [];
52
+ if ( !$req->combine_events ) {
53
+ /** @var Strings $strings */
54
+ $strings = $this->getCon()->getModule_Events()->getStrings();
55
+ foreach ( $req->events as $event ) {
56
+ $legend[] = $strings->getEventName( $event );
57
+ }
58
+ }
59
+ return $legend;
60
+ }
61
+
62
+ protected function buildTitle() :string {
63
+ $req = $this->getChartRequest();
64
+ switch ( $req->interval ) {
65
+ case 'hourly':
66
+ $counter = 'hours';
67
+ break;
68
+ case 'weekly':
69
+ $counter = 'weeks';
70
+ break;
71
+ case 'monthly':
72
+ $counter = 'months';
73
+ break;
74
+ case 'yearly':
75
+ $counter = 'years';
76
+ break;
77
+ default:
78
+ case 'daily':
79
+ $counter = 'days';
80
+ break;
81
+ }
82
+ return sprintf( 'Chart showing events from the past %s %s', $req->ticks, $counter );
83
+ }
84
+
85
+ protected function buildDataForEvents( array $events ) :array {
86
+ $req = $this->getChartRequest();
87
+ $dbhEvents = $this->getCon()->getModule_Events()->getDbHandler_Events();
88
+
89
+ $tick = 0;
90
+ $carbon = Services::Request()->carbon();
91
+
92
+ $labels = [];
93
+ $dataSeries = [];
94
+ do {
95
+
96
+ /** @var Events\Select $eventSelect */
97
+ $eventSelect = $dbhEvents->getQuerySelector();
98
+ switch ( $req->interval ) {
99
+ case 'hourly':
100
+ $eventSelect->filterByBoundary_Hour( $carbon->timestamp );
101
+ $labels[] = $carbon->format( 'H' );
102
+ $carbon->subHour();
103
+ break;
104
+ case 'daily':
105
+ $eventSelect->filterByBoundary_Day( $carbon->timestamp );
106
+ $labels[] = $carbon->format( 'Y-m-d' );
107
+ $carbon->subDay();
108
+ break;
109
+ case 'weekly':
110
+ $eventSelect->filterByBoundary_Week( $carbon->timestamp );
111
+ $labels[] = ( clone $carbon )->startOfWeek()->format( 'Y-m-d' );
112
+ $carbon->subWeek();
113
+ break;
114
+ case 'monthly':
115
+ $eventSelect->filterByBoundary_Month( $carbon->timestamp );
116
+ $labels[] = $carbon->format( 'Y-m' );
117
+ $carbon->subMonth();
118
+ break;
119
+ case 'yearly':
120
+ $eventSelect->filterByBoundary_Year( $carbon->timestamp );
121
+ $labels[] = $carbon->format( 'Y' );
122
+ $carbon->subYear();
123
+ break;
124
+ }
125
+
126
+ $dataSeries[] = $eventSelect->sumEvents( $events );
127
+
128
+ $tick++;
129
+ } while ( $tick < $req->ticks );
130
+
131
+ if ( empty( $this->labels ) ) {
132
+ $this->labels = $labels;
133
+ }
134
+
135
+ return $dataSeries;
136
+ }
137
+
138
+ /**
139
+ * @throws \InvalidArgumentException
140
+ */
141
+ protected function preProcessRequest() {
142
+ $req = $this->getChartRequest();
143
+
144
+ if ( empty( $req->events ) ) {
145
+ throw new \InvalidArgumentException( 'No events selected - please select at least 1 event.' );
146
+ }
147
+
148
+ if ( empty( $req->ticks ) ) {
149
+ switch ( $req->interval ) {
150
+ case 'daily':
151
+ $req->ticks = 7;
152
+ break;
153
+ case 'weekly':
154
+ $req->ticks = 8;
155
+ break;
156
+ default:
157
+ $req->ticks = 12;
158
+ break;
159
+ }
160
+ }
161
+ }
162
+ }
src/lib/src/Modules/Reporting/Charts/ChartRequestConsumer.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Charts;
4
+
5
+ trait ChartRequestConsumer {
6
+
7
+ /**
8
+ * @var ChartRequestVO
9
+ */
10
+ protected $chartReq;
11
+
12
+ /**
13
+ * @return ChartRequestVO
14
+ */
15
+ public function getChartRequest() {
16
+ return $this->chartReq;
17
+ }
18
+
19
+ /**
20
+ * @param ChartRequestVO $req
21
+ * @return $this
22
+ */
23
+ public function setChartRequest( $req ) {
24
+ $this->chartReq = $req;
25
+ return $this;
26
+ }
27
+ }
src/lib/src/Modules/Reporting/Charts/ChartRequestVO.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Charts;
4
+
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
+
7
+ /**
8
+ * Class ChartRequestVO
9
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Charts
10
+ * @property string $interval
11
+ * @property string $ticks
12
+ * @property string[] $events
13
+ * @property bool $combine_events
14
+ * @property array $chart_params
15
+ */
16
+ class ChartRequestVO extends DynPropertiesClass {
17
+
18
+ /**
19
+ * @param string $key
20
+ * @return mixed
21
+ */
22
+ public function __get( string $key ) {
23
+ $value = parent::__get( $key );
24
+ switch ( $key ) {
25
+ case 'interval':
26
+ if ( empty( $value ) ) {
27
+ $value = 'weekly';
28
+ }
29
+ break;
30
+ default:
31
+ break;
32
+ }
33
+ return $value;
34
+ }
35
+ }
src/lib/src/Modules/Reporting/Charts/CustomChartData.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Charts;
4
+
5
+ class CustomChartData extends BaseBuildChartData {
6
+
7
+ /**
8
+ * @inheritDoc
9
+ */
10
+ protected function preProcessRequest() {
11
+ parent::preProcessRequest();
12
+
13
+ /** @var CustomChartRequestVO $req */
14
+ $req = $this->getChartRequest();
15
+
16
+ if ( $req->render_location === static::LOCATION_SUMMARYCARD ) {
17
+ $req->interval = 'daily';
18
+ }
19
+
20
+ $theEvent = current( $req->events );
21
+ $possibleEvents = array_keys( $this->getCon()->loadEventsService()->getEvents() );
22
+ switch ( $theEvent ) {
23
+ case 'comment_block':
24
+ $req->events = array_filter(
25
+ $possibleEvents,
26
+ function ( $event ) {
27
+ return strpos( $event, 'spam_block_' ) === 0;
28
+ }
29
+ );
30
+ break;
31
+ case 'bot_blocks':
32
+ $req->events = array_filter(
33
+ $possibleEvents,
34
+ function ( $event ) {
35
+ return strpos( $event, 'bottrack_' ) === 0;
36
+ }
37
+ );
38
+ break;
39
+ default:
40
+ break;
41
+ }
42
+ }
43
+ }
src/lib/src/Modules/Reporting/Charts/CustomChartRequestVO.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Charts;
4
+
5
+ /**
6
+ * Class CustomChartRequestVO
7
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Charts
8
+ * @property string $render_location
9
+ */
10
+ class CustomChartRequestVO extends ChartRequestVO {
11
+
12
+ }
src/lib/src/Modules/Reporting/Lib/ReportingController.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Lib;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Crons\PluginCronsConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Reports as DBReports;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
@@ -12,13 +12,10 @@ use FernleafSystems\Wordpress\Services\Services;
12
  class ReportingController {
13
 
14
  use Modules\ModConsumer;
15
- use OneTimeExecute;
16
  use PluginCronsConsumer;
17
 
18
- /**
19
- * @return bool
20
- */
21
- protected function canRun() {
22
  /** @var Modules\Reporting\Options $opts */
23
  $opts = $this->getOptions();
24
  return $opts->getFrequencyInfo() !== 'disabled' || $opts->getFrequencyAlert() !== 'disabled';
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Lib;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Crons\PluginCronsConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Reports as DBReports;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
12
  class ReportingController {
13
 
14
  use Modules\ModConsumer;
15
+ use ExecOnce;
16
  use PluginCronsConsumer;
17
 
18
+ protected function canRun() :bool {
 
 
 
19
  /** @var Modules\Reporting\Options $opts */
20
  $opts = $this->getOptions();
21
  return $opts->getFrequencyInfo() !== 'disabled' || $opts->getFrequencyAlert() !== 'disabled';
src/lib/src/Modules/Reporting/Lib/Reports/ReportVO.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Lib\Reports;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Reports\EntryVO;
7
 
8
  /**
@@ -18,5 +18,5 @@ use FernleafSystems\Wordpress\Plugin\Shield\Databases\Reports\EntryVO;
18
  */
19
  class ReportVO {
20
 
21
- use StdClassAdapter;
22
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Lib\Reports;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynProperties;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Reports\EntryVO;
7
 
8
  /**
18
  */
19
  class ReportVO {
20
 
21
+ use DynProperties;
22
  }
src/lib/src/Modules/Reporting/UI.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting;
4
 
@@ -8,89 +8,138 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Events;
8
 
9
  class UI extends BaseShield\UI {
10
 
11
- public function renderSummaryStats() :string {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  $con = $this->getCon();
13
- /** @var Databases\Events\Select $oSelEvents */
14
- $oSelEvents = $con->getModule_Events()
15
- ->getDbHandler_Events()
16
- ->getQuerySelector();
17
 
18
  /** @var Databases\IPs\Select $oSelectIp */
19
  $oSelectIp = $con->getModule_IPs()
20
  ->getDbHandler_IPs()
21
  ->getQuerySelector();
22
 
23
- $aStatsData = [
24
  'login' => [
25
  'id' => 'login_block',
26
  'title' => __( 'Login Blocks', 'wp-simple-firewall' ),
27
  'val' => sprintf( '%s: %s', __( 'Lifetime Total' ),
28
- $oSelEvents->clearWheres()->sumEvent( 'login_block' ) ),
29
  'tooltip_p' => __( 'Total login attempts blocked.', 'wp-simple-firewall' ),
30
  ],
31
  // 'firewall' => [
32
  // 'id' => 'firewall_block',
33
  // 'title' => __( 'Firewall Blocks', 'wp-simple-firewall' ),
34
- // 'val' => $oSelEvents->clearWheres()->sumEvent( 'firewall_block' ),
35
  // 'tooltip' => __( 'Total requests blocked by firewall rules.', 'wp-simple-firewall' )
36
  // ],
37
  'bot_blocks' => [
38
  'id' => 'bot_blocks',
39
  'title' => __( 'Bot Detection', 'wp-simple-firewall' ),
40
  'val' => sprintf( '%s: %s', __( 'Lifetime Total' ),
41
- $oSelEvents->clearWheres()->sumEventsLike( 'bottrack_' ) ),
42
  'tooltip_p' => __( 'Total requests identified as bots.', 'wp-simple-firewall' ),
43
  ],
44
  'comments' => [
45
  'id' => 'comment_block',
46
  'title' => __( 'Comment Blocks', 'wp-simple-firewall' ),
47
  'val' => sprintf( '%s: %s', __( 'Lifetime Total' ),
48
- $oSelEvents->clearWheres()->sumEvents( [
49
  'spam_block_bot',
50
  'spam_block_human',
51
  'spam_block_recaptcha'
52
- ] ) ),
53
  'tooltip_p' => __( 'Total SPAM comments blocked.', 'wp-simple-firewall' ),
54
  ],
55
  'transgressions' => [
56
  'id' => 'ip_offense',
57
  'title' => __( 'Offenses', 'wp-simple-firewall' ),
58
  'val' => sprintf( '%s: %s', __( 'Lifetime Total' ),
59
- $oSelEvents->clearWheres()->sumEvent( 'ip_offense' ) ),
60
  'tooltip_p' => __( 'Total offenses against the site.', 'wp-simple-firewall' ),
61
  ],
62
  'conn_kills' => [
63
  'id' => 'conn_kill',
64
  'title' => __( 'Connection Killed', 'wp-simple-firewall' ),
65
  'val' => sprintf( '%s: %s', __( 'Lifetime Total' ),
66
- $oSelEvents->clearWheres()->sumEvent( 'conn_kill' ) ),
67
  'tooltip_p' => __( 'Total connections blocked/killed after too many offenses.', 'wp-simple-firewall' ),
68
  ],
69
  'ip_blocked' => [
70
  'id' => 'ip_blocked',
71
  'title' => __( 'IP Blocked', 'wp-simple-firewall' ),
72
  'val' => sprintf( '%s: %s', __( 'Now' ),
73
- $oSelectIp->filterByBlacklist()->count()
74
  ),
75
  'tooltip_p' => __( 'IP address exceeds offense limit and is blocked.', 'wp-simple-firewall' ),
76
  ],
77
  ];
78
 
79
- foreach ( $aStatsData as $sKey => $sStatData ) {
80
- $sSub = sprintf( __( 'previous %s %s', 'wp-simple-firewall' ), 7, __( 'days', 'wp-simple-firewall' ) );
81
- $aStatsData[ $sKey ][ 'title_sub' ] = $sSub;
82
- $aStatsData[ $sKey ][ 'tooltip_chart' ] = sprintf( '%s: %s.', __( 'Stats', 'wp-simple-firewall' ), $sSub );
83
  }
84
 
 
 
85
  return $this->getMod()
86
  ->renderTemplate(
87
- '/wpadmin_pages/insights/reports/summary_stats.twig',
88
  [
89
- 'ajax' => [
90
- 'render_chart_post' => $con->getModule_Events()->getAjaxActionData( 'render_chart_post', true ),
91
  ],
92
- 'vars' => [
93
- 'stats' => $aStatsData,
94
  ],
95
  ],
96
  true
@@ -98,33 +147,13 @@ class UI extends BaseShield\UI {
98
  }
99
 
100
  public function buildInsightsVars() :array {
101
- $oEvtsMod = $this->getCon()->getModule_Events();
102
- /** @var Events\Strings $oStrs */
103
- $oStrs = $oEvtsMod->getStrings();
104
- $aEvtNames = $oStrs->getEventNames();
105
-
106
  return [
107
- 'ajax' => [
108
- 'render_chart' => $oEvtsMod->getAjaxActionData( 'render_chart', true ),
109
- ],
110
  'content' => [
111
- 'summary_stats' => $this->renderSummaryStats(),
 
112
  ],
113
  'flags' => [],
114
- 'strings' => [
115
- ],
116
- 'vars' => [
117
- 'events_options' => array_intersect_key(
118
- $aEvtNames,
119
- array_flip(
120
- [
121
- 'ip_offense',
122
- 'conn_kill',
123
- 'firewall_block',
124
- ]
125
- )
126
- )
127
- ],
128
  ];
129
  }
130
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting;
4
 
8
 
9
  class UI extends BaseShield\UI {
10
 
11
+ public function renderSectionCustomChart() :string {
12
+ /** @var ModCon $mod */
13
+ $mod = $this->getMod();
14
+ return $this->getMod()
15
+ ->renderTemplate(
16
+ '/wpadmin_pages/insights/reports/charts_custom.twig',
17
+ [
18
+ 'ajax' => [
19
+ 'render_custom_chart' => $mod->getAjaxActionData( 'render_custom_chart', true ),
20
+ ],
21
+ 'strings' => [
22
+ 'select_events' => __( 'Events', 'wp-simple-firewall' ),
23
+ 'select_interval' => __( 'Interval', 'wp-simple-firewall' ),
24
+ 'build_chart' => __( 'Build Chart', 'wp-simple-firewall' ),
25
+ ],
26
+ 'vars' => [
27
+ 'events' => $this->buildPossibleEvents(),
28
+ 'interval' => [
29
+ 'hourly' => __( 'Hourly', 'wp-simple-firewall' ),
30
+ 'daily' => __( 'Daily', 'wp-simple-firewall' ),
31
+ 'weekly' => __( 'Weekly', 'wp-simple-firewall' ),
32
+ 'monthly' => __( 'Monthly', 'wp-simple-firewall' ),
33
+ 'yearly' => __( 'Yearly', 'wp-simple-firewall' ),
34
+ ],
35
+ ],
36
+ ],
37
+ true
38
+ );
39
+ }
40
+
41
+ /**
42
+ * Finds all available events logged in the DB and intersects this with all available Event names
43
+ * i.e. so you can only build charts of events with actual records
44
+ * @return array
45
+ */
46
+ private function buildPossibleEvents() :array {
47
+ $eventsMod = $this->getCon()->getModule_Events();
48
+ /** @var Events\Strings $strings */
49
+ $strings = $eventsMod->getStrings();
50
+ return array_intersect_key(
51
+ $strings->getEventNames(),
52
+ array_flip( $eventsMod->getDbHandler_Events()
53
+ ->getQuerySelector()
54
+ ->getDistinctForColumn( 'event' ) )
55
+ );
56
+ }
57
+
58
+ private function renderSectionSummaryStats() :string {
59
  $con = $this->getCon();
60
+ /** @var Databases\Events\Select $eventSelector */
61
+ $eventSelector = $con->getModule_Events()
62
+ ->getDbHandler_Events()
63
+ ->getQuerySelector();
64
 
65
  /** @var Databases\IPs\Select $oSelectIp */
66
  $oSelectIp = $con->getModule_IPs()
67
  ->getDbHandler_IPs()
68
  ->getQuerySelector();
69
 
70
+ $statsData = [
71
  'login' => [
72
  'id' => 'login_block',
73
  'title' => __( 'Login Blocks', 'wp-simple-firewall' ),
74
  'val' => sprintf( '%s: %s', __( 'Lifetime Total' ),
75
+ number_format( $eventSelector->clearWheres()->sumEvent( 'login_block' ) ) ),
76
  'tooltip_p' => __( 'Total login attempts blocked.', 'wp-simple-firewall' ),
77
  ],
78
  // 'firewall' => [
79
  // 'id' => 'firewall_block',
80
  // 'title' => __( 'Firewall Blocks', 'wp-simple-firewall' ),
81
+ // 'val' => $eventSelector->clearWheres()->sumEvent( 'firewall_block' ),
82
  // 'tooltip' => __( 'Total requests blocked by firewall rules.', 'wp-simple-firewall' )
83
  // ],
84
  'bot_blocks' => [
85
  'id' => 'bot_blocks',
86
  'title' => __( 'Bot Detection', 'wp-simple-firewall' ),
87
  'val' => sprintf( '%s: %s', __( 'Lifetime Total' ),
88
+ number_format( $eventSelector->clearWheres()->sumEventsLike( 'bottrack_' ) ) ),
89
  'tooltip_p' => __( 'Total requests identified as bots.', 'wp-simple-firewall' ),
90
  ],
91
  'comments' => [
92
  'id' => 'comment_block',
93
  'title' => __( 'Comment Blocks', 'wp-simple-firewall' ),
94
  'val' => sprintf( '%s: %s', __( 'Lifetime Total' ),
95
+ number_format( $eventSelector->clearWheres()->sumEvents( [
96
  'spam_block_bot',
97
  'spam_block_human',
98
  'spam_block_recaptcha'
99
+ ] ) ) ),
100
  'tooltip_p' => __( 'Total SPAM comments blocked.', 'wp-simple-firewall' ),
101
  ],
102
  'transgressions' => [
103
  'id' => 'ip_offense',
104
  'title' => __( 'Offenses', 'wp-simple-firewall' ),
105
  'val' => sprintf( '%s: %s', __( 'Lifetime Total' ),
106
+ number_format( $eventSelector->clearWheres()->sumEvent( 'ip_offense' ) ) ),
107
  'tooltip_p' => __( 'Total offenses against the site.', 'wp-simple-firewall' ),
108
  ],
109
  'conn_kills' => [
110
  'id' => 'conn_kill',
111
  'title' => __( 'Connection Killed', 'wp-simple-firewall' ),
112
  'val' => sprintf( '%s: %s', __( 'Lifetime Total' ),
113
+ number_format( $eventSelector->clearWheres()->sumEvent( 'conn_kill' ) ) ),
114
  'tooltip_p' => __( 'Total connections blocked/killed after too many offenses.', 'wp-simple-firewall' ),
115
  ],
116
  'ip_blocked' => [
117
  'id' => 'ip_blocked',
118
  'title' => __( 'IP Blocked', 'wp-simple-firewall' ),
119
  'val' => sprintf( '%s: %s', __( 'Now' ),
120
+ number_format( $oSelectIp->filterByBlacklist()->count() )
121
  ),
122
  'tooltip_p' => __( 'IP address exceeds offense limit and is blocked.', 'wp-simple-firewall' ),
123
  ],
124
  ];
125
 
126
+ foreach ( $statsData as $key => $statData ) {
127
+ $subtitle = sprintf( __( 'previous %s %s', 'wp-simple-firewall' ), 7, __( 'days', 'wp-simple-firewall' ) );
128
+ $statsData[ $key ][ 'title_sub' ] = $subtitle;
129
+ $statsData[ $key ][ 'tooltip_chart' ] = sprintf( '%s: %s.', __( 'Stats', 'wp-simple-firewall' ), $subtitle );
130
  }
131
 
132
+ /** @var ModCon $mod */
133
+ $mod = $this->getMod();
134
  return $this->getMod()
135
  ->renderTemplate(
136
+ '/wpadmin_pages/insights/reports/charts_summary.twig',
137
  [
138
+ 'ajax' => [
139
+ 'render_summary_chart' => $mod->getAjaxActionData( 'render_summary_chart', true ),
140
  ],
141
+ 'vars' => [
142
+ 'stats' => $statsData,
143
  ],
144
  ],
145
  true
147
  }
148
 
149
  public function buildInsightsVars() :array {
 
 
 
 
 
150
  return [
 
 
 
151
  'content' => [
152
+ 'summary_stats' => $this->renderSectionSummaryStats(),
153
+ 'custom_chart' => $this->renderSectionCustomChart(),
154
  ],
155
  'flags' => [],
156
+ 'strings' => [],
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  ];
158
  }
159
  }
src/lib/src/Modules/SecurityAdmin/AjaxHandler.php CHANGED
@@ -34,10 +34,7 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
34
  return $aResponse;
35
  }
36
 
37
- /**
38
- * @return array
39
- */
40
- private function ajaxExec_SecAdminCheck() {
41
  /** @var ModCon $mod */
42
  $mod = $this->getMod();
43
  return [
@@ -49,71 +46,61 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
49
  private function ajaxExec_SecAdminLogin() :array {
50
  /** @var ModCon $mod */
51
  $mod = $this->getMod();
52
- $bSuccess = false;
53
- $sHtml = '';
54
 
55
  if ( $mod->testSecAccessKeyRequest() ) {
56
 
57
  if ( $mod->setSecurityAdminStatusOnOff( true ) ) {
58
- $bSuccess = true;
59
- $sMsg = __( 'Security Admin PIN Accepted.', 'wp-simple-firewall' )
60
- .' '.__( 'Please wait', 'wp-simple-firewall' ).' ...';
61
  }
62
  else {
63
- $sMsg = __( 'Failed to process key - you may need to re-login to WordPress.', 'wp-simple-firewall' );
64
  }
65
  }
66
  else {
67
- $nRemaining = ( new Shield\Modules\IPs\Components\QueryRemainingOffenses() )
68
  ->setMod( $this->getCon()->getModule_IPs() )
69
  ->setIP( Services::IP()->getRequestIp() )
70
  ->run();
71
- $sMsg = __( 'Security Admin PIN incorrect.', 'wp-simple-firewall' ).' ';
72
- if ( $nRemaining > 0 ) {
73
- $sMsg .= sprintf( __( 'Attempts remaining: %s.', 'wp-simple-firewall' ), $nRemaining );
74
  }
75
  else {
76
- $sMsg .= __( "No attempts remaining.", 'wp-simple-firewall' );
77
  }
78
- $sHtml = $this->renderAdminAccessAjaxLoginForm( $sMsg );
79
  }
80
 
81
  return [
82
- 'success' => $bSuccess,
83
  'page_reload' => true,
84
- 'message' => $sMsg,
85
- 'html' => $sHtml,
86
  ];
87
  }
88
 
89
- /**
90
- * @return array
91
- */
92
- private function ajaxExec_SecAdminLoginBox() {
93
  return [
94
- 'success' => 'true',
95
  'html' => $this->renderAdminAccessAjaxLoginForm()
96
  ];
97
  }
98
 
99
- /**
100
- * @return array
101
- */
102
- private function ajaxExec_SendEmailRemove() {
103
  ( new Shield\Modules\SecurityAdmin\Lib\Actions\RemoveSecAdmin() )
104
  ->setMod( $this->getMod() )
105
  ->sendConfirmationEmail();
106
  return [
107
- 'success' => 'true',
108
  'message' => __( 'Email sent. Please ensure the confirmation link opens in THIS browser window.' ),
109
  ];
110
  }
111
 
112
- /**
113
- * @param string $sMessage
114
- * @return string
115
- */
116
- private function renderAdminAccessAjaxLoginForm( $sMessage = '' ) {
117
  /** @var ModCon $mod */
118
  $mod = $this->getMod();
119
  return $mod->renderTemplate( 'snippets/admin_access_login', [
@@ -121,7 +108,7 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
121
  'sec_admin_login' => json_encode( $mod->getSecAdminLoginAjaxData() )
122
  ],
123
  'strings' => [
124
- 'access_message' => empty( $sMessage ) ? __( 'Enter your Security Admin PIN', 'wp-simple-firewall' ) : $sMessage
125
  ]
126
  ] );
127
  }
34
  return $aResponse;
35
  }
36
 
37
+ private function ajaxExec_SecAdminCheck() :array {
 
 
 
38
  /** @var ModCon $mod */
39
  $mod = $this->getMod();
40
  return [
46
  private function ajaxExec_SecAdminLogin() :array {
47
  /** @var ModCon $mod */
48
  $mod = $this->getMod();
49
+ $success = false;
50
+ $html = '';
51
 
52
  if ( $mod->testSecAccessKeyRequest() ) {
53
 
54
  if ( $mod->setSecurityAdminStatusOnOff( true ) ) {
55
+ $success = true;
56
+ $msg = __( 'Security Admin PIN Accepted.', 'wp-simple-firewall' )
57
+ .' '.__( 'Please wait', 'wp-simple-firewall' ).' ...';
58
  }
59
  else {
60
+ $msg = __( 'Failed to process key - you may need to re-login to WordPress.', 'wp-simple-firewall' );
61
  }
62
  }
63
  else {
64
+ $remaining = ( new Shield\Modules\IPs\Components\QueryRemainingOffenses() )
65
  ->setMod( $this->getCon()->getModule_IPs() )
66
  ->setIP( Services::IP()->getRequestIp() )
67
  ->run();
68
+ $msg = __( 'Security Admin PIN incorrect.', 'wp-simple-firewall' ).' ';
69
+ if ( $remaining > 0 ) {
70
+ $msg .= sprintf( __( 'Attempts remaining: %s.', 'wp-simple-firewall' ), $remaining );
71
  }
72
  else {
73
+ $msg .= __( "No attempts remaining.", 'wp-simple-firewall' );
74
  }
75
+ $html = $this->renderAdminAccessAjaxLoginForm( $msg );
76
  }
77
 
78
  return [
79
+ 'success' => $success,
80
  'page_reload' => true,
81
+ 'message' => $msg,
82
+ 'html' => $html,
83
  ];
84
  }
85
 
86
+ private function ajaxExec_SecAdminLoginBox() :array {
 
 
 
87
  return [
88
+ 'success' => true,
89
  'html' => $this->renderAdminAccessAjaxLoginForm()
90
  ];
91
  }
92
 
93
+ private function ajaxExec_SendEmailRemove() :array {
 
 
 
94
  ( new Shield\Modules\SecurityAdmin\Lib\Actions\RemoveSecAdmin() )
95
  ->setMod( $this->getMod() )
96
  ->sendConfirmationEmail();
97
  return [
98
+ 'success' => true,
99
  'message' => __( 'Email sent. Please ensure the confirmation link opens in THIS browser window.' ),
100
  ];
101
  }
102
 
103
+ private function renderAdminAccessAjaxLoginForm( string $msg = '' ) :string {
 
 
 
 
104
  /** @var ModCon $mod */
105
  $mod = $this->getMod();
106
  return $mod->renderTemplate( 'snippets/admin_access_login', [
108
  'sec_admin_login' => json_encode( $mod->getSecAdminLoginAjaxData() )
109
  ],
110
  'strings' => [
111
+ 'access_message' => empty( $msg ) ? __( 'Enter your Security Admin PIN', 'wp-simple-firewall' ) : $msg
112
  ]
113
  ] );
114
  }
src/lib/src/Modules/SecurityAdmin/Lib/WhiteLabel/ApplyLabels.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin\Lib\WhiteLabel;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin;
8
  use FernleafSystems\Wordpress\Services\Services;
@@ -10,9 +10,9 @@ use FernleafSystems\Wordpress\Services\Services;
10
  class ApplyLabels {
11
 
12
  use ModConsumer;
13
- use OneTimeExecute;
14
 
15
- protected function canRun() {
16
  /** @var SecurityAdmin\Options $opts */
17
  $opts = $this->getOptions();
18
  return $opts->isEnabledWhitelabel();
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin\Lib\WhiteLabel;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin;
8
  use FernleafSystems\Wordpress\Services\Services;
10
  class ApplyLabels {
11
 
12
  use ModConsumer;
13
+ use ExecOnce;
14
 
15
+ protected function canRun() :bool {
16
  /** @var SecurityAdmin\Options $opts */
17
  $opts = $this->getOptions();
18
  return $opts->isEnabledWhitelabel();
src/lib/src/Modules/SecurityAdmin/ModCon.php CHANGED
@@ -32,6 +32,10 @@ class ModCon extends BaseShield\ModCon {
32
  return $this->whitelabelCon;
33
  }
34
 
 
 
 
 
35
  /**
36
  * @return bool
37
  * @throws \Exception
@@ -70,6 +74,13 @@ class ModCon extends BaseShield\ModCon {
70
  }
71
 
72
  $opts->setOpt( 'sec_admin_users', $this->verifySecAdminUsers( $opts->getSecurityAdminUsers() ) );
 
 
 
 
 
 
 
73
  }
74
 
75
  /**
@@ -145,6 +156,7 @@ class ModCon extends BaseShield\ModCon {
145
  ->remove();
146
  break;
147
  default:
 
148
  break;
149
  }
150
  }
@@ -252,16 +264,19 @@ class ModCon extends BaseShield\ModCon {
252
  $sLogoUrl = $opts->getOpt( $key );
253
  }
254
  if ( !empty( $sLogoUrl ) && !Services::Data()->isValidWebUrl( $sLogoUrl ) && strpos( $sLogoUrl, '/' ) !== 0 ) {
255
- $sLogoUrl = $this->getCon()->getPluginUrl_Image( $sLogoUrl );
256
  if ( empty( $sLogoUrl ) ) {
257
  $opts->resetOptToDefault( $key );
258
- $sLogoUrl = $this->getCon()->getPluginUrl_Image( $opts->getOpt( $key ) );
259
  }
260
  }
261
 
262
  return $sLogoUrl;
263
  }
264
 
 
 
 
265
  public function isWlEnabled() :bool {
266
  /** @var Options $opts */
267
  $opts = $this->getOptions();
@@ -269,7 +284,7 @@ class ModCon extends BaseShield\ModCon {
269
  }
270
 
271
  public function isWlHideUpdates() :bool {
272
- return $this->isWlEnabled() && $this->getOptions()->isOpt( 'wl_hide_updates', 'Y' );
273
  }
274
 
275
  /**
@@ -334,11 +349,6 @@ class ModCon extends BaseShield\ModCon {
334
  /** @var Options $opts */
335
  $opts = $this->getOptions();
336
 
337
- if ( hash_equals( $opts->getSecurityPIN(), self::HASH_DELETE ) ) {
338
- $opts->clearSecurityAdminKey();
339
- $this->setSecurityAdminStatusOnOff( false );
340
- }
341
-
342
  // Restricting Activate Plugins also means restricting the rest.
343
  $aPluginsRestrictions = $opts->getAdminAccessArea_Plugins();
344
  if ( in_array( 'activate_plugins', $aPluginsRestrictions ) ) {
32
  return $this->whitelabelCon;
33
  }
34
 
35
+ public function getSecAdminLoginAjaxData() :array {
36
+ return $this->getAjaxActionData( 'sec_admin_login' );
37
+ }
38
+
39
  /**
40
  * @return bool
41
  * @throws \Exception
74
  }
75
 
76
  $opts->setOpt( 'sec_admin_users', $this->verifySecAdminUsers( $opts->getSecurityAdminUsers() ) );
77
+
78
+ if ( hash_equals( $opts->getSecurityPIN(), self::HASH_DELETE ) ) {
79
+ $opts->clearSecurityAdminKey();
80
+ $this->setSecurityAdminStatusOnOff( false );
81
+ // If you delete the PIN, you also delete the sec admins. Prevents a lock out bug.
82
+ $opts->setOpt( 'sec_admin_users', [] );
83
+ }
84
  }
85
 
86
  /**
156
  ->remove();
157
  break;
158
  default:
159
+ parent::handleModAction( $action );
160
  break;
161
  }
162
  }
264
  $sLogoUrl = $opts->getOpt( $key );
265
  }
266
  if ( !empty( $sLogoUrl ) && !Services::Data()->isValidWebUrl( $sLogoUrl ) && strpos( $sLogoUrl, '/' ) !== 0 ) {
267
+ $sLogoUrl = $this->getCon()->urls->forImage( $sLogoUrl );
268
  if ( empty( $sLogoUrl ) ) {
269
  $opts->resetOptToDefault( $key );
270
+ $sLogoUrl = $this->getCon()->urls->forImage( $opts->getOpt( $key ) );
271
  }
272
  }
273
 
274
  return $sLogoUrl;
275
  }
276
 
277
+ /**
278
+ * @deprecated 11.0
279
+ */
280
  public function isWlEnabled() :bool {
281
  /** @var Options $opts */
282
  $opts = $this->getOptions();
284
  }
285
 
286
  public function isWlHideUpdates() :bool {
287
+ return $this->isEnabledWhitelabel() && $this->getOptions()->isOpt( 'wl_hide_updates', 'Y' );
288
  }
289
 
290
  /**
349
  /** @var Options $opts */
350
  $opts = $this->getOptions();
351
 
 
 
 
 
 
352
  // Restricting Activate Plugins also means restricting the rest.
353
  $aPluginsRestrictions = $opts->getAdminAccessArea_Plugins();
354
  if ( in_array( 'activate_plugins', $aPluginsRestrictions ) ) {
src/lib/src/Modules/SecurityAdmin/Strings.php CHANGED
@@ -92,155 +92,163 @@ class Strings extends Base\Strings {
92
  * @throws \Exception
93
  */
94
  public function getOptionStrings( string $key ) :array {
95
- /** @var Options $oOpts */
96
- $oOpts = $this->getOptions();
97
  $sPlugName = $this->getCon()->getHumanName();
98
 
99
  switch ( $key ) {
100
 
101
  case 'enable_admin_access_restriction' :
102
- $sName = sprintf( __( 'Enable %s Module', 'wp-simple-firewall' ), __( 'Security Admin', 'wp-simple-firewall' ) );
103
- $sSummary = __( 'Enforce Security Admin Access Restriction', 'wp-simple-firewall' );
104
- $sDescription = __( "Enable this with great care and consideration. Ensure that you set an Security PIN that you'll remember.", 'wp-simple-firewall' );
105
  break;
106
 
107
  case 'admin_access_key' :
108
- $sName = __( 'Security Admin PIN', 'wp-simple-firewall' );
109
- $sSummary = __( 'Provide/Update Security Admin PIN', 'wp-simple-firewall' );
110
- $sDescription = [
111
  sprintf( '%s: %s', __( 'Careful', 'wp-simple-firewall' ), __( 'If you forget this, you could potentially lock yourself out from using this plugin.', 'wp-simple-firewall' ) ),
112
- '<strong>'.( $oOpts->hasSecurityPIN() ? __( 'Security PIN Currently Set', 'wp-simple-firewall' ) : __( 'Security PIN NOT Currently Set', 'wp-simple-firewall' ) ).'</strong>',
113
- $oOpts->hasSecurityPIN() ? sprintf( __( 'To delete the current security PIN, type exactly "%s" and save.', 'wp-simple-firewall' ), '<strong>DELETE</strong>' ) : ''
114
  ];
 
 
 
 
 
 
 
115
  break;
116
 
117
  case 'sec_admin_users' :
118
- $sName = __( 'Security Admins', 'wp-simple-firewall' );
119
- $sSummary = __( 'Persistent Security Admins', 'wp-simple-firewall' );
120
- $sDescription = __( "Users provided will be security admins automatically, without needing the security PIN.", 'wp-simple-firewall' )
121
- .'<br/>'.__( 'Enter admin username, email or ID.', 'wp-simple-firewall' ).' '.__( '1 entry per-line.', 'wp-simple-firewall' )
122
- .'<br/>'.sprintf( '%s: %s', __( 'Note', 'wp-simple-firewall' ), __( 'Verified users will be converted to usernames.', 'wp-simple-firewall' ) );
 
 
123
  break;
124
 
125
  case 'admin_access_timeout' :
126
- $sName = __( 'Security Admin Timeout', 'wp-simple-firewall' );
127
- $sSummary = __( 'Specify An Automatic Timeout Interval For Security Admin Access', 'wp-simple-firewall' );
128
- $sDescription = __( 'This will automatically expire your Security Admin Session.', 'wp-simple-firewall' )
129
- .'<br />'
130
- .sprintf(
131
- '%s: %s',
132
- __( 'Default', 'wp-simple-firewall' ),
133
- sprintf( '%s minutes', $oOpts->getOptDefault( 'admin_access_timeout' ) )
134
- );
135
  break;
136
 
137
  case 'allow_email_override' :
138
- $sName = __( 'Allow Email Override', 'wp-simple-firewall' );
139
- $sSummary = __( 'Allow Email Override Of Admin Access Restrictions', 'wp-simple-firewall' );
140
- $sDescription = __( 'Allow the use of verification emails to override and switch off the Security Admin restrictions.', 'wp-simple-firewall' )
141
- .'<br/>'.sprintf( __( "The email address specified in %s's General settings will be used.", 'wp-simple-firewall' ), $sPlugName );
142
  break;
143
 
144
  case 'admin_access_restrict_posts' :
145
- $sName = __( 'Pages', 'wp-simple-firewall' );
146
- $sSummary = __( 'Restrict Access To Key WordPress Posts And Pages Actions', 'wp-simple-firewall' );
147
- $sDescription = sprintf( '%s: %s', __( 'Careful', 'wp-simple-firewall' ), __( 'This will restrict access to page/post creation, editing and deletion.', 'wp-simple-firewall' ) )
148
- .'<br />'.sprintf( '%s: %s', __( 'Note', 'wp-simple-firewall' ), sprintf( __( 'Selecting "%s" will also restrict all other options.', 'wp-simple-firewall' ), __( 'Edit', 'wp-simple-firewall' ) ) );
149
  break;
150
 
151
  case 'admin_access_restrict_plugins' :
152
- $sName = __( 'Plugins', 'wp-simple-firewall' );
153
- $sSummary = __( 'Restrict Access To Key WordPress Plugin Actions', 'wp-simple-firewall' );
154
- $sDescription = sprintf( '%s: %s', __( 'Careful', 'wp-simple-firewall' ), __( 'This will restrict access to plugin installation, update, activation and deletion.', 'wp-simple-firewall' ) )
155
- .'<br />'.sprintf( '%s: %s', __( 'Note', 'wp-simple-firewall' ), sprintf( __( 'Selecting "%s" will also restrict all other options.', 'wp-simple-firewall' ), __( 'Activate', 'wp-simple-firewall' ) ) );
156
  break;
157
 
158
  case 'admin_access_restrict_options' :
159
- $sName = __( 'WordPress Options', 'wp-simple-firewall' );
160
- $sSummary = __( 'Restrict Access To Certain WordPress Admin Options', 'wp-simple-firewall' );
161
- $sDescription = sprintf( '%s: %s', __( 'Careful', 'wp-simple-firewall' ), __( 'This will restrict the ability of WordPress administrators from changing key WordPress settings.', 'wp-simple-firewall' ) );
162
  break;
163
 
164
  case 'admin_access_restrict_admin_users' :
165
- $sName = __( 'Admin Users', 'wp-simple-firewall' );
166
- $sSummary = __( 'Restrict Access To Create/Delete/Modify Other Admin Users', 'wp-simple-firewall' );
167
- $sDescription = sprintf( '%s: %s', __( 'Careful', 'wp-simple-firewall' ), __( 'This will restrict the ability of WordPress administrators from creating, modifying or promoting other administrators.', 'wp-simple-firewall' ) );
168
  break;
169
 
170
  case 'admin_access_restrict_themes' :
171
- $sName = __( 'Themes', 'wp-simple-firewall' );
172
- $sSummary = __( 'Restrict Access To WordPress Theme Actions', 'wp-simple-firewall' );
173
- $sDescription = sprintf( '%s: %s', __( 'Careful', 'wp-simple-firewall' ), __( 'This will restrict access to theme installation, update, activation and deletion.', 'wp-simple-firewall' ) )
174
- .'<br />'.
175
- sprintf( '%s: %s',
176
- __( 'Note', 'wp-simple-firewall' ),
177
- sprintf(
178
- __( 'Selecting "%s" will also restrict all other options.', 'wp-simple-firewall' ),
179
- sprintf(
180
- __( '%s and %s', 'wp-simple-firewall' ),
181
- __( 'Activate', 'wp-simple-firewall' ),
182
- __( 'Edit Theme Options', 'wp-simple-firewall' )
183
- )
184
- )
185
- );
186
  break;
187
 
188
  case 'whitelabel_enable' :
189
- $sName = sprintf( '%s: %s', __( 'Enable', 'wp-simple-firewall' ), __( 'White Label', 'wp-simple-firewall' ) );
190
- $sSummary = __( 'Activate Your White Label Settings', 'wp-simple-firewall' );
191
- $sDescription = __( 'Turn on/off the application of your White Label settings.', 'wp-simple-firewall' );
192
  break;
193
  case 'wl_hide_updates' :
194
- $sName = __( 'Hide Updates', 'wp-simple-firewall' );
195
- $sSummary = __( 'Hide Plugin Updates From Non-Security Admins', 'wp-simple-firewall' );
196
- $sDescription = sprintf( __( 'Hide available %s updates from non-security administrators.', 'wp-simple-firewall' ), $sPlugName );
197
  break;
198
  case 'wl_pluginnamemain' :
199
- $sName = __( 'Plugin Name', 'wp-simple-firewall' );
200
- $sSummary = __( 'The Name Of The Plugin', 'wp-simple-firewall' );
201
- $sDescription = __( 'The name of the plugin that will be displayed to your site users.', 'wp-simple-firewall' );
202
  break;
203
  case 'wl_replace_badge_url' :
204
- $sName = __( 'Replace Plugin Badge', 'wp-simple-firewall' );
205
- $sSummary = __( 'Replace Plugin Badge URL and Images', 'wp-simple-firewall' );
206
- $sDescription = __( 'When using the plugin badge, replace the URL and link with your Whitelabel settings.', 'wp-simple-firewall' );
207
  break;
208
  case 'wl_namemenu' :
209
- $sName = __( 'Menu Title', 'wp-simple-firewall' );
210
- $sSummary = __( 'The Main Menu Title Of The Plugin', 'wp-simple-firewall' );
211
- $sDescription = sprintf( __( 'The Main Menu Title Of The Plugin. If left empty, the "%s" will be used.', 'wp-simple-firewall' ), __( 'Plugin Name', 'wp-simple-firewall' ) );
212
  break;
213
  case 'wl_companyname' :
214
- $sName = __( 'Company Name', 'wp-simple-firewall' );
215
- $sSummary = __( 'The Name Of Your Company', 'wp-simple-firewall' );
216
- $sDescription = __( 'Provide the name of your company.', 'wp-simple-firewall' );
217
  break;
218
  case 'wl_description' :
219
- $sName = __( 'Description', 'wp-simple-firewall' );
220
- $sSummary = __( 'The Description Of The Plugin', 'wp-simple-firewall' );
221
- $sDescription = __( 'The description of the plugin displayed on the plugins page.', 'wp-simple-firewall' );
222
  break;
223
  case 'wl_homeurl' :
224
- $sName = __( 'Home URL', 'wp-simple-firewall' );
225
- $sSummary = __( 'Plugin Home Page URL', 'wp-simple-firewall' );
226
- $sDescription = __( "When a user clicks the home link for this plugin, this is where they'll be directed.", 'wp-simple-firewall' );
227
  break;
228
  case 'wl_menuiconurl' :
229
- $sName = __( 'Menu Icon', 'wp-simple-firewall' );
230
- $sSummary = __( 'Menu Icon URL', 'wp-simple-firewall' );
231
- $sDescription = __( 'The URL of the icon to display in the menu.', 'wp-simple-firewall' )
232
- .' '.sprintf( __( 'The %s should measure %s.', 'wp-simple-firewall' ), __( 'icon', 'wp-simple-firewall' ), '16px x 16px' );
233
  break;
234
  case 'wl_dashboardlogourl' :
235
- $sName = __( 'Dashboard Logo', 'wp-simple-firewall' );
236
- $sSummary = __( 'Dashboard Logo URL', 'wp-simple-firewall' );
237
- $sDescription = __( 'The URL of the logo to display in the admin pages.', 'wp-simple-firewall' )
238
- .' '.sprintf( __( 'The %s should measure %s.', 'wp-simple-firewall' ), __( 'logo', 'wp-simple-firewall' ), '128px x 128px' );
239
  break;
240
  case 'wl_login2fa_logourl' :
241
- $sName = __( '2FA Login Logo URL', 'wp-simple-firewall' );
242
- $sSummary = __( '2FA Login Logo URL', 'wp-simple-firewall' );
243
- $sDescription = __( 'The URL of the logo to display on the Two-Factor Authentication login page.', 'wp-simple-firewall' );
244
  break;
245
 
246
  default:
@@ -248,9 +256,9 @@ class Strings extends Base\Strings {
248
  }
249
 
250
  return [
251
- 'name' => $sName,
252
- 'summary' => $sSummary,
253
- 'description' => $sDescription,
254
  ];
255
  }
256
  }
92
  * @throws \Exception
93
  */
94
  public function getOptionStrings( string $key ) :array {
95
+ /** @var Options $opts */
96
+ $opts = $this->getOptions();
97
  $sPlugName = $this->getCon()->getHumanName();
98
 
99
  switch ( $key ) {
100
 
101
  case 'enable_admin_access_restriction' :
102
+ $name = sprintf( __( 'Enable %s Module', 'wp-simple-firewall' ), __( 'Security Admin', 'wp-simple-firewall' ) );
103
+ $summary = __( 'Enforce Security Admin Access Restriction', 'wp-simple-firewall' );
104
+ $description = __( "Enable this with great care and consideration. Ensure that you set an Security PIN that you'll remember.", 'wp-simple-firewall' );
105
  break;
106
 
107
  case 'admin_access_key' :
108
+ $name = __( 'Security Admin PIN', 'wp-simple-firewall' );
109
+ $summary = __( 'Provide/Update Security Admin PIN', 'wp-simple-firewall' );
110
+ $description = [
111
  sprintf( '%s: %s', __( 'Careful', 'wp-simple-firewall' ), __( 'If you forget this, you could potentially lock yourself out from using this plugin.', 'wp-simple-firewall' ) ),
112
+ '<strong>'.( $opts->hasSecurityPIN() ? __( 'Security PIN Currently Set', 'wp-simple-firewall' ) : __( 'Security PIN NOT Currently Set', 'wp-simple-firewall' ) ).'</strong>',
 
113
  ];
114
+ if ( $opts->hasSecurityPIN() ) {
115
+ $description[] = sprintf( __( 'To delete the current security PIN, type exactly "%s" and save.', 'wp-simple-firewall' ), '<strong>DELETE</strong>' );
116
+ if ( !empty( $opts->getSecurityAdminUsers() ) ) {
117
+ $description[] = sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( 'Deleting the PIN will also remove security admin users.', 'wp-simple-firewall' ) );
118
+ }
119
+ }
120
+
121
  break;
122
 
123
  case 'sec_admin_users' :
124
+ $name = __( 'Security Admins', 'wp-simple-firewall' );
125
+ $summary = __( 'Persistent Security Admins', 'wp-simple-firewall' );
126
+ $description = [
127
+ __( "Users provided will be security admins automatically, without needing the security PIN.", 'wp-simple-firewall' ),
128
+ __( 'Enter admin username, email or ID.', 'wp-simple-firewall' ).' '.__( '1 entry per-line.', 'wp-simple-firewall' ),
129
+ sprintf( '%s: %s', __( 'Note', 'wp-simple-firewall' ), __( 'Verified users will be converted to usernames.', 'wp-simple-firewall' ) )
130
+ ];
131
  break;
132
 
133
  case 'admin_access_timeout' :
134
+ $name = __( 'Security Admin Timeout', 'wp-simple-firewall' );
135
+ $summary = __( 'Specify An Automatic Timeout Interval For Security Admin Access', 'wp-simple-firewall' );
136
+ $description = __( 'This will automatically expire your Security Admin Session.', 'wp-simple-firewall' )
137
+ .'<br />'
138
+ .sprintf(
139
+ '%s: %s',
140
+ __( 'Default', 'wp-simple-firewall' ),
141
+ sprintf( '%s minutes', $opts->getOptDefault( 'admin_access_timeout' ) )
142
+ );
143
  break;
144
 
145
  case 'allow_email_override' :
146
+ $name = __( 'Allow Email Override', 'wp-simple-firewall' );
147
+ $summary = __( 'Allow Email Override Of Admin Access Restrictions', 'wp-simple-firewall' );
148
+ $description = __( 'Allow the use of verification emails to override and switch off the Security Admin restrictions.', 'wp-simple-firewall' )
149
+ .'<br/>'.sprintf( __( "The email address specified in %s's General settings will be used.", 'wp-simple-firewall' ), $sPlugName );
150
  break;
151
 
152
  case 'admin_access_restrict_posts' :
153
+ $name = __( 'Pages', 'wp-simple-firewall' );
154
+ $summary = __( 'Restrict Access To Key WordPress Posts And Pages Actions', 'wp-simple-firewall' );
155
+ $description = sprintf( '%s: %s', __( 'Careful', 'wp-simple-firewall' ), __( 'This will restrict access to page/post creation, editing and deletion.', 'wp-simple-firewall' ) )
156
+ .'<br />'.sprintf( '%s: %s', __( 'Note', 'wp-simple-firewall' ), sprintf( __( 'Selecting "%s" will also restrict all other options.', 'wp-simple-firewall' ), __( 'Edit', 'wp-simple-firewall' ) ) );
157
  break;
158
 
159
  case 'admin_access_restrict_plugins' :
160
+ $name = __( 'Plugins', 'wp-simple-firewall' );
161
+ $summary = __( 'Restrict Access To Key WordPress Plugin Actions', 'wp-simple-firewall' );
162
+ $description = sprintf( '%s: %s', __( 'Careful', 'wp-simple-firewall' ), __( 'This will restrict access to plugin installation, update, activation and deletion.', 'wp-simple-firewall' ) )
163
+ .'<br />'.sprintf( '%s: %s', __( 'Note', 'wp-simple-firewall' ), sprintf( __( 'Selecting "%s" will also restrict all other options.', 'wp-simple-firewall' ), __( 'Activate', 'wp-simple-firewall' ) ) );
164
  break;
165
 
166
  case 'admin_access_restrict_options' :
167
+ $name = __( 'WordPress Options', 'wp-simple-firewall' );
168
+ $summary = __( 'Restrict Access To Certain WordPress Admin Options', 'wp-simple-firewall' );
169
+ $description = sprintf( '%s: %s', __( 'Careful', 'wp-simple-firewall' ), __( 'This will restrict the ability of WordPress administrators from changing key WordPress settings.', 'wp-simple-firewall' ) );
170
  break;
171
 
172
  case 'admin_access_restrict_admin_users' :
173
+ $name = __( 'Admin Users', 'wp-simple-firewall' );
174
+ $summary = __( 'Restrict Access To Create/Delete/Modify Other Admin Users', 'wp-simple-firewall' );
175
+ $description = sprintf( '%s: %s', __( 'Careful', 'wp-simple-firewall' ), __( 'This will restrict the ability of WordPress administrators from creating, modifying or promoting other administrators.', 'wp-simple-firewall' ) );
176
  break;
177
 
178
  case 'admin_access_restrict_themes' :
179
+ $name = __( 'Themes', 'wp-simple-firewall' );
180
+ $summary = __( 'Restrict Access To WordPress Theme Actions', 'wp-simple-firewall' );
181
+ $description = sprintf( '%s: %s', __( 'Careful', 'wp-simple-firewall' ), __( 'This will restrict access to theme installation, update, activation and deletion.', 'wp-simple-firewall' ) )
182
+ .'<br />'.
183
+ sprintf( '%s: %s',
184
+ __( 'Note', 'wp-simple-firewall' ),
185
+ sprintf(
186
+ __( 'Selecting "%s" will also restrict all other options.', 'wp-simple-firewall' ),
187
+ sprintf(
188
+ __( '%s and %s', 'wp-simple-firewall' ),
189
+ __( 'Activate', 'wp-simple-firewall' ),
190
+ __( 'Edit Theme Options', 'wp-simple-firewall' )
191
+ )
192
+ )
193
+ );
194
  break;
195
 
196
  case 'whitelabel_enable' :
197
+ $name = sprintf( '%s: %s', __( 'Enable', 'wp-simple-firewall' ), __( 'White Label', 'wp-simple-firewall' ) );
198
+ $summary = __( 'Activate Your White Label Settings', 'wp-simple-firewall' );
199
+ $description = __( 'Turn on/off the application of your White Label settings.', 'wp-simple-firewall' );
200
  break;
201
  case 'wl_hide_updates' :
202
+ $name = __( 'Hide Updates', 'wp-simple-firewall' );
203
+ $summary = __( 'Hide Plugin Updates From Non-Security Admins', 'wp-simple-firewall' );
204
+ $description = sprintf( __( 'Hide available %s updates from non-security administrators.', 'wp-simple-firewall' ), $sPlugName );
205
  break;
206
  case 'wl_pluginnamemain' :
207
+ $name = __( 'Plugin Name', 'wp-simple-firewall' );
208
+ $summary = __( 'The Name Of The Plugin', 'wp-simple-firewall' );
209
+ $description = __( 'The name of the plugin that will be displayed to your site users.', 'wp-simple-firewall' );
210
  break;
211
  case 'wl_replace_badge_url' :
212
+ $name = __( 'Replace Plugin Badge', 'wp-simple-firewall' );
213
+ $summary = __( 'Replace Plugin Badge URL and Images', 'wp-simple-firewall' );
214
+ $description = __( 'When using the plugin badge, replace the URL and link with your Whitelabel settings.', 'wp-simple-firewall' );
215
  break;
216
  case 'wl_namemenu' :
217
+ $name = __( 'Menu Title', 'wp-simple-firewall' );
218
+ $summary = __( 'The Main Menu Title Of The Plugin', 'wp-simple-firewall' );
219
+ $description = sprintf( __( 'The Main Menu Title Of The Plugin. If left empty, the "%s" will be used.', 'wp-simple-firewall' ), __( 'Plugin Name', 'wp-simple-firewall' ) );
220
  break;
221
  case 'wl_companyname' :
222
+ $name = __( 'Company Name', 'wp-simple-firewall' );
223
+ $summary = __( 'The Name Of Your Company', 'wp-simple-firewall' );
224
+ $description = __( 'Provide the name of your company.', 'wp-simple-firewall' );
225
  break;
226
  case 'wl_description' :
227
+ $name = __( 'Description', 'wp-simple-firewall' );
228
+ $summary = __( 'The Description Of The Plugin', 'wp-simple-firewall' );
229
+ $description = __( 'The description of the plugin displayed on the plugins page.', 'wp-simple-firewall' );
230
  break;
231
  case 'wl_homeurl' :
232
+ $name = __( 'Home URL', 'wp-simple-firewall' );
233
+ $summary = __( 'Plugin Home Page URL', 'wp-simple-firewall' );
234
+ $description = __( "When a user clicks the home link for this plugin, this is where they'll be directed.", 'wp-simple-firewall' );
235
  break;
236
  case 'wl_menuiconurl' :
237
+ $name = __( 'Menu Icon', 'wp-simple-firewall' );
238
+ $summary = __( 'Menu Icon URL', 'wp-simple-firewall' );
239
+ $description = __( 'The URL of the icon to display in the menu.', 'wp-simple-firewall' )
240
+ .' '.sprintf( __( 'The %s should measure %s.', 'wp-simple-firewall' ), __( 'icon', 'wp-simple-firewall' ), '16px x 16px' );
241
  break;
242
  case 'wl_dashboardlogourl' :
243
+ $name = __( 'Dashboard Logo', 'wp-simple-firewall' );
244
+ $summary = __( 'Dashboard Logo URL', 'wp-simple-firewall' );
245
+ $description = __( 'The URL of the logo to display in the admin pages.', 'wp-simple-firewall' )
246
+ .' '.sprintf( __( 'The %s should measure %s.', 'wp-simple-firewall' ), __( 'logo', 'wp-simple-firewall' ), '128px x 128px' );
247
  break;
248
  case 'wl_login2fa_logourl' :
249
+ $name = __( '2FA Login Logo URL', 'wp-simple-firewall' );
250
+ $summary = __( '2FA Login Logo URL', 'wp-simple-firewall' );
251
+ $description = __( 'The URL of the logo to display on the Two-Factor Authentication login page.', 'wp-simple-firewall' );
252
  break;
253
 
254
  default:
256
  }
257
 
258
  return [
259
+ 'name' => $name,
260
+ 'summary' => $summary,
261
+ 'description' => $description,
262
  ];
263
  }
264
  }
src/lib/src/Modules/Sessions/ModCon.php CHANGED
@@ -22,7 +22,8 @@ class ModCon extends BaseShield\ModCon {
22
  }
23
 
24
  public function getDbHandler_Sessions() :Databases\Session\Handler {
25
- return $this->getDbH( 'session' );
 
26
  }
27
 
28
  public function isAutoAddSessions() :bool {
22
  }
23
 
24
  public function getDbHandler_Sessions() :Databases\Session\Handler {
25
+ $new = $this->getDbH( 'sessions' );
26
+ return empty( $new ) ? $this->getDbH( 'session' ) : $new;
27
  }
28
 
29
  public function isAutoAddSessions() :bool {
src/lib/src/Modules/Sessions/Processor.php CHANGED
@@ -33,6 +33,7 @@ class Processor extends BaseShield\Processor {
33
 
34
  protected function captureLogin( \WP_User $user ) {
35
  $this->activateUserSession( $user );
 
36
  }
37
 
38
  public function onWpLoaded() {
33
 
34
  protected function captureLogin( \WP_User $user ) {
35
  $this->activateUserSession( $user );
36
+ $this->getCon()->fireEvent( 'login_success' );
37
  }
38
 
39
  public function onWpLoaded() {
src/lib/src/Modules/Traffic/AjaxHandler.php CHANGED
@@ -10,14 +10,14 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
10
 
11
  switch ( $action ) {
12
  case 'render_table_traffic':
13
- $aResponse = $this->ajaxExec_BuildTableTraffic();
14
  break;
15
 
16
  default:
17
- $aResponse = parent::processAjaxAction( $action );
18
  }
19
 
20
- return $aResponse;
21
  }
22
 
23
  private function ajaxExec_BuildTableTraffic() :array {
10
 
11
  switch ( $action ) {
12
  case 'render_table_traffic':
13
+ $response = $this->ajaxExec_BuildTableTraffic();
14
  break;
15
 
16
  default:
17
+ $response = parent::processAjaxAction( $action );
18
  }
19
 
20
+ return $response;
21
  }
22
 
23
  private function ajaxExec_BuildTableTraffic() :array {
src/lib/src/Modules/Traffic/Lib/Logger.php CHANGED
@@ -6,7 +6,6 @@ use FernleafSystems\Wordpress\Plugin\Shield\Databases\Traffic\EntryVO;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic;
8
  use FernleafSystems\Wordpress\Services\Services;
9
- use FernleafSystems\Wordpress\Services\Utilities\Net\IpIdentify;
10
 
11
  class Logger {
12
 
@@ -26,7 +25,7 @@ class Logger {
26
 
27
  private function isRequestToBeLogged() :bool {
28
  return !$this->getCon()->plugin_deleting
29
- && apply_filters( $this->getCon()->prefix( 'is_log_traffic' ), true )
30
  && ( !$this->isCustomExcluded() )
31
  && ( !$this->isRequestTypeExcluded() );
32
  }
@@ -68,25 +67,13 @@ class Logger {
68
  }
69
 
70
  private function isServiceIp_Search() :bool {
71
- return in_array( Services::IP()->getIpDetector()->getIPIdentity(), [
72
- IpIdentify::APPLE,
73
- IpIdentify::BAIDU,
74
- IpIdentify::BING,
75
- IpIdentify::DUCKDUCKGO,
76
- IpIdentify::GOOGLE,
77
- IpIdentify::HUAWEI,
78
- IpIdentify::YAHOO,
79
- IpIdentify::YANDEX,
80
- ] );
81
  }
82
 
83
  private function isServiceIp_Uptime() :bool {
84
- return in_array( Services::IP()->getIpDetector()->getIPIdentity(), [
85
- IpIdentify::PINGDOM,
86
- IpIdentify::STATUSCAKE,
87
- IpIdentify::UPTIMEROBOT,
88
- IpIdentify::NODEPING
89
- ] );
90
  }
91
 
92
  private function logTraffic() {
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic;
8
  use FernleafSystems\Wordpress\Services\Services;
 
9
 
10
  class Logger {
11
 
25
 
26
  private function isRequestToBeLogged() :bool {
27
  return !$this->getCon()->plugin_deleting
28
+ && apply_filters( 'shield/is_log_traffic', true )
29
  && ( !$this->isCustomExcluded() )
30
  && ( !$this->isRequestTypeExcluded() );
31
  }
67
  }
68
 
69
  private function isServiceIp_Search() :bool {
70
+ return in_array( Services::IP()->getIpDetector()->getIPIdentity(),
71
+ Services::ServiceProviders()->getSearchProviders() );
 
 
 
 
 
 
 
 
72
  }
73
 
74
  private function isServiceIp_Uptime() :bool {
75
+ return in_array( Services::IP()->getIpDetector()->getIPIdentity(),
76
+ Services::ServiceProviders()->getUptimeProviders() );
 
 
 
 
77
  }
78
 
79
  private function logTraffic() {
src/lib/src/Modules/Traffic/ModCon.php CHANGED
@@ -4,6 +4,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic;
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 {
@@ -12,6 +13,16 @@ class ModCon extends BaseShield\ModCon {
12
  return $this->getDbH( 'traffic' );
13
  }
14
 
 
 
 
 
 
 
 
 
 
 
15
  protected function preProcessOptions() {
16
  /** @var Options $opts */
17
  $opts = $this->getOptions();
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Tool\DbTableExport;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
10
  class ModCon extends BaseShield\ModCon {
13
  return $this->getDbH( 'traffic' );
14
  }
15
 
16
+ protected function handleFileDownload( string $downloadID ) {
17
+ switch ( $downloadID ) {
18
+ case 'db_traffic':
19
+ ( new DbTableExport() )
20
+ ->setDbHandler( $this->getDbHandler_Traffic() )
21
+ ->toCSV();
22
+ break;
23
+ }
24
+ }
25
+
26
  protected function preProcessOptions() {
27
  /** @var Options $opts */
28
  $opts = $this->getOptions();
src/lib/src/Modules/Traffic/Processor.php CHANGED
@@ -3,12 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic\Lib;
7
 
8
- /**
9
- * Class Processor
10
- * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic
11
- */
12
  class Processor extends Modules\BaseShield\Processor {
13
 
14
  protected function run() {
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
 
6
 
 
 
 
 
7
  class Processor extends Modules\BaseShield\Processor {
8
 
9
  protected function run() {
src/lib/src/Modules/Traffic/Strings.php CHANGED
@@ -28,27 +28,27 @@ class Strings extends Base\Strings {
28
  switch ( $section ) {
29
 
30
  case 'section_enable_plugin_feature_traffic' :
31
- $sTitleShort = sprintf( '%s/%s', __( 'On', 'wp-simple-firewall' ), __( 'Off', 'wp-simple-firewall' ) );
32
- $sTitle = sprintf( __( 'Enable Module: %s', 'wp-simple-firewall' ), $sModName );
33
- $aSummary = [
34
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Monitor and review all requests to your site.', 'wp-simple-firewall' ) ),
35
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), sprintf( __( 'Required only if you need to review and investigate and monitor requests to your site', 'wp-simple-firewall' ) ) )
36
  ];
37
  break;
38
 
39
  case 'section_traffic_options' :
40
- $sTitle = __( 'Traffic Watch Options', 'wp-simple-firewall' );
41
- $aSummary = [
 
42
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Provides finer control over the Traffic Watch system.', 'wp-simple-firewall' ) ),
43
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), sprintf( __( 'These settings are dependent on your requirements.', 'wp-simple-firewall' ), __( 'User Management', 'wp-simple-firewall' ) ) )
44
  ];
45
- $sTitleShort = __( 'Traffic Logging Options', 'wp-simple-firewall' );
46
  break;
47
 
48
  case 'section_traffic_limiter' :
49
- $sTitle = __( 'Brute Force Traffic Rate Limiting', 'wp-simple-firewall' );
50
- $sTitleShort = __( 'Traffic Rate Limiting', 'wp-simple-firewall' );
51
- $aSummary = [
52
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Prevents excessive requests from a single visitor.', 'wp-simple-firewall' ) ),
53
  sprintf( '%s - %s', __( 'Important', 'wp-simple-firewall' ), sprintf( __( 'This feature is only available while the Traffic Logger is active.', 'wp-simple-firewall' ), __( 'User Management', 'wp-simple-firewall' ) ) ),
54
  sprintf( '%s - %s', __( 'Warning', 'wp-simple-firewall' ), __( 'Use this feature with care.', 'wp-simple-firewall' ) )
@@ -61,9 +61,9 @@ class Strings extends Base\Strings {
61
  }
62
 
63
  return [
64
- 'title' => $sTitle,
65
- 'title_short' => $sTitleShort,
66
- 'summary' => ( isset( $aSummary ) && is_array( $aSummary ) ) ? $aSummary : [],
67
  ];
68
  }
69
 
@@ -80,69 +80,75 @@ class Strings extends Base\Strings {
80
  switch ( $key ) {
81
 
82
  case 'enable_traffic' :
83
- $sName = sprintf( __( 'Enable %s Module', 'wp-simple-firewall' ), $sModName );
84
- $sSummary = sprintf( __( 'Enable (or Disable) The %s Module', 'wp-simple-firewall' ), $sModName );
85
- $sDescription = sprintf( __( 'Un-Checking this option will completely disable the %s module.', 'wp-simple-firewall' ), $sModName );
86
  break;
87
 
88
  case 'enable_logger' :
89
- $sName = __( 'Enable Traffic Logger', 'wp-simple-firewall' );
90
- $sSummary = __( 'Turn On The Traffic Logging Feature', 'wp-simple-firewall' );
91
- $sDescription = __( 'Enable or disable the ability to log and monitor requests to your site', 'wp-simple-firewall' );
92
  break;
93
 
94
  case 'type_exclusions' :
95
- $sName = __( 'Traffic Log Exclusions', 'wp-simple-firewall' );
96
- $sSummary = __( 'Select Which Types Of Requests To Exclude', 'wp-simple-firewall' );
97
- $sDescription = __( "Select request types that you don't want to appear in the traffic viewer.", 'wp-simple-firewall' )
98
- .'<br/>'.__( 'If a request matches any exclusion rule, it will not show on the traffic viewer.', 'wp-simple-firewall' );
99
  break;
100
 
101
  case 'custom_exclusions' :
102
- $sName = __( 'Custom Exclusions', 'wp-simple-firewall' );
103
- $sSummary = __( 'Provide Custom Traffic Exclusions', 'wp-simple-firewall' );
104
- $sDescription = __( "For each entry, if the text is present in either the User Agent or request Path, it will be excluded.", 'wp-simple-firewall' )
105
- .'<br/>'.__( 'Take a new line for each entry.', 'wp-simple-firewall' )
106
- .'<br/>'.__( 'Comparisons are case-insensitive.', 'wp-simple-firewall' );
 
 
107
  break;
108
 
109
  case 'auto_clean' :
110
- $sName = __( 'Auto Expiry Cleaning', 'wp-simple-firewall' );
111
- $sSummary = __( 'Enable Traffic Log Auto Expiry', 'wp-simple-firewall' );
112
- $sDescription = __( 'DB cleanup will delete logs older than this maximum value (in days).', 'wp-simple-firewall' );
113
  break;
114
 
115
  case 'max_entries' :
116
- $sName = __( 'Max Log Length', 'wp-simple-firewall' );
117
- $sSummary = __( 'Maximum Traffic Log Length To Keep', 'wp-simple-firewall' );
118
- $sDescription = __( 'DB cleanup will delete logs to maintain this maximum number of records.', 'wp-simple-firewall' );
119
  break;
120
 
121
  case 'enable_limiter' :
122
- $sName = __( 'Enable Rate Limiting', 'wp-simple-firewall' );
123
- $sSummary = __( 'Turn On The Rate Limiting Feature', 'wp-simple-firewall' );
124
- $sDescription = __( 'Enable or disable the rate limiting feature according to your rate limiting parameters.', 'wp-simple-firewall' );
125
  break;
126
 
127
  case 'limit_requests' :
128
- $sName = __( 'Max Request Limit', 'wp-simple-firewall' );
129
- $sSummary = __( 'Maximum Number Of Requests Allowed In Time Limit', 'wp-simple-firewall' );
130
- $sDescription = __( 'The maximum number of requests that are allowed within the given request time limit.', 'wp-simple-firewall' )
131
- .'<br/>'.__( 'Any visitor that exceeds this number of requests in the given time period will register an offense against their IP address.', 'wp-simple-firewall' )
132
- .'<br/>'.__( 'Enough offenses will result in a ban of the IP address.', 'wp-simple-firewall' )
133
- .'<br/>'.__( 'Use a larger maximum request limit to reduce the risk of blocking legitimate visitors.', 'wp-simple-firewall' );
 
 
134
  break;
135
 
136
  case 'limit_time_span' :
137
- $sName = __( 'Request Limit Time Interval', 'wp-simple-firewall' );
138
- $sSummary = __( 'The Time Interval To Test For Excessive Requests', 'wp-simple-firewall' );
139
- $sDescription = __( 'The time period within which to monitor for multiple requests that exceed the max request limit.', 'wp-simple-firewall' )
140
- .'<br/>'.sprintf( '%s: %s', __( 'Note', 'wp-simple-firewall' ), __( 'Interval is measured in seconds.', 'wp-simple-firewall' ) )
141
- .'<br/>'.sprintf( '%s: %s', __( 'Example', 'wp-simple-firewall' ),
142
- sprintf( __( 'Use %s to test for excessive requests within a %s minutes interval.', 'wp-simple-firewall' ), '<code>300</code>', 5 ) )
143
- .'<br/>'.sprintf( '%s: %s', __( 'Example', 'wp-simple-firewall' ),
144
- sprintf( __( 'Use %s to test for excessive requests within a %s minutes interval.', 'wp-simple-firewall' ), '<code>3600</code>', 60 ) )
145
- .'<br/>'.__( 'Use a smaller interval to reduce the risk of blocking legitimate visitors.', 'wp-simple-firewall' );
 
 
146
  break;
147
 
148
  default:
@@ -150,9 +156,9 @@ class Strings extends Base\Strings {
150
  }
151
 
152
  return [
153
- 'name' => $sName,
154
- 'summary' => $sSummary,
155
- 'description' => $sDescription,
156
  ];
157
  }
158
  }
28
  switch ( $section ) {
29
 
30
  case 'section_enable_plugin_feature_traffic' :
31
+ $short = sprintf( '%s/%s', __( 'On', 'wp-simple-firewall' ), __( 'Off', 'wp-simple-firewall' ) );
32
+ $title = sprintf( __( 'Enable Module: %s', 'wp-simple-firewall' ), $sModName );
33
+ $summary = [
34
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Monitor and review all requests to your site.', 'wp-simple-firewall' ) ),
35
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), sprintf( __( 'Required only if you need to review and investigate and monitor requests to your site', 'wp-simple-firewall' ) ) )
36
  ];
37
  break;
38
 
39
  case 'section_traffic_options' :
40
+ $short = __( 'Traffic Logging Options', 'wp-simple-firewall' );
41
+ $title = __( 'Traffic Watch Options', 'wp-simple-firewall' );
42
+ $summary = [
43
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Provides finer control over the Traffic Watch system.', 'wp-simple-firewall' ) ),
44
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), sprintf( __( 'These settings are dependent on your requirements.', 'wp-simple-firewall' ), __( 'User Management', 'wp-simple-firewall' ) ) )
45
  ];
 
46
  break;
47
 
48
  case 'section_traffic_limiter' :
49
+ $title = __( 'Brute Force Traffic Rate Limiting', 'wp-simple-firewall' );
50
+ $short = __( 'Traffic Rate Limiting', 'wp-simple-firewall' );
51
+ $summary = [
52
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Prevents excessive requests from a single visitor.', 'wp-simple-firewall' ) ),
53
  sprintf( '%s - %s', __( 'Important', 'wp-simple-firewall' ), sprintf( __( 'This feature is only available while the Traffic Logger is active.', 'wp-simple-firewall' ), __( 'User Management', 'wp-simple-firewall' ) ) ),
54
  sprintf( '%s - %s', __( 'Warning', 'wp-simple-firewall' ), __( 'Use this feature with care.', 'wp-simple-firewall' ) )
61
  }
62
 
63
  return [
64
+ 'title' => $title,
65
+ 'title_short' => $short,
66
+ 'summary' => ( isset( $summary ) && is_array( $summary ) ) ? $summary : [],
67
  ];
68
  }
69
 
80
  switch ( $key ) {
81
 
82
  case 'enable_traffic' :
83
+ $name = sprintf( __( 'Enable %s Module', 'wp-simple-firewall' ), $sModName );
84
+ $summary = sprintf( __( 'Enable (or Disable) The %s Module', 'wp-simple-firewall' ), $sModName );
85
+ $desc = sprintf( __( 'Un-Checking this option will completely disable the %s module.', 'wp-simple-firewall' ), $sModName );
86
  break;
87
 
88
  case 'enable_logger' :
89
+ $name = __( 'Enable Traffic Logger', 'wp-simple-firewall' );
90
+ $summary = __( 'Turn On The Traffic Logging Feature', 'wp-simple-firewall' );
91
+ $desc = __( 'Enable or disable the ability to log and monitor requests to your site', 'wp-simple-firewall' );
92
  break;
93
 
94
  case 'type_exclusions' :
95
+ $name = __( 'Traffic Log Exclusions', 'wp-simple-firewall' );
96
+ $summary = __( 'Select Which Types Of Requests To Exclude', 'wp-simple-firewall' );
97
+ $desc = __( "Select request types that you don't want to appear in the traffic viewer.", 'wp-simple-firewall' )
98
+ .'<br/>'.__( 'If a request matches any exclusion rule, it will not show on the traffic viewer.', 'wp-simple-firewall' );
99
  break;
100
 
101
  case 'custom_exclusions' :
102
+ $name = __( 'Custom Exclusions', 'wp-simple-firewall' );
103
+ $summary = __( 'Provide Custom Traffic Exclusions', 'wp-simple-firewall' );
104
+ $desc = [
105
+ __( "For each entry, if the text is present in either the User Agent or request Path, it will be excluded.", 'wp-simple-firewall' ),
106
+ __( 'Take a new line for each entry.', 'wp-simple-firewall' ),
107
+ __( 'Comparisons are case-insensitive.', 'wp-simple-firewall' )
108
+ ];
109
  break;
110
 
111
  case 'auto_clean' :
112
+ $name = __( 'Auto Expiry Cleaning', 'wp-simple-firewall' );
113
+ $summary = __( 'Enable Traffic Log Auto Expiry', 'wp-simple-firewall' );
114
+ $desc = __( 'DB cleanup will delete logs older than this maximum value (in days).', 'wp-simple-firewall' );
115
  break;
116
 
117
  case 'max_entries' :
118
+ $name = __( 'Max Log Length', 'wp-simple-firewall' );
119
+ $summary = __( 'Maximum Traffic Log Length To Keep', 'wp-simple-firewall' );
120
+ $desc = __( 'DB cleanup will delete logs to maintain this maximum number of records.', 'wp-simple-firewall' );
121
  break;
122
 
123
  case 'enable_limiter' :
124
+ $name = __( 'Enable Rate Limiting', 'wp-simple-firewall' );
125
+ $summary = __( 'Turn On The Rate Limiting Feature', 'wp-simple-firewall' );
126
+ $desc = __( 'Enable or disable the rate limiting feature according to your rate limiting parameters.', 'wp-simple-firewall' );
127
  break;
128
 
129
  case 'limit_requests' :
130
+ $name = __( 'Max Request Limit', 'wp-simple-firewall' );
131
+ $summary = __( 'Maximum Number Of Requests Allowed In Time Limit', 'wp-simple-firewall' );
132
+ $desc = [
133
+ __( 'The maximum number of requests that are allowed within the given request time limit.', 'wp-simple-firewall' ),
134
+ __( 'Any visitor that exceeds this number of requests in the given time period will register an offense against their IP address.', 'wp-simple-firewall' ),
135
+ __( 'Enough offenses will result in a ban of the IP address.', 'wp-simple-firewall' ),
136
+ __( 'Use a larger maximum request limit to reduce the risk of blocking legitimate visitors.', 'wp-simple-firewall' )
137
+ ];
138
  break;
139
 
140
  case 'limit_time_span' :
141
+ $name = __( 'Request Limit Time Interval', 'wp-simple-firewall' );
142
+ $summary = __( 'The Time Interval To Test For Excessive Requests', 'wp-simple-firewall' );
143
+ $desc = [
144
+ __( 'The time period within which to monitor for multiple requests that exceed the max request limit.', 'wp-simple-firewall' ),
145
+ sprintf( '%s: %s', __( 'Note', 'wp-simple-firewall' ), __( 'Interval is measured in seconds.', 'wp-simple-firewall' ) ),
146
+ sprintf( '%s: %s', __( 'Example', 'wp-simple-firewall' ),
147
+ sprintf( __( 'Use %s to test for excessive requests within a %s minutes interval.', 'wp-simple-firewall' ), '<code>300</code>', 5 ) ),
148
+ sprintf( '%s: %s', __( 'Example', 'wp-simple-firewall' ),
149
+ sprintf( __( 'Use %s to test for excessive requests within a %s minutes interval.', 'wp-simple-firewall' ), '<code>3600</code>', 60 ) ),
150
+ __( 'Use a smaller interval to reduce the risk of blocking legitimate visitors.', 'wp-simple-firewall' )
151
+ ];
152
  break;
153
 
154
  default:
156
  }
157
 
158
  return [
159
+ 'name' => $name,
160
+ 'summary' => $summary,
161
+ 'description' => $desc,
162
  ];
163
  }
164
  }
src/lib/src/Modules/Traffic/UI.php CHANGED
@@ -48,35 +48,31 @@ class UI extends BaseShield\UI {
48
  );
49
  }
50
 
51
- /**
52
- * @param string $section
53
- * @return array
54
- */
55
  protected function getSectionWarnings( string $section ) :array {
56
- /** @var Options $oOpts */
57
- $oOpts = $this->getOptions();
58
 
59
- $aWarnings = [];
60
 
61
- $oIp = Services::IP();
62
- if ( !$oIp->isValidIp_PublicRange( $oIp->getRequestIp() ) ) {
63
- $aWarnings[] = __( 'Traffic Watcher will not run because visitor IP address detection is not correctly configured.', 'wp-simple-firewall' );
64
  }
65
 
66
  switch ( $section ) {
67
  case 'section_traffic_limiter':
68
  if ( $this->getCon()->isPremiumActive() ) {
69
- if ( !$oOpts->isTrafficLoggerEnabled() ) {
70
- $aWarnings[] = sprintf( __( '%s may only be enabled if the Traffic Logger feature is also turned on.', 'wp-simple-firewall' ), __( 'Traffic Rate Limiter', 'wp-simple-firewall' ) );
71
  }
72
  }
73
  else {
74
- $aWarnings[] = sprintf( __( '%s is a Pro-only feature.', 'wp-simple-firewall' ), __( 'Traffic Rate Limiter', 'wp-simple-firewall' ) );
75
  }
76
  break;
77
  }
78
 
79
- return $aWarnings;
80
  }
81
 
82
  protected function getSettingsRelatedLinks() :array {
48
  );
49
  }
50
 
 
 
 
 
51
  protected function getSectionWarnings( string $section ) :array {
52
+ /** @var Options $opts */
53
+ $opts = $this->getOptions();
54
 
55
+ $warning = [];
56
 
57
+ $srvIP = Services::IP();
58
+ if ( !$srvIP->isValidIp_PublicRange( $srvIP->getRequestIp() ) ) {
59
+ $warning[] = __( 'Traffic Watcher will not run because visitor IP address detection is not correctly configured.', 'wp-simple-firewall' );
60
  }
61
 
62
  switch ( $section ) {
63
  case 'section_traffic_limiter':
64
  if ( $this->getCon()->isPremiumActive() ) {
65
+ if ( !$opts->isTrafficLoggerEnabled() ) {
66
+ $warning[] = sprintf( __( '%s may only be enabled if the Traffic Logger feature is also turned on.', 'wp-simple-firewall' ), __( 'Traffic Rate Limiter', 'wp-simple-firewall' ) );
67
  }
68
  }
69
  else {
70
+ $warning[] = sprintf( __( '%s is a Pro-only feature.', 'wp-simple-firewall' ), __( 'Traffic Rate Limiter', 'wp-simple-firewall' ) );
71
  }
72
  break;
73
  }
74
 
75
+ return $warning;
76
  }
77
 
78
  protected function getSettingsRelatedLinks() :array {
src/lib/src/Modules/UserManagement/Lib/Password/UserPasswordHandler.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement\Lib\Password;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Consumer\WpLoginCapture;
@@ -16,10 +16,10 @@ use FernleafSystems\Wordpress\Services\Services;
16
  class UserPasswordHandler {
17
 
18
  use ModConsumer;
19
- use OneTimeExecute;
20
  use WpLoginCapture;
21
 
22
- protected function canRun() {
23
  /** @var UserManagement\Options $opts */
24
  $opts = $this->getOptions();
25
  return $opts->isPasswordPoliciesEnabled();
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement\Lib\Password;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Consumer\WpLoginCapture;
16
  class UserPasswordHandler {
17
 
18
  use ModConsumer;
19
+ use ExecOnce;
20
  use WpLoginCapture;
21
 
22
+ protected function canRun() :bool {
23
  /** @var UserManagement\Options $opts */
24
  $opts = $this->getOptions();
25
  return $opts->isPasswordPoliciesEnabled();
src/lib/src/Modules/UserManagement/Lib/Registration/EmailValidate.php CHANGED
@@ -10,7 +10,7 @@ class EmailValidate {
10
 
11
  use ModConsumer;
12
 
13
- private $aTrack;
14
 
15
  public function run() {
16
  /** @var UserManagement\Options $oOpts */
@@ -25,30 +25,30 @@ class EmailValidate {
25
  * @return array
26
  */
27
  public function validateNewUserEmail( $aUserData ) {
28
- $sEmail = $aUserData[ 'user_email' ];
29
- /** @var UserManagement\Options $oOpts */
30
 
31
- if ( !is_array( $this->aTrack ) ) {
32
- $this->aTrack = [];
33
  }
34
 
35
  // This hook seems to be called twice on any given registration.
36
- if ( !in_array( $sEmail, $this->aTrack ) ) {
37
- $this->aTrack[] = $sEmail;
38
 
39
- $oOpts = $this->getOptions();
40
  $sInvalidBecause = null;
41
- if ( !is_email( $sEmail ) ) {
42
  $sInvalidBecause = 'syntax';
43
  }
44
  else {
45
- $sApiToken = $this->getCon()
46
- ->getModule_License()
47
- ->getWpHashesTokenManager()
48
- ->getToken();
49
- if ( !empty( $sApiToken ) ) {
50
- $aChecks = $oOpts->getEmailValidationChecks();
51
- $aVerifys = ( new Email( $sApiToken ) )->getEmailVerification( $sEmail );
52
  if ( is_array( $aVerifys ) ) {
53
  foreach ( $aVerifys as $sVerifyKey => $bIsValid ) {
54
  if ( !$bIsValid && in_array( $sVerifyKey, $aChecks ) ) {
@@ -61,12 +61,12 @@ class EmailValidate {
61
  }
62
 
63
  if ( !empty( $sInvalidBecause ) ) {
64
- $sOpt = $oOpts->getValidateEmailOnRegistration();
65
  $this->getCon()->fireEvent(
66
  'reg_email_invalid',
67
  [
68
  'audit' => [
69
- 'email' => sanitize_email( $sEmail ),
70
  'reason' => sanitize_key( $sInvalidBecause ),
71
  ],
72
  'offense_count' => $sOpt == 'log' ? 0 : 1,
10
 
11
  use ModConsumer;
12
 
13
+ private $track;
14
 
15
  public function run() {
16
  /** @var UserManagement\Options $oOpts */
25
  * @return array
26
  */
27
  public function validateNewUserEmail( $aUserData ) {
28
+ $email = $aUserData[ 'user_email' ];
29
+ /** @var UserManagement\Options $opts */
30
 
31
+ if ( !is_array( $this->track ) ) {
32
+ $this->track = [];
33
  }
34
 
35
  // This hook seems to be called twice on any given registration.
36
+ if ( !empty( $email ) && !in_array( $email, $this->track ) ) {
37
+ $this->track[] = $email;
38
 
39
+ $opts = $this->getOptions();
40
  $sInvalidBecause = null;
41
+ if ( !is_email( $email ) ) {
42
  $sInvalidBecause = 'syntax';
43
  }
44
  else {
45
+ $apiToken = $this->getCon()
46
+ ->getModule_License()
47
+ ->getWpHashesTokenManager()
48
+ ->getToken();
49
+ if ( !empty( $apiToken ) ) {
50
+ $aChecks = $opts->getEmailValidationChecks();
51
+ $aVerifys = ( new Email( $apiToken ) )->getEmailVerification( $email );
52
  if ( is_array( $aVerifys ) ) {
53
  foreach ( $aVerifys as $sVerifyKey => $bIsValid ) {
54
  if ( !$bIsValid && in_array( $sVerifyKey, $aChecks ) ) {
61
  }
62
 
63
  if ( !empty( $sInvalidBecause ) ) {
64
+ $sOpt = $opts->getValidateEmailOnRegistration();
65
  $this->getCon()->fireEvent(
66
  'reg_email_invalid',
67
  [
68
  'audit' => [
69
+ 'email' => sanitize_email( $email ),
70
  'reason' => sanitize_key( $sInvalidBecause ),
71
  ],
72
  'offense_count' => $sOpt == 'log' ? 0 : 1,
src/lib/src/Modules/UserManagement/Lib/Session/UserSessionHandler.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement\Lib\Session;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Session\EntryVO;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
@@ -12,10 +12,10 @@ use FernleafSystems\Wordpress\Services\Services;
12
  class UserSessionHandler {
13
 
14
  use ModConsumer;
15
- use OneTimeExecute;
16
  use WpLoginCapture;
17
 
18
- protected function canRun() {
19
  return $this->getCon()
20
  ->getModule_Sessions()
21
  ->getDbHandler_Sessions()
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement\Lib\Session;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Session\EntryVO;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
12
  class UserSessionHandler {
13
 
14
  use ModConsumer;
15
+ use ExecOnce;
16
  use WpLoginCapture;
17
 
18
+ protected function canRun() :bool {
19
  return $this->getCon()
20
  ->getModule_Sessions()
21
  ->getDbHandler_Sessions()
src/lib/src/Modules/UserManagement/Lib/Suspend/UserSuspendController.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement\Lib\Suspend;
4
 
5
- use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Sessions\Lib\Ops\Terminate;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
@@ -11,9 +11,9 @@ use FernleafSystems\Wordpress\Services\Services;
11
  class UserSuspendController {
12
 
13
  use ModConsumer;
14
- use OneTimeExecute;
15
 
16
- protected function canRun() {
17
  /** @var UserManagement\Options $opts */
18
  $opts = $this->getOptions();
19
  return $opts->isSuspendEnabled() && $this->getCon()->isPremiumActive();
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement\Lib\Suspend;
4
 
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Sessions\Lib\Ops\Terminate;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
11
  class UserSuspendController {
12
 
13
  use ModConsumer;
14
+ use ExecOnce;
15
 
16
+ protected function canRun() :bool {
17
  /** @var UserManagement\Options $opts */
18
  $opts = $this->getOptions();
19
  return $opts->isSuspendEnabled() && $this->getCon()->isPremiumActive();
src/lib/src/Scans/Apc/ResultItem.php CHANGED
@@ -1,16 +1,14 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Apc;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base;
6
-
7
  /**
8
  * Class ResultItem
9
- * @property string slug
10
- * @property string context
11
- * @property int last_updated_at
12
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Apc
 
 
 
13
  */
14
- class ResultItem extends Base\BaseResultItem {
15
 
16
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Apc;
4
 
 
 
5
  /**
6
  * Class ResultItem
 
 
 
7
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Apc
8
+ * @property string $slug
9
+ * @property string $context
10
+ * @property int $last_updated_at
11
  */
12
+ class ResultItem extends \FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseResultItem {
13
 
14
  }
src/lib/src/Scans/Apc/Scan.php CHANGED
@@ -22,7 +22,7 @@ class Scan extends Shield\Scans\Base\BaseScan {
22
  $aNewItems = [];
23
  if ( $oTempRs->hasItems() ) {
24
  foreach ( $oTempRs->getAllItems() as $oItem ) {
25
- $aNewItems[] = $oItem->getRawDataAsArray();
26
  }
27
  }
28
  $oAction->results = $aNewItems;
22
  $aNewItems = [];
23
  if ( $oTempRs->hasItems() ) {
24
  foreach ( $oTempRs->getAllItems() as $oItem ) {
25
+ $aNewItems[] = $oItem->getRawData();
26
  }
27
  }
28
  $oAction->results = $aNewItems;
src/lib/src/Scans/Apc/ScanActionVO.php CHANGED
@@ -1,15 +1,13 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Apc;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
-
7
  /**
8
  * Class ScanActionVO
9
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Apc
10
  * @property int $abandoned_limit
11
  */
12
- class ScanActionVO extends Shield\Scans\Base\BaseScanActionVO {
13
 
14
  const QUEUE_GROUP_SIZE_LIMIT = 3;
15
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Apc;
4
 
 
 
5
  /**
6
  * Class ScanActionVO
7
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Apc
8
  * @property int $abandoned_limit
9
  */
10
+ class ScanActionVO extends \FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseScanActionVO {
11
 
12
  const QUEUE_GROUP_SIZE_LIMIT = 3;
13
  }
src/lib/src/Scans/Base/BaseMergeItems.php CHANGED
@@ -15,7 +15,7 @@ class BaseMergeItems {
15
  * @return BaseResultItem
16
  */
17
  public function mergeItemTo( $oBaseItem, $oMergeItem ) {
18
- foreach ( $oMergeItem->getRawDataAsArray() as $sKey => $mVal ) {
19
  $oBaseItem->{$sKey} = $mVal;
20
  }
21
  return $oBaseItem;
15
  * @return BaseResultItem
16
  */
17
  public function mergeItemTo( $oBaseItem, $oMergeItem ) {
18
+ foreach ( $oMergeItem->getRawData() as $sKey => $mVal ) {
19
  $oBaseItem->{$sKey} = $mVal;
20
  }
21
  return $oBaseItem;
src/lib/src/Scans/Base/BaseResultItem.php CHANGED
@@ -1,8 +1,8 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Base;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
 
7
  /**
8
  * Class BaseResultItem
@@ -14,26 +14,20 @@ use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
14
  */
15
  class BaseResultItem {
16
 
17
- use StdClassAdapter;
18
 
19
- /**
20
- * @return bool
21
- */
22
- public function isReady() {
23
  return false;
24
  }
25
 
26
- /**
27
- * @return string
28
- */
29
- public function generateHash() {
30
- return md5( json_encode( $this->getRawDataAsArray() ) );
31
  }
32
 
33
  /**
34
  * @return mixed
35
  */
36
  public function getData() {
37
- return isset( $this->data ) ? $this->data : $this->getRawDataAsArray();
38
  }
39
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Base;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynProperties;
6
 
7
  /**
8
  * Class BaseResultItem
14
  */
15
  class BaseResultItem {
16
 
17
+ use DynProperties;
18
 
19
+ public function isReady() :bool {
 
 
 
20
  return false;
21
  }
22
 
23
+ public function generateHash() :string {
24
+ return md5( json_encode( $this->getRawData() ) );
 
 
 
25
  }
26
 
27
  /**
28
  * @return mixed
29
  */
30
  public function getData() {
31
+ return $this->data ?? $this->getRawData();
32
  }
33
  }
src/lib/src/Scans/Base/BaseScanActionVO.php CHANGED
@@ -1,8 +1,8 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Base;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\Table\BaseEntryFormatter;
7
 
8
  /**
@@ -20,7 +20,7 @@ use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\Table\BaseEntryFormatter;
20
  */
21
  abstract class BaseScanActionVO {
22
 
23
- use StdClassAdapter;
24
 
25
  const QUEUE_GROUP_SIZE_LIMIT = 1;
26
  const DEFAULT_SLEEP_SECONDS = 0;
@@ -29,32 +29,29 @@ abstract class BaseScanActionVO {
29
  * @return BaseResultItem|mixed
30
  */
31
  public function getNewResultItem() {
32
- $sClass = $this->getScanNamespace().'ResultItem';
33
- return new $sClass();
34
  }
35
 
36
  /**
37
  * @return BaseResultsSet|mixed
38
  */
39
  public function getNewResultsSet() {
40
- $sClass = $this->getScanNamespace().'ResultsSet';
41
- return new $sClass();
42
  }
43
 
44
  /**
45
  * @return BaseEntryFormatter|mixed
46
  */
47
  public function getTableEntryFormatter() {
48
- $sClass = $this->getScanNamespace().'Table\\EntryFormatter';
49
- /** @var BaseEntryFormatter $oF */
50
- $oF = new $sClass();
51
- return $oF->setScanActionVO( $this );
52
  }
53
 
54
- /**
55
- * @return string
56
- */
57
- public function getScanNamespace() {
58
  try {
59
  $namespace = ( new \ReflectionClass( $this ) )->getNamespaceName();
60
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Base;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynProperties;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\Table\BaseEntryFormatter;
7
 
8
  /**
20
  */
21
  abstract class BaseScanActionVO {
22
 
23
+ use DynProperties;
24
 
25
  const QUEUE_GROUP_SIZE_LIMIT = 1;
26
  const DEFAULT_SLEEP_SECONDS = 0;
29
  * @return BaseResultItem|mixed
30
  */
31
  public function getNewResultItem() {
32
+ $class = $this->getScanNamespace().'ResultItem';
33
+ return new $class();
34
  }
35
 
36
  /**
37
  * @return BaseResultsSet|mixed
38
  */
39
  public function getNewResultsSet() {
40
+ $class = $this->getScanNamespace().'ResultsSet';
41
+ return new $class();
42
  }
43
 
44
  /**
45
  * @return BaseEntryFormatter|mixed
46
  */
47
  public function getTableEntryFormatter() {
48
+ $class = $this->getScanNamespace().'Table\\EntryFormatter';
49
+ /** @var BaseEntryFormatter $formatter */
50
+ $formatter = new $class();
51
+ return $formatter->setScanActionVO( $this );
52
  }
53
 
54
+ public function getScanNamespace() :string {
 
 
 
55
  try {
56
  $namespace = ( new \ReflectionClass( $this ) )->getNamespaceName();
57
  }
src/lib/src/Scans/Base/Files/BaseFileMapScan.php CHANGED
@@ -24,7 +24,7 @@ abstract class BaseFileMapScan extends Base\BaseScan {
24
  $newItems = [];
25
  if ( $oTempRs->hasItems() ) {
26
  foreach ( $oTempRs->getAllItems() as $oItem ) {
27
- $newItems[] = $oItem->getRawDataAsArray();
28
  }
29
  }
30
  $action->results = $newItems;
24
  $newItems = [];
25
  if ( $oTempRs->hasItems() ) {
26
  foreach ( $oTempRs->getAllItems() as $oItem ) {
27
+ $newItems[] = $oItem->getRawData();
28
  }
29
  }
30
  $action->results = $newItems;
src/lib/src/Scans/Base/Table/BaseEntryFormatter.php CHANGED
@@ -61,7 +61,7 @@ abstract class BaseEntryFormatter {
61
  * @return array
62
  */
63
  protected function getBaseData() {
64
- return $this->getEntryVO()->getRawDataAsArray();
65
  }
66
 
67
  /**
61
  * @return array
62
  */
63
  protected function getBaseData() {
64
+ return $this->getEntryVO()->getRawData();
65
  }
66
 
67
  /**
src/lib/src/Scans/Base/Utilities/ItemActionHandler.php CHANGED
@@ -63,9 +63,9 @@ abstract class ItemActionHandler {
63
 
64
  /** @var HackGuard\ModCon $mod */
65
  $mod = $this->getMod();
66
- /** @var Scanner\Update $oUp */
67
- $oUp = $mod->getDbHandler_ScanResults()->getQueryUpdater();
68
- if ( !$oUp->setIgnored( $oEntry ) ) {
69
  throw new \Exception( 'Item could not be ignored at this time.' );
70
  }
71
 
63
 
64
  /** @var HackGuard\ModCon $mod */
65
  $mod = $this->getMod();
66
+ /** @var Scanner\Update $updater */
67
+ $updater = $mod->getDbHandler_ScanResults()->getQueryUpdater();
68
+ if ( !$updater->setIgnored( $oEntry ) ) {
69
  throw new \Exception( 'Item could not be ignored at this time.' );
70
  }
71
 
src/lib/src/Scans/Mal/ResultItem.php CHANGED
@@ -1,32 +1,24 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Mal;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base;
6
-
7
  /**
8
  * Class ResultItem
 
9
  * @property string $path_full
10
  * @property string $path_fragment - relative to ABSPATH
11
  * @property bool $is_mal
12
  * @property string $mal_sig
13
  * @property int[] $file_lines
14
  * @property int $fp_confidence - false positive confidence level
15
- * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Mal
16
  */
17
- class ResultItem extends Base\BaseResultItem {
18
 
19
- /**
20
- * @return string
21
- */
22
- public function generateHash() {
23
  return md5( $this->path_full );
24
  }
25
 
26
- /**
27
- * @return bool
28
- */
29
- public function isReady() {
30
  return !empty( $this->path_full ) && !empty( $this->md5_file_wp ) && !empty( $this->path_fragment );
31
  }
32
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Mal;
4
 
 
 
5
  /**
6
  * Class ResultItem
7
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Mal
8
  * @property string $path_full
9
  * @property string $path_fragment - relative to ABSPATH
10
  * @property bool $is_mal
11
  * @property string $mal_sig
12
  * @property int[] $file_lines
13
  * @property int $fp_confidence - false positive confidence level
 
14
  */
15
+ class ResultItem extends \FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseResultItem {
16
 
17
+ public function generateHash() :string {
 
 
 
18
  return md5( $this->path_full );
19
  }
20
 
21
+ public function isReady() :bool {
 
 
 
22
  return !empty( $this->path_full ) && !empty( $this->md5_file_wp ) && !empty( $this->path_fragment );
23
  }
24
  }
src/lib/src/Scans/Mal/ScanActionVO.php CHANGED
@@ -2,8 +2,6 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Mal;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseScanActionVO;
6
-
7
  /**
8
  * Class ScanActionVO
9
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Mal
@@ -15,7 +13,7 @@ use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseScanActionVO;
15
  * @property string[] $patterns_simple
16
  * @property int $confidence_threshold
17
  */
18
- class ScanActionVO extends BaseScanActionVO {
19
 
20
  const QUEUE_GROUP_SIZE_LIMIT = 50;
21
  const DEFAULT_SLEEP_SECONDS = 1;
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Mal;
4
 
 
 
5
  /**
6
  * Class ScanActionVO
7
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Mal
13
  * @property string[] $patterns_simple
14
  * @property int $confidence_threshold
15
  */
16
+ class ScanActionVO extends \FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseScanActionVO {
17
 
18
  const QUEUE_GROUP_SIZE_LIMIT = 50;
19
  const DEFAULT_SLEEP_SECONDS = 1;
src/lib/src/Scans/Ptg/ResultItem.php CHANGED
@@ -1,9 +1,7 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Ptg;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base;
6
-
7
  /**
8
  * Class ResultItem
9
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Ptg
@@ -15,12 +13,9 @@ use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base;
15
  * @property string $is_different
16
  * @property string $is_missing
17
  */
18
- class ResultItem extends Base\BaseResultItem {
19
 
20
- /**
21
- * @return string
22
- */
23
- public function generateHash() {
24
  return md5( $this->path_full );
25
  }
26
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Ptg;
4
 
 
 
5
  /**
6
  * Class ResultItem
7
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Ptg
13
  * @property string $is_different
14
  * @property string $is_missing
15
  */
16
+ class ResultItem extends \FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseResultItem {
17
 
18
+ public function generateHash() :string {
 
 
 
19
  return md5( $this->path_full );
20
  }
21
  }
src/lib/src/Scans/Ptg/ScanActionVO.php CHANGED
@@ -1,16 +1,14 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Ptg;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
-
7
  /**
8
  * Class ScanActionVO
9
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Ptg
10
  * @property string[] $scan_root_dirs
11
  * @property string[] $file_exts
12
  */
13
- class ScanActionVO extends Shield\Scans\Base\BaseScanActionVO {
14
 
15
  const CONTEXT_PLUGINS = 'plugins';
16
  const CONTEXT_THEMES = 'themes';
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Ptg;
4
 
 
 
5
  /**
6
  * Class ScanActionVO
7
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Ptg
8
  * @property string[] $scan_root_dirs
9
  * @property string[] $file_exts
10
  */
11
+ class ScanActionVO extends \FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseScanActionVO {
12
 
13
  const CONTEXT_PLUGINS = 'plugins';
14
  const CONTEXT_THEMES = 'themes';
src/lib/src/Scans/Ufc/ResultItem.php CHANGED
@@ -1,21 +1,16 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Ufc;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base;
6
-
7
  /**
8
  * Class ResultItem
9
- * @property string path_full
10
- * @property string path_fragment
11
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Ufc
 
 
12
  */
13
- class ResultItem extends Base\BaseResultItem {
14
 
15
- /**
16
- * @return string
17
- */
18
- public function generateHash() {
19
  return md5( $this->path_full );
20
  }
21
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Ufc;
4
 
 
 
5
  /**
6
  * Class ResultItem
 
 
7
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Ufc
8
+ * @property string $path_full
9
+ * @property string $path_fragment
10
  */
11
+ class ResultItem extends \FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseResultItem {
12
 
13
+ public function generateHash() :string {
 
 
 
14
  return md5( $this->path_full );
15
  }
16
  }
src/lib/src/Scans/Ufc/ScanActionVO.php CHANGED
@@ -1,16 +1,14 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Ufc;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseScanActionVO;
6
-
7
  /**
8
  * Class ScanActionVO
9
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Ufc
10
  * @property string[] $scan_dirs
11
  * @property string[] $exclusions
12
  */
13
- class ScanActionVO extends BaseScanActionVO {
14
 
15
  const QUEUE_GROUP_SIZE_LIMIT = 100;
16
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Ufc;
4
 
 
 
5
  /**
6
  * Class ScanActionVO
7
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Ufc
8
  * @property string[] $scan_dirs
9
  * @property string[] $exclusions
10
  */
11
+ class ScanActionVO extends \FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseScanActionVO {
12
 
13
  const QUEUE_GROUP_SIZE_LIMIT = 100;
14
  }
src/lib/src/Scans/Wcf/ResultItem.php CHANGED
@@ -1,31 +1,23 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Wcf;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base;
6
-
7
  /**
8
  * Class ResultItem
9
- * @property string path_full
10
- * @property string path_fragment
11
- * @property string md5_file_wp
12
- * @property bool is_checksumfail
13
- * @property bool is_missing
14
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Wcf
 
 
 
 
 
15
  */
16
- class ResultItem extends Base\BaseResultItem {
17
 
18
- /**
19
- * @return string
20
- */
21
- public function generateHash() {
22
  return md5( $this->path_full );
23
  }
24
 
25
- /**
26
- * @return bool
27
- */
28
- public function isReady() {
29
  return !empty( $this->path_full ) && !empty( $this->md5_file_wp ) && !empty( $this->path_fragment );
30
  }
31
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Wcf;
4
 
 
 
5
  /**
6
  * Class ResultItem
 
 
 
 
 
7
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Wcf
8
+ * @property string $path_full
9
+ * @property string $path_fragment
10
+ * @property string $md5_file_wp
11
+ * @property bool $is_checksumfail
12
+ * @property bool $is_missing
13
  */
14
+ class ResultItem extends \FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseResultItem {
15
 
16
+ public function generateHash() :string {
 
 
 
17
  return md5( $this->path_full );
18
  }
19
 
20
+ public function isReady() :bool {
 
 
 
21
  return !empty( $this->path_full ) && !empty( $this->md5_file_wp ) && !empty( $this->path_fragment );
22
  }
23
  }
src/lib/src/Scans/Wcf/ScanActionVO.php CHANGED
@@ -1,16 +1,14 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Wcf;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseScanActionVO;
6
-
7
  /**
8
  * Class ScanActionVO
9
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Wcf
10
  * @property string $exclusions_missing_regex
11
  * @property string $exclusions_files_regex
12
  */
13
- class ScanActionVO extends BaseScanActionVO {
14
 
15
  const QUEUE_GROUP_SIZE_LIMIT = 100;
16
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Wcf;
4
 
 
 
5
  /**
6
  * Class ScanActionVO
7
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Wcf
8
  * @property string $exclusions_missing_regex
9
  * @property string $exclusions_files_regex
10
  */
11
+ class ScanActionVO extends \FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseScanActionVO {
12
 
13
  const QUEUE_GROUP_SIZE_LIMIT = 100;
14
  }
src/lib/src/Scans/Wpv/ResultItem.php CHANGED
@@ -1,31 +1,24 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Wpv;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Scans\Wpv\WpVulnDb\WpVulnVO;
7
 
8
  /**
9
  * Class ResultItem
10
- * @property string slug
11
- * @property string context
12
- * @property int wpvuln_id
13
- * @property array wpvuln_vo
14
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Wpv
 
 
 
 
15
  */
16
- class ResultItem extends Base\BaseResultItem {
17
 
18
- /**
19
- * @return string
20
- */
21
- public function generateHash() {
22
  return md5( $this->slug.$this->wpvuln_id );
23
  }
24
 
25
- /**
26
- * @return WpVulnVO
27
- */
28
- public function getWpVulnVo() {
29
  return ( new WpVulnVO() )->applyFromArray( $this->wpvuln_vo );
30
  }
31
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Wpv;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Scans\Wpv\WpVulnDb\WpVulnVO;
6
 
7
  /**
8
  * Class ResultItem
 
 
 
 
9
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Wpv
10
+ * @property string $slug
11
+ * @property string $context
12
+ * @property int $wpvuln_id
13
+ * @property array $wpvuln_vo
14
  */
15
+ class ResultItem extends \FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseResultItem {
16
 
17
+ public function generateHash() :string {
 
 
 
18
  return md5( $this->slug.$this->wpvuln_id );
19
  }
20
 
21
+ public function getWpVulnVo() :WpVulnVO {
 
 
 
22
  return ( new WpVulnVO() )->applyFromArray( $this->wpvuln_vo );
23
  }
24
  }
src/lib/src/Scans/Wpv/Scan.php CHANGED
@@ -23,8 +23,8 @@ class Scan extends Shield\Scans\Base\BaseScan {
23
 
24
  $aNewItems = [];
25
  if ( $oTempRs->hasItems() ) {
26
- foreach ( $oTempRs->getAllItems() as $oItem ) {
27
- $aNewItems[] = $oItem->getRawDataAsArray();
28
  }
29
  }
30
  $oAction->results = $aNewItems;
@@ -73,7 +73,7 @@ class Scan extends Shield\Scans\Base\BaseScan {
73
  $oItem->slug = $sFile;
74
  $oItem->context = $sContext;
75
  $oItem->wpvuln_id = $oVo->id;
76
- $oItem->wpvuln_vo = $oVo->getRawDataAsArray();
77
  $oResultsSet->addItem( $oItem );
78
  }
79
 
23
 
24
  $aNewItems = [];
25
  if ( $oTempRs->hasItems() ) {
26
+ foreach ( $oTempRs->getAllItems() as $item ) {
27
+ $aNewItems[] = $item->getRawData();
28
  }
29
  }
30
  $oAction->results = $aNewItems;
73
  $oItem->slug = $sFile;
74
  $oItem->context = $sContext;
75
  $oItem->wpvuln_id = $oVo->id;
76
+ $oItem->wpvuln_vo = $oVo->getRawData();
77
  $oResultsSet->addItem( $oItem );
78
  }
79
 
src/lib/src/Scans/Wpv/ScanActionVO.php CHANGED
@@ -1,13 +1,11 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Wpv;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
-
7
  /**
8
  * Class ScanActionVO
9
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Wpv
10
  */
11
- class ScanActionVO extends Shield\Scans\Base\BaseScanActionVO {
12
 
13
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Wpv;
4
 
 
 
5
  /**
6
  * Class ScanActionVO
7
  * @package FernleafSystems\Wordpress\Plugin\Shield\Scans\Wpv
8
  */
9
+ class ScanActionVO extends \FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseScanActionVO {
10
 
11
  }
src/lib/src/Scans/Wpv/WpVulnDb/WpVulnVO.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Wpv\WpVulnDb;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\DynamicPropertiesClass;
6
 
7
  /**
8
  * Class WpVulnVO
@@ -17,7 +17,7 @@ use FernleafSystems\Utilities\Data\Adapter\DynamicPropertiesClass;
17
  * @property int $created_at
18
  * @property int $published_date
19
  */
20
- class WpVulnVO extends DynamicPropertiesClass {
21
 
22
  const URL_BASE = 'https://wpscan.com/vulnerability/%s';
23
 
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Scans\Wpv\WpVulnDb;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
 
7
  /**
8
  * Class WpVulnVO
17
  * @property int $created_at
18
  * @property int $published_date
19
  */
20
+ class WpVulnVO extends DynPropertiesClass {
21
 
22
  const URL_BASE = 'https://wpscan.com/vulnerability/%s';
23
 
src/lib/src/ShieldNetApi/Common/BaseApi.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\ShieldNetApi\Common;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
  use FernleafSystems\Wordpress\Services\Services;
7
  use FernleafSystems\Wordpress\Services\Utilities\HttpRequest;
8
 
@@ -16,11 +16,8 @@ use FernleafSystems\Wordpress\Services\Utilities\HttpRequest;
16
  * @property array $params_body
17
  * @property array $params_query
18
  */
19
- abstract class BaseApi {
20
 
21
- use StdClassAdapter {
22
- __get as __adapterGet;
23
- }
24
  const DEFAULT_URL_STUB = '';
25
  const API_ACTION = '';
26
 
@@ -75,36 +72,36 @@ abstract class BaseApi {
75
  }
76
 
77
  /**
78
- * @param string $sProperty
79
  * @return mixed
80
  */
81
- public function __get( $sProperty ) {
82
 
83
- $mValue = $this->__adapterGet( $sProperty );
84
 
85
- switch ( $sProperty ) {
86
 
87
  case 'params_query':
88
  case 'params_body':
89
- if ( !is_array( $mValue ) ) {
90
- $mValue = [];
91
  }
92
  break;
93
 
94
  case 'request_method':
95
- $mValue = empty( $mValue ) ? 'get' : strtolower( $mValue );
96
  break;
97
 
98
  case 'lookup_url_stub':
99
- if ( empty( $mValue ) ) {
100
- $mValue = static::DEFAULT_URL_STUB;
101
  }
102
- $mValue = rtrim( $mValue, '/' );
103
  break;
104
 
105
  case 'timeout':
106
- if ( empty( $mValue ) || !is_numeric( $mValue ) ) {
107
- $mValue = 60;
108
  }
109
  break;
110
 
@@ -112,6 +109,6 @@ abstract class BaseApi {
112
  break;
113
  }
114
 
115
- return $mValue;
116
  }
117
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\ShieldNetApi\Common;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
  use FernleafSystems\Wordpress\Services\Services;
7
  use FernleafSystems\Wordpress\Services\Utilities\HttpRequest;
8
 
16
  * @property array $params_body
17
  * @property array $params_query
18
  */
19
+ abstract class BaseApi extends DynPropertiesClass {
20
 
 
 
 
21
  const DEFAULT_URL_STUB = '';
22
  const API_ACTION = '';
23
 
72
  }
73
 
74
  /**
75
+ * @param string $key
76
  * @return mixed
77
  */
78
+ public function __get( string $key ) {
79
 
80
+ $value = parent::__get( $key );
81
 
82
+ switch ( $key ) {
83
 
84
  case 'params_query':
85
  case 'params_body':
86
+ if ( !is_array( $value ) ) {
87
+ $value = [];
88
  }
89
  break;
90
 
91
  case 'request_method':
92
+ $value = empty( $value ) ? 'get' : strtolower( $value );
93
  break;
94
 
95
  case 'lookup_url_stub':
96
+ if ( empty( $value ) ) {
97
+ $value = static::DEFAULT_URL_STUB;
98
  }
99
+ $value = rtrim( $value, '/' );
100
  break;
101
 
102
  case 'timeout':
103
+ if ( empty( $value ) || !is_numeric( $value ) ) {
104
+ $value = 60;
105
  }
106
  break;
107
 
109
  break;
110
  }
111
 
112
+ return $value;
113
  }
114
  }
src/lib/src/ShieldNetApi/Common/BaseShieldNetApi.php CHANGED
@@ -17,31 +17,31 @@ class BaseShieldNetApi extends BaseApi {
17
  const DEFAULT_URL_STUB = 'https://net.getshieldsecurity.com/wp-json/apto-snapi/v1';
18
 
19
  /**
20
- * @param string $sProperty
21
  * @return mixed
22
  */
23
- public function __get( $sProperty ) {
24
 
25
- $mValue = parent::__get( $sProperty );
26
 
27
- switch ( $sProperty ) {
28
 
29
  case 'params_query':
30
  if ( $this->request_method == 'get' ) {
31
- $mValue = array_merge( $this->shield_net_params, $mValue );
32
  }
33
  break;
34
 
35
  case 'params_body':
36
  if ( $this->request_method == 'post' ) {
37
- $mValue = array_merge( $this->shield_net_params, $mValue );
38
  }
39
  break;
40
 
41
  case 'shield_net_params':
42
- if ( !is_array( $mValue ) ) {
43
- $mValue = $this->getShieldNetApiParams();
44
- $this->shield_net_params = $mValue;
45
  }
46
  break;
47
 
@@ -49,7 +49,7 @@ class BaseShieldNetApi extends BaseApi {
49
  break;
50
  }
51
 
52
- return $mValue;
53
  }
54
 
55
  /**
17
  const DEFAULT_URL_STUB = 'https://net.getshieldsecurity.com/wp-json/apto-snapi/v1';
18
 
19
  /**
20
+ * @param string $key
21
  * @return mixed
22
  */
23
+ public function __get( string $key ) {
24
 
25
+ $value = parent::__get( $key );
26
 
27
+ switch ( $key ) {
28
 
29
  case 'params_query':
30
  if ( $this->request_method == 'get' ) {
31
+ $value = array_merge( $this->shield_net_params, $value );
32
  }
33
  break;
34
 
35
  case 'params_body':
36
  if ( $this->request_method == 'post' ) {
37
+ $value = array_merge( $this->shield_net_params, $value );
38
  }
39
  break;
40
 
41
  case 'shield_net_params':
42
+ if ( !is_array( $value ) ) {
43
+ $value = $this->getShieldNetApiParams();
44
+ $this->shield_net_params = $value;
45
  }
46
  break;
47
 
49
  break;
50
  }
51
 
52
+ return $value;
53
  }
54
 
55
  /**
src/lib/src/ShieldNetApi/FileLocker/DecryptFile.php CHANGED
@@ -8,7 +8,6 @@ use FernleafSystems\Wordpress\Services\Utilities\Encrypt\OpenSslEncryptVo;
8
 
9
  class DecryptFile extends BaseShieldNetApi {
10
 
11
- use ModConsumer;
12
  const API_ACTION = 'filelocker/decrypt';
13
 
14
  /**
@@ -17,7 +16,7 @@ class DecryptFile extends BaseShieldNetApi {
17
  * @return string|null
18
  */
19
  public function retrieve( OpenSslEncryptVo $oOpenSslVO, $nPublicKeyId ) {
20
- $sContent = null;
21
 
22
  $this->request_method = 'post';
23
  $this->params_body = [
@@ -26,10 +25,10 @@ class DecryptFile extends BaseShieldNetApi {
26
  'sealed_pass' => $oOpenSslVO->sealed_password,
27
  ];
28
 
29
- $aRaw = $this->sendReq();
30
- if ( is_array( $aRaw ) && !empty( $aRaw[ 'data' ] ) ) {
31
- $sContent = base64_decode( $aRaw[ 'data' ][ 'opened_data' ] );
32
  }
33
- return $sContent;
34
  }
35
  }
8
 
9
  class DecryptFile extends BaseShieldNetApi {
10
 
 
11
  const API_ACTION = 'filelocker/decrypt';
12
 
13
  /**
16
  * @return string|null
17
  */
18
  public function retrieve( OpenSslEncryptVo $oOpenSslVO, $nPublicKeyId ) {
19
+ $content = null;
20
 
21
  $this->request_method = 'post';
22
  $this->params_body = [
25
  'sealed_pass' => $oOpenSslVO->sealed_password,
26
  ];
27
 
28
+ $raw = $this->sendReq();
29
+ if ( is_array( $raw ) && !empty( $raw[ 'data' ] ) ) {
30
+ $content = base64_decode( $raw[ 'data' ][ 'opened_data' ] );
31
  }
32
+ return $content;
33
  }
34
  }
src/lib/src/ShieldNetApi/FileLocker/GetPublicKey.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\ShieldNetApi\FileLocker;
4
 
@@ -12,11 +12,11 @@ class GetPublicKey extends BaseShieldNetApi {
12
  * @return array|null
13
  */
14
  public function retrieve() {
15
- $aKey = null;
16
- $aRaw = $this->sendReq();
17
- if ( is_array( $aRaw ) && !empty( $aRaw[ 'data' ][ 'key_id' ] ) ) {
18
- $aKey[ $aRaw[ 'data' ][ 'key_id' ] ] = $aRaw[ 'data' ][ 'pub_key' ];
19
  }
20
- return $aKey;
21
  }
22
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\ShieldNetApi\FileLocker;
4
 
12
  * @return array|null
13
  */
14
  public function retrieve() {
15
+ $key = null;
16
+ $raw = $this->sendReq();
17
+ if ( is_array( $raw ) && !empty( $raw[ 'data' ][ 'key_id' ] ) ) {
18
+ $key[ $raw[ 'data' ][ 'key_id' ] ] = $raw[ 'data' ][ 'pub_key' ];
19
  }
20
+ return $key;
21
  }
22
  }
src/lib/src/ShieldNetApi/Handshake/Verify.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\ShieldNetApi\Handshake;
4
 
@@ -8,11 +8,8 @@ class Verify extends Common\BaseShieldNetApi {
8
 
9
  const API_ACTION = 'handshake/verify';
10
 
11
- /**
12
- * @return bool
13
- */
14
- public function run() {
15
- $aRaw = $this->sendReq();
16
- return is_array( $aRaw ) && !empty( $aRaw[ 'data' ][ 'success' ] );
17
  }
18
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\ShieldNetApi\Handshake;
4
 
8
 
9
  const API_ACTION = 'handshake/verify';
10
 
11
+ public function run() :bool {
12
+ $raw = $this->sendReq();
13
+ return is_array( $raw ) && !empty( $raw[ 'data' ][ 'success' ] );
 
 
 
14
  }
15
  }
src/lib/src/ShieldNetApi/HandshakingNonce.php CHANGED
@@ -9,32 +9,25 @@ class HandshakingNonce {
9
 
10
  use ModConsumer;
11
 
12
- /**
13
- * @return string
14
- */
15
- public function create() {
16
- $aNonces = $this->getNonces();
17
 
18
- $sPass = wp_generate_password( 12, false );
19
- $aNonces[ $sPass ] = Services::Request()->ts() + 90;
20
- $this->storeNonces( $aNonces );
21
 
22
- return $sPass;
23
  }
24
 
25
- /**
26
- * @param string $sNonce
27
- * @return bool
28
- */
29
- public function verify( $sNonce ) {
30
- $aNs = $this->getNonces();
31
- $bValid = false;
32
- if ( isset( $aNs[ $sNonce ] ) ) {
33
- $bValid = Services::Request()->ts() <= $aNs[ $sNonce ];
34
- unset( $aNs[ $sNonce ] );
35
- $this->storeNonces( $aNs );
36
  }
37
- return $bValid;
38
  }
39
 
40
  /**
@@ -48,20 +41,20 @@ class HandshakingNonce {
48
 
49
  /**
50
  * Also filters out expired nonces on-save
51
- * @param int[] $aNonces
52
  * @return $this
53
  */
54
- private function storeNonces( array $aNonces ) {
55
- $oSnapiCon = $this->getCon()
56
- ->getModule_Plugin()
57
- ->getShieldNetApiController();
58
- $oSnapiCon->vo->nonces = array_filter(
59
- $aNonces,
60
- function ( $nTS ) {
61
- return $nTS > Services::Request()->ts();
62
  }
63
  );
64
- $oSnapiCon->storeVoData();
65
  return $this;
66
  }
67
  }
9
 
10
  use ModConsumer;
11
 
12
+ public function create() :string {
13
+ $nonces = $this->getNonces();
 
 
 
14
 
15
+ $pass = wp_generate_password( 12, false );
16
+ $nonces[ $pass ] = Services::Request()->ts() + 90;
17
+ $this->storeNonces( $nonces );
18
 
19
+ return $pass;
20
  }
21
 
22
+ public function verify( string $nonce ) :bool {
23
+ $nonces = $this->getNonces();
24
+ $valid = false;
25
+ if ( isset( $nonces[ $nonce ] ) ) {
26
+ $valid = Services::Request()->ts() <= $nonces[ $nonce ];
27
+ unset( $nonces[ $nonce ] );
28
+ $this->storeNonces( $nonces );
 
 
 
 
29
  }
30
+ return $valid;
31
  }
32
 
33
  /**
41
 
42
  /**
43
  * Also filters out expired nonces on-save
44
+ * @param int[] $nonces
45
  * @return $this
46
  */
47
+ private function storeNonces( array $nonces ) {
48
+ $snapiCon = $this->getCon()
49
+ ->getModule_Plugin()
50
+ ->getShieldNetApiController();
51
+ $snapiCon->vo->nonces = array_filter(
52
+ $nonces,
53
+ function ( $ts ) {
54
+ return $ts > Services::Request()->ts();
55
  }
56
  );
57
+ $snapiCon->storeVoData();
58
  return $this;
59
  }
60
  }
src/lib/src/ShieldNetApi/ShieldNetApiController.php CHANGED
@@ -1,8 +1,8 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\ShieldNetApi;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
8
  use FernleafSystems\Wordpress\Plugin\Shield\ShieldNetApi;
@@ -13,12 +13,9 @@ use FernleafSystems\Wordpress\Services\Services;
13
  * @package FernleafSystems\Wordpress\Plugin\Shield\ShieldNetApi
14
  * @property ShieldNetApiDataVO $vo
15
  */
16
- class ShieldNetApiController {
17
 
18
  use ModConsumer;
19
- use StdClassAdapter {
20
- __get as __adapterGet;
21
- }
22
 
23
  /**
24
  * Automatically throttles request because otherwise PRO-nulled versions of Shield will cause
@@ -27,28 +24,28 @@ class ShieldNetApiController {
27
  * Note To Plugin 'Null'ers:
28
  * PRO features that require handshaking wont work even if you null the plugin because our
29
  * API will always reject those requests. Don't fiddle with this function, please. You may get
30
- * away with nulling the plugin for many PRO features, but you can't null our API, sorry.
31
  * @return bool
32
  */
33
- public function canHandshake() {
34
- $nNow = Services::Request()->ts();
35
  if ( $this->vo->last_handshake_at === 0 ) {
36
 
37
- $bCanTry = $nNow - MINUTE_IN_SECONDS*5*$this->vo->handshake_fail_count
38
- > $this->vo->last_handshake_attempt_at;
39
- if ( $bCanTry ) {
40
- $bCanHandshake = ( new ShieldNetApi\Handshake\Verify() )
41
  ->setMod( $this->getMod() )
42
  ->run();
43
 
44
- if ( $bCanHandshake ) {
45
- $this->vo->last_handshake_at = $nNow;
46
  $this->vo->handshake_fail_count = 0;
47
  }
48
  else {
49
  $this->vo->handshake_fail_count++;
50
  }
51
- $this->vo->last_handshake_attempt_at = $nNow;
52
  $this->storeVoData();
53
  }
54
  }
@@ -57,29 +54,29 @@ class ShieldNetApiController {
57
  }
58
 
59
  public function storeVoData() {
60
- $this->getOptions()->setOpt( 'snapi_data', $this->vo->getRawDataAsArray() );
61
  $this->getMod()->saveModOptions();
62
  }
63
 
64
  /**
65
- * @param string $sProperty
66
  * @return mixed
67
  */
68
- public function __get( $sProperty ) {
69
- /** @var Plugin\Options $oOpts */
70
- $oOpts = $this->getOptions();
71
 
72
- $mValue = $this->__adapterGet( $sProperty );
73
 
74
- switch ( $sProperty ) {
75
 
76
  case 'vo':
77
- if ( empty( $mValue ) ) {
78
- $aData = $oOpts->getOpt( 'snapi_data', [] );
79
- $mValue = ( new ShieldNetApiDataVO() )->applyFromArray(
80
- is_array( $aData ) ? $aData : []
81
  );
82
- $this->vo = $mValue;
83
  }
84
  break;
85
 
@@ -87,6 +84,6 @@ class ShieldNetApiController {
87
  break;
88
  }
89
 
90
- return $mValue;
91
  }
92
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\ShieldNetApi;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
8
  use FernleafSystems\Wordpress\Plugin\Shield\ShieldNetApi;
13
  * @package FernleafSystems\Wordpress\Plugin\Shield\ShieldNetApi
14
  * @property ShieldNetApiDataVO $vo
15
  */
16
+ class ShieldNetApiController extends DynPropertiesClass {
17
 
18
  use ModConsumer;
 
 
 
19
 
20
  /**
21
  * Automatically throttles request because otherwise PRO-nulled versions of Shield will cause
24
  * Note To Plugin 'Null'ers:
25
  * PRO features that require handshaking wont work even if you null the plugin because our
26
  * API will always reject those requests. Don't fiddle with this function, please. You may get
27
+ * away with nulling the plugin for some PRO features, but you can't null our API, sorry.
28
  * @return bool
29
  */
30
+ public function canHandshake() :bool {
31
+ $now = Services::Request()->ts();
32
  if ( $this->vo->last_handshake_at === 0 ) {
33
 
34
+ $canAttempt = $now - MINUTE_IN_SECONDS*5*$this->vo->handshake_fail_count
35
+ > $this->vo->last_handshake_attempt_at;
36
+ if ( $canAttempt ) {
37
+ $handshakeSuccess = ( new ShieldNetApi\Handshake\Verify() )
38
  ->setMod( $this->getMod() )
39
  ->run();
40
 
41
+ if ( $handshakeSuccess ) {
42
+ $this->vo->last_handshake_at = $now;
43
  $this->vo->handshake_fail_count = 0;
44
  }
45
  else {
46
  $this->vo->handshake_fail_count++;
47
  }
48
+ $this->vo->last_handshake_attempt_at = $now;
49
  $this->storeVoData();
50
  }
51
  }
54
  }
55
 
56
  public function storeVoData() {
57
+ $this->getOptions()->setOpt( 'snapi_data', $this->vo->getRawData() );
58
  $this->getMod()->saveModOptions();
59
  }
60
 
61
  /**
62
+ * @param string $key
63
  * @return mixed
64
  */
65
+ public function __get( string $key ) {
66
+ /** @var Plugin\Options $opts */
67
+ $opts = $this->getOptions();
68
 
69
+ $value = parent::__get( $key );
70
 
71
+ switch ( $key ) {
72
 
73
  case 'vo':
74
+ if ( empty( $value ) ) {
75
+ $data = $opts->getOpt( 'snapi_data', [] );
76
+ $value = ( new ShieldNetApiDataVO() )->applyFromArray(
77
+ is_array( $data ) ? $data : []
78
  );
79
+ $this->vo = $value;
80
  }
81
  break;
82
 
84
  break;
85
  }
86
 
87
+ return $value;
88
  }
89
  }
src/lib/src/ShieldNetApi/ShieldNetApiDataVO.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\ShieldNetApi;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
 
7
  /**
8
  * Class ShieldNetApiDataVO
@@ -12,38 +12,34 @@ use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
12
  * @property int $handshake_fail_count
13
  * @property int[] $nonces
14
  */
15
- class ShieldNetApiDataVO {
16
-
17
- use StdClassAdapter {
18
- __get as __adapterGet;
19
- }
20
 
21
  /**
22
- * @param string $sProperty
23
  * @return mixed
24
  */
25
- public function __get( $sProperty ) {
26
 
27
- $mValue = $this->__adapterGet( $sProperty );
28
 
29
- switch ( $sProperty ) {
30
 
31
  case 'nonces':
32
- if ( !is_array( $mValue ) ) {
33
- $mValue = [];
34
  }
35
  break;
36
 
37
  case 'last_handshake_at':
38
  case 'last_handshake_attempt_at':
39
  case 'handshake_fail_count':
40
- $mValue = (int)$mValue;
41
  break;
42
 
43
  default:
44
  break;
45
  }
46
 
47
- return $mValue;
48
  }
49
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\ShieldNetApi;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
 
7
  /**
8
  * Class ShieldNetApiDataVO
12
  * @property int $handshake_fail_count
13
  * @property int[] $nonces
14
  */
15
+ class ShieldNetApiDataVO extends DynPropertiesClass {
 
 
 
 
16
 
17
  /**
18
+ * @param string $key
19
  * @return mixed
20
  */
21
+ public function __get( string $key ) {
22
 
23
+ $value = parent::__get( $key );
24
 
25
+ switch ( $key ) {
26
 
27
  case 'nonces':
28
+ if ( !is_array( $value ) ) {
29
+ $value = [];
30
  }
31
  break;
32
 
33
  case 'last_handshake_at':
34
  case 'last_handshake_attempt_at':
35
  case 'handshake_fail_count':
36
+ $value = (int)$value;
37
  break;
38
 
39
  default:
40
  break;
41
  }
42
 
43
+ return $value;
44
  }
45
  }
src/lib/src/Tables/Build/AdminNotes.php CHANGED
@@ -15,16 +15,16 @@ class AdminNotes extends BaseBuild {
15
  * @return array[]
16
  */
17
  public function getEntriesFormatted() :array {
18
- $aEntries = [];
19
 
20
- foreach ( $this->getEntriesRaw() as $nKey => $oEntry ) {
21
- /** @var EntryVO $oEntry */
22
- $aE = $oEntry->getRawDataAsArray();
23
- $aE[ 'created_at' ] = $this->formatTimestampField( $oEntry->created_at );
24
- $aEntries[ $nKey ] = $aE;
25
  }
26
 
27
- return $aEntries;
28
  }
29
 
30
  /**
15
  * @return array[]
16
  */
17
  public function getEntriesFormatted() :array {
18
+ $entries = [];
19
 
20
+ foreach ( $this->getEntriesRaw() as $key => $entry ) {
21
+ /** @var EntryVO $entry */
22
+ $e = $entry->getRawData();
23
+ $e[ 'created_at' ] = $this->formatTimestampField( $entry->created_at );
24
+ $entries[ $key ] = $e;
25
  }
26
 
27
+ return $entries;
28
  }
29
 
30
  /**
src/lib/src/Tables/Build/AuditTrail.php CHANGED
@@ -16,53 +16,50 @@ class AuditTrail extends BaseBuild {
16
  * @return $this
17
  */
18
  protected function applyCustomQueryFilters() {
19
- $aParams = $this->getParams();
20
- /** @var Shield\Databases\AuditTrail\Select $oSelector */
21
- $oSelector = $this->getWorkingSelector();
22
 
23
- $oSelector->filterByEvent( $aParams[ 'fEvent' ] );
24
 
25
- $oIp = Services::IP();
26
  // If an IP is specified, it takes priority
27
- if ( $oIp->isValidIp( $aParams[ 'fIp' ] ) ) {
28
- $oSelector->filterByIp( $aParams[ 'fIp' ] );
29
  }
30
- elseif ( $aParams[ 'fExcludeYou' ] == 'Y' ) {
31
- $oSelector->filterByNotIp( $oIp->getRequestIp() );
32
  }
33
 
34
  /**
35
  * put this date stuff in the base so we can filter anything
36
  */
37
- if ( !empty( $aParams[ 'fDateFrom' ] ) && preg_match( '#^\d{4}-\d{2}-\d{2}$#', $aParams[ 'fDateFrom' ] ) ) {
38
- $aParts = explode( '-', $aParams[ 'fDateFrom' ] );
39
- $sTs = Services::Request()->carbon()
40
- ->setDate( $aParts[ 0 ], $aParts[ 1 ], $aParts[ 2 ] )
41
- ->setTime( 0, 0 )
42
- ->timestamp;
43
- $oSelector->filterByCreatedAt( $sTs, '>' );
44
  }
45
 
46
- if ( !empty( $aParams[ 'fDateTo' ] ) && preg_match( '#^\d{4}-\d{2}-\d{2}$#', $aParams[ 'fDateTo' ] ) ) {
47
- $aParts = explode( '-', $aParams[ 'fDateTo' ] );
48
- $sTs = Services::Request()->carbon()
49
- ->setDate( $aParts[ 0 ], $aParts[ 1 ], $aParts[ 2 ] )
50
- ->setTime( 0, 0 )
51
- ->addDay()
52
- ->timestamp;
53
- $oSelector->filterByCreatedAt( $sTs, '<' );
54
  }
55
 
56
  // if username is provided, this takes priority over "logged-in" (even if it's invalid)
57
- if ( !empty( $aParams[ 'fUsername' ] ) ) {
58
- $oSelector->filterByUsername( $aParams[ 'fUsername' ] );
59
  }
60
- elseif ( $aParams[ 'fLoggedIn' ] >= 0 ) {
61
- $oSelector->filterByIsLoggedIn( $aParams[ 'fLoggedIn' ] );
62
  }
63
 
64
- $oSelector->setOrderBy( 'updated_at', 'DESC', true )
65
- ->setOrderBy( 'created_at' );
66
 
67
  return $this;
68
  }
@@ -83,80 +80,75 @@ class AuditTrail extends BaseBuild {
83
  * @return array[]
84
  */
85
  public function getEntriesFormatted() :array {
86
- $aEntries = [];
87
 
88
  $srvIP = Services::IP();
89
  $you = $srvIP->getRequestIp();
90
- $oCon = $this->getCon();
91
- foreach ( $this->getEntriesRaw() as $nKey => $oEntry ) {
92
- /** @var Shield\Databases\AuditTrail\EntryVO $oEntry */
93
 
94
- $sMsg = 'Audit message could not be retrieved';
95
- if ( empty( $oEntry->message ) ) {
96
  /**
97
  * To cater for the contexts that don't refer to a module, but rather a context
98
  * with the Audit Trail module
99
  */
100
- $mod = $oCon->getModule( $oEntry->context );
101
  if ( empty( $mod ) ) {
102
- $mod = $oCon->getModule_AuditTrail();
103
- }
104
- $oStrings = $mod->getStrings();
105
-
106
- if ( $oStrings instanceof Shield\Modules\Base\Strings ) {
107
- $sMsg = stripslashes( sanitize_textarea_field(
108
- vsprintf(
109
- implode( "\n", $oStrings->getAuditMessage( $oEntry->event ) ),
110
- $oEntry->meta
111
- )
112
- ) );
113
  }
 
 
 
 
114
  }
115
  else {
116
- $sMsg = $oEntry->message;
117
  }
118
 
119
- if ( !isset( $aEntries[ $oEntry->rid ] ) ) {
120
- $aE = $oEntry->getRawDataAsArray();
121
- $aE[ 'meta' ] = $oEntry->meta;
122
- $aE[ 'event' ] = str_replace( '_', ' ', sanitize_text_field( $oEntry->event ) );
123
- $aE[ 'message' ] = $sMsg;
124
- $aE[ 'created_at' ] = $this->formatTimestampField( $oEntry->created_at );
125
- if ( $oEntry->wp_username == '-' ) {
126
- $aE[ 'wp_username' ] = __( 'Not logged-in', 'wp-simple-firewall' );
127
  }
128
 
129
  try {
130
- $aE[ 'is_you' ] = $srvIP->checkIp( $you, $oEntry->ip );
131
  }
132
  catch ( \Exception $e ) {
133
- $aE[ 'is_you' ] = false;
134
  }
135
 
136
- if ( empty( $oEntry->ip ) ) {
137
- $aE[ 'ip' ] = '';
138
  }
139
  else {
140
- $aE[ 'ip' ] = sprintf( '%s%s',
141
- $this->getIpAnalysisLink( $oEntry->ip ),
142
- $aE[ 'is_you' ] ? ' <small>('.__( 'You', 'wp-simple-firewall' ).')</small>' : ''
143
  );
144
  }
145
  }
146
  else {
147
- $aE = $aEntries[ $oEntry->rid ];
148
- $aE[ 'message' ] .= "\n".$sMsg;
149
- $aE[ 'category' ] = max( $aE[ 'category' ], $oEntry->category );
 
150
  }
151
 
152
- if ( $oEntry->count > 1 ) {
153
- $aE[ 'message' ] = $sMsg."\n"
154
- .sprintf( __( 'This event repeated %s times in the last 24hrs.', 'wp-simple-firewall' ), $oEntry->count );
155
  }
156
 
157
- $aEntries[ $oEntry->rid ] = $aE;
158
  }
159
- return $aEntries;
160
  }
161
 
162
  /**
16
  * @return $this
17
  */
18
  protected function applyCustomQueryFilters() {
19
+ $params = $this->getParams();
20
+ /** @var Shield\Databases\AuditTrail\Select $selector */
21
+ $selector = $this->getWorkingSelector();
22
 
23
+ $selector->filterByEvent( $params[ 'fEvent' ] );
24
 
 
25
  // If an IP is specified, it takes priority
26
+ if ( Services::IP()->isValidIp( $params[ 'fIp' ] ) ) {
27
+ $selector->filterByIp( $params[ 'fIp' ] );
28
  }
29
+ elseif ( $params[ 'fExcludeYou' ] == 'Y' ) {
30
+ $selector->filterByNotIp( Services::IP()->getRequestIp() );
31
  }
32
 
33
  /**
34
  * put this date stuff in the base so we can filter anything
35
  */
36
+ if ( !empty( $params[ 'fDateFrom' ] ) && preg_match( '#^\d{4}-\d{2}-\d{2}$#', $params[ 'fDateFrom' ] ) ) {
37
+ $aParts = explode( '-', $params[ 'fDateFrom' ] );
38
+ $ts = Services::Request()->carbon()
39
+ ->setDate( $aParts[ 0 ], $aParts[ 1 ], $aParts[ 2 ] )
40
+ ->setTime( 0, 0 )->timestamp;
41
+ $selector->filterByCreatedAt( $ts, '>' );
 
42
  }
43
 
44
+ if ( !empty( $params[ 'fDateTo' ] ) && preg_match( '#^\d{4}-\d{2}-\d{2}$#', $params[ 'fDateTo' ] ) ) {
45
+ $aParts = explode( '-', $params[ 'fDateTo' ] );
46
+ $ts = Services::Request()->carbon()
47
+ ->setDate( $aParts[ 0 ], $aParts[ 1 ], $aParts[ 2 ] )
48
+ ->setTime( 0, 0 )
49
+ ->addDay()->timestamp;
50
+ $selector->filterByCreatedAt( $ts, '<' );
 
51
  }
52
 
53
  // if username is provided, this takes priority over "logged-in" (even if it's invalid)
54
+ if ( !empty( $params[ 'fUsername' ] ) ) {
55
+ $selector->filterByUsername( $params[ 'fUsername' ] );
56
  }
57
+ elseif ( $params[ 'fLoggedIn' ] >= 0 ) {
58
+ $selector->filterByIsLoggedIn( $params[ 'fLoggedIn' ] );
59
  }
60
 
61
+ $selector->setOrderBy( 'updated_at', 'DESC', true )
62
+ ->setOrderBy( 'created_at' );
63
 
64
  return $this;
65
  }
80
  * @return array[]
81
  */
82
  public function getEntriesFormatted() :array {
83
+ $entries = [];
84
 
85
  $srvIP = Services::IP();
86
  $you = $srvIP->getRequestIp();
87
+ $con = $this->getCon();
88
+ foreach ( $this->getEntriesRaw() as $key => $entry ) {
89
+ /** @var Shield\Databases\AuditTrail\EntryVO $entry */
90
 
91
+ $msg = 'Audit message could not be retrieved';
92
+ if ( empty( $entry->message ) ) {
93
  /**
94
  * To cater for the contexts that don't refer to a module, but rather a context
95
  * with the Audit Trail module
96
  */
97
+ $mod = $con->getModule( $entry->context );
98
  if ( empty( $mod ) ) {
99
+ $mod = $con->getModule_AuditTrail();
 
 
 
 
 
 
 
 
 
 
100
  }
101
+
102
+ $msg = Shield\Modules\AuditTrail\Lib\AuditMessageBuilder::Build(
103
+ $entry, $mod->getStrings()->getAuditMessage( $entry->event )
104
+ );
105
  }
106
  else {
107
+ $msg = $entry->message;
108
  }
109
 
110
+ if ( !isset( $entries[ $entry->rid ] ) ) {
111
+ $ent = $entry->getRawData();
112
+ $ent[ 'meta' ] = $entry->meta;
113
+ $ent[ 'event' ] = str_replace( '_', ' ', sanitize_text_field( $entry->event ) );
114
+ $ent[ 'message' ] = $msg;
115
+ $ent[ 'created_at' ] = $this->formatTimestampField( $entry->created_at );
116
+ if ( $entry->wp_username == '-' ) {
117
+ $ent[ 'wp_username' ] = __( 'Not logged-in', 'wp-simple-firewall' );
118
  }
119
 
120
  try {
121
+ $ent[ 'is_you' ] = $srvIP->checkIp( $you, $entry->ip );
122
  }
123
  catch ( \Exception $e ) {
124
+ $ent[ 'is_you' ] = false;
125
  }
126
 
127
+ if ( empty( $entry->ip ) ) {
128
+ $ent[ 'ip' ] = '';
129
  }
130
  else {
131
+ $ent[ 'ip' ] = sprintf( '%s%s',
132
+ $this->getIpAnalysisLink( $entry->ip ),
133
+ $ent[ 'is_you' ] ? ' <small>('.__( 'You', 'wp-simple-firewall' ).')</small>' : ''
134
  );
135
  }
136
  }
137
  else {
138
+ $ent = $entries[ $entry->rid ];
139
+ $ent[ 'meta' ] = Services::DataManipulation()->mergeArraysRecursive( $ent[ 'meta' ], $entry->meta );
140
+ $ent[ 'message' ] .= "\n".$msg;
141
+ $ent[ 'category' ] = max( $ent[ 'category' ], $entry->category );
142
  }
143
 
144
+ if ( $entry->count > 1 ) {
145
+ $ent[ 'message' ] = $msg."\n"
146
+ .sprintf( __( 'This event repeated %s times in the last 24hrs.', 'wp-simple-firewall' ), $entry->count );
147
  }
148
 
149
+ $entries[ $entry->rid ] = $ent;
150
  }
151
+ return $entries;
152
  }
153
 
154
  /**
src/lib/src/Tables/Build/Ip.php CHANGED
@@ -18,17 +18,17 @@ class Ip extends BaseBuild {
18
  * @return $this
19
  */
20
  protected function applyCustomQueryFilters() {
21
- $aParams = $this->getParams();
22
 
23
- /** @var IPs\Select $oSelector */
24
- $oSelector = $this->getWorkingSelector();
25
- $oSelector->filterByLists( $aParams[ 'fLists' ] );
26
- if ( Services::IP()->isValidIp( $aParams[ 'fIp' ] ) ) {
27
- $oSelector->filterByIp( $aParams[ 'fIp' ] );
28
  }
29
 
30
- $oSelector->setOrderBy( 'last_access_at', 'DESC', true );
31
- $oSelector->setOrderBy( 'created_at', 'DESC', false );
32
 
33
  return $this;
34
  }
@@ -50,29 +50,29 @@ class Ip extends BaseBuild {
50
 
51
  $nTransLimit = $opts->getOffenseLimit();
52
  $you = $srvIP->getRequestIp();
53
- $aEntries = [];
54
 
55
- foreach ( $this->getEntriesRaw() as $nKey => $oEntry ) {
56
- /** @var IPs\EntryVO $oEntry */
57
- $aE = $oEntry->getRawDataAsArray();
58
- $bBlocked = $oEntry->blocked_at > 0 || $oEntry->transgressions >= $nTransLimit;
59
  $aE[ 'last_trans_at' ] = Services::Request()
60
  ->carbon( true )
61
- ->setTimestamp( $oEntry->last_access_at )
62
  ->diffForHumans();
63
- $aE[ 'last_access_at' ] = $this->formatTimestampField( $oEntry->last_access_at );
64
- $aE[ 'created_at' ] = $this->formatTimestampField( $oEntry->created_at );
65
  $aE[ 'blocked' ] = $bBlocked ? __( 'Yes' ) : __( 'No' );
66
- $aE[ 'expires_at' ] = $this->formatTimestampField( $oEntry->last_access_at + $opts->getAutoExpireTime() );
67
- $aE[ 'is_you' ] = $srvIP->checkIp( $you, $oEntry->ip );
68
  $aE[ 'ip' ] = sprintf( '%s%s',
69
- $this->getIpAnalysisLink( $oEntry->ip ),
70
  $aE[ 'is_you' ] ? ' <span class="small">('.__( 'You', 'wp-simple-firewall' ).')</span>' : ''
71
  );
72
 
73
- $aEntries[ $nKey ] = $aE;
74
  }
75
- return $aEntries;
76
  }
77
 
78
  /**
18
  * @return $this
19
  */
20
  protected function applyCustomQueryFilters() {
21
+ $params = $this->getParams();
22
 
23
+ /** @var IPs\Select $selector */
24
+ $selector = $this->getWorkingSelector();
25
+ $selector->filterByLists( $params[ 'fLists' ] );
26
+ if ( Services::IP()->isValidIp( $params[ 'fIp' ] ) ) {
27
+ $selector->filterByIp( $params[ 'fIp' ] );
28
  }
29
 
30
+ $selector->setOrderBy( 'last_access_at', 'DESC', true );
31
+ $selector->setOrderBy( 'created_at', 'DESC', false );
32
 
33
  return $this;
34
  }
50
 
51
  $nTransLimit = $opts->getOffenseLimit();
52
  $you = $srvIP->getRequestIp();
53
+ $entries = [];
54
 
55
+ foreach ( $this->getEntriesRaw() as $key => $entry ) {
56
+ /** @var IPs\EntryVO $entry */
57
+ $aE = $entry->getRawData();
58
+ $bBlocked = $entry->blocked_at > 0 || $entry->transgressions >= $nTransLimit;
59
  $aE[ 'last_trans_at' ] = Services::Request()
60
  ->carbon( true )
61
+ ->setTimestamp( $entry->last_access_at )
62
  ->diffForHumans();
63
+ $aE[ 'last_access_at' ] = $this->formatTimestampField( $entry->last_access_at );
64
+ $aE[ 'created_at' ] = $this->formatTimestampField( $entry->created_at );
65
  $aE[ 'blocked' ] = $bBlocked ? __( 'Yes' ) : __( 'No' );
66
+ $aE[ 'expires_at' ] = $this->formatTimestampField( $entry->last_access_at + $opts->getAutoExpireTime() );
67
+ $aE[ 'is_you' ] = $srvIP->checkIp( $you, $entry->ip );
68
  $aE[ 'ip' ] = sprintf( '%s%s',
69
+ $this->getIpAnalysisLink( $entry->ip ),
70
  $aE[ 'is_you' ] ? ' <span class="small">('.__( 'You', 'wp-simple-firewall' ).')</span>' : ''
71
  );
72
 
73
+ $entries[ $key ] = $aE;
74
  }
75
+ return $entries;
76
  }
77
 
78
  /**
src/lib/src/Tables/Build/ScanApc.php CHANGED
@@ -26,20 +26,20 @@ class ScanApc extends ScanBase {
26
  $oConverter = new Scan\Results\ConvertBetweenTypes();
27
 
28
  $oWpPlugins = Services::WpPlugins();
29
- foreach ( $this->getEntriesRaw() as $nKey => $oEntry ) {
30
- /** @var Shield\Databases\Scanner\EntryVO $oEntry */
31
  /** @var Shield\Scans\Apc\ResultItem $oIt */
32
  $oIt = $oConverter
33
- ->setScanController( $mod->getScanCon( $oEntry->scan ) )
34
- ->convertVoToResultItem( $oEntry );
35
  $oPlugin = $oWpPlugins->getPluginAsVo( $oIt->slug );
36
- $aE = $oEntry->getRawDataAsArray();
37
  $aE[ 'plugin' ] = sprintf( '%s (%s)', $oPlugin->Name, $oPlugin->Version );
38
  $aE[ 'status' ] = sprintf( '%s: %s',
39
  __( 'Abandoned', 'wp-simple-firewall' ), $oCarbon->setTimestamp( $oIt->last_updated_at )
40
  ->diffForHumans() );
41
- $aE[ 'ignored' ] = $this->formatIsIgnored( $oEntry );
42
- $aE[ 'created_at' ] = $this->formatTimestampField( $oEntry->created_at );
43
  $aEntries[ $nKey ] = $aE;
44
  }
45
 
26
  $oConverter = new Scan\Results\ConvertBetweenTypes();
27
 
28
  $oWpPlugins = Services::WpPlugins();
29
+ foreach ( $this->getEntriesRaw() as $nKey => $entry ) {
30
+ /** @var Shield\Databases\Scanner\EntryVO $entry */
31
  /** @var Shield\Scans\Apc\ResultItem $oIt */
32
  $oIt = $oConverter
33
+ ->setScanController( $mod->getScanCon( $entry->scan ) )
34
+ ->convertVoToResultItem( $entry );
35
  $oPlugin = $oWpPlugins->getPluginAsVo( $oIt->slug );
36
+ $aE = $entry->getRawData();
37
  $aE[ 'plugin' ] = sprintf( '%s (%s)', $oPlugin->Name, $oPlugin->Version );
38
  $aE[ 'status' ] = sprintf( '%s: %s',
39
  __( 'Abandoned', 'wp-simple-firewall' ), $oCarbon->setTimestamp( $oIt->last_updated_at )
40
  ->diffForHumans() );
41
+ $aE[ 'ignored' ] = $this->formatIsIgnored( $entry );
42
+ $aE[ 'created_at' ] = $this->formatTimestampField( $entry->created_at );
43
  $aEntries[ $nKey ] = $aE;
44
  }
45
 
src/lib/src/Tables/Build/ScanPtg.php CHANGED
@@ -19,19 +19,19 @@ class ScanPtg extends ScanBase {
19
  * @return Shield\Databases\Scanner\EntryVO[]
20
  */
21
  protected function postSelectEntriesFilter( $aEntries ) {
22
- $aParams = $this->getParams();
23
 
24
  /** @var ModCon $mod */
25
  $mod = $this->getMod();
26
 
27
- if ( !empty( $aParams[ 'fSlug' ] ) ) {
28
 
29
 
30
  /** @var Shield\Scans\Ptg\ResultsSet $oSlugResults */
31
  $oSlugResults = ( new Scan\Results\ConvertBetweenTypes() )
32
- ->setScanController( $mod->getScanCon( $aParams[ 'fSlug' ] ) )
33
  ->fromVOsToResultsSet( $aEntries );
34
- $oSlugResults = $oSlugResults->getResultsSetForSlug( $aParams[ 'fSlug' ] );
35
 
36
  foreach ( $aEntries as $key => $oVo ) {
37
  if ( !$oSlugResults->getItemExists( $oVo->hash ) ) {
19
  * @return Shield\Databases\Scanner\EntryVO[]
20
  */
21
  protected function postSelectEntriesFilter( $aEntries ) {
22
+ $params = $this->getParams();
23
 
24
  /** @var ModCon $mod */
25
  $mod = $this->getMod();
26
 
27
+ if ( !empty( $params[ 'fSlug' ] ) ) {
28
 
29
 
30
  /** @var Shield\Scans\Ptg\ResultsSet $oSlugResults */
31
  $oSlugResults = ( new Scan\Results\ConvertBetweenTypes() )
32
+ ->setScanController( $mod->getScanCon( $params[ 'fSlug' ] ) )
33
  ->fromVOsToResultsSet( $aEntries );
34
+ $oSlugResults = $oSlugResults->getResultsSetForSlug( $params[ 'fSlug' ] );
35
 
36
  foreach ( $aEntries as $key => $oVo ) {
37
  if ( !$oSlugResults->getItemExists( $oVo->hash ) ) {
src/lib/src/Tables/Build/ScanWpv.php CHANGED
@@ -30,13 +30,13 @@ class ScanWpv extends ScanBase {
30
  $oWpThemes->getUpdates( true );
31
 
32
  $oConverter = new Scan\Results\ConvertBetweenTypes();
33
- foreach ( $this->getEntriesRaw() as $nKey => $oEntry ) {
34
- /** @var Shield\Databases\Scanner\EntryVO $oEntry */
35
  /** @var Shield\Scans\Wpv\ResultItem $oIt */
36
  $oIt = $oConverter
37
- ->setScanController( $mod->getScanCon( $oEntry->scan ) )
38
- ->convertVoToResultItem( $oEntry );
39
- $aE = $oEntry->getRawDataAsArray();
40
  if ( $oIt->context == 'plugins' ) {
41
  $oAsset = $oWpPlugins->getPluginAsVo( $oIt->slug );
42
  $aE[ 'asset' ] = $oAsset;
@@ -55,8 +55,8 @@ class ScanWpv extends ScanBase {
55
  }
56
  $aE[ 'slug' ] = $oIt->slug;
57
  $aE[ 'wpvuln_vo' ] = $oIt->getWpVulnVo();
58
- $aE[ 'ignored' ] = $this->formatIsIgnored( $oEntry );
59
- $aE[ 'created_at' ] = $this->formatTimestampField( $oEntry->created_at );
60
  $aEntries[ $nKey ] = $aE;
61
  }
62
 
30
  $oWpThemes->getUpdates( true );
31
 
32
  $oConverter = new Scan\Results\ConvertBetweenTypes();
33
+ foreach ( $this->getEntriesRaw() as $nKey => $entry ) {
34
+ /** @var Shield\Databases\Scanner\EntryVO $entry */
35
  /** @var Shield\Scans\Wpv\ResultItem $oIt */
36
  $oIt = $oConverter
37
+ ->setScanController( $mod->getScanCon( $entry->scan ) )
38
+ ->convertVoToResultItem( $entry );
39
+ $aE = $entry->getRawData();
40
  if ( $oIt->context == 'plugins' ) {
41
  $oAsset = $oWpPlugins->getPluginAsVo( $oIt->slug );
42
  $aE[ 'asset' ] = $oAsset;
55
  }
56
  $aE[ 'slug' ] = $oIt->slug;
57
  $aE[ 'wpvuln_vo' ] = $oIt->getWpVulnVo();
58
+ $aE[ 'ignored' ] = $this->formatIsIgnored( $entry );
59
+ $aE[ 'created_at' ] = $this->formatTimestampField( $entry->created_at );
60
  $aEntries[ $nKey ] = $aE;
61
  }
62
 
src/lib/src/Tables/Build/Sessions.php CHANGED
@@ -59,21 +59,21 @@ class Sessions extends BaseBuild {
59
 
60
  $srvIP = Services::IP();
61
  $you = $srvIP->getRequestIp();
62
- foreach ( $this->getEntriesRaw() as $nKey => $oEntry ) {
63
- /** @var Session\EntryVO $oEntry */
64
- $aE = $oEntry->getRawDataAsArray();
65
- $aE[ 'is_secadmin' ] = $this->isSecAdminSession( $oEntry ) ? __( 'Yes' ) : __( 'No' );
66
- $aE[ 'last_activity_at' ] = $this->formatTimestampField( $oEntry->last_activity_at );
67
- $aE[ 'logged_in_at' ] = $this->formatTimestampField( $oEntry->logged_in_at );
68
 
69
  try {
70
- $aE[ 'is_you' ] = $srvIP->checkIp( $you, $oEntry->ip );
71
  }
72
  catch ( \Exception $e ) {
73
  $aE[ 'is_you' ] = false;
74
  }
75
  $aE[ 'ip' ] = sprintf( '%s%s',
76
- $this->getIpAnalysisLink( $oEntry->ip ),
77
  $aE[ 'is_you' ] ? ' <small>('.__( 'You', 'wp-simple-firewall' ).')</small>' : ''
78
  );
79
 
59
 
60
  $srvIP = Services::IP();
61
  $you = $srvIP->getRequestIp();
62
+ foreach ( $this->getEntriesRaw() as $nKey => $entry ) {
63
+ /** @var Session\EntryVO $entry */
64
+ $aE = $entry->getRawData();
65
+ $aE[ 'is_secadmin' ] = $this->isSecAdminSession( $entry ) ? __( 'Yes' ) : __( 'No' );
66
+ $aE[ 'last_activity_at' ] = $this->formatTimestampField( $entry->last_activity_at );
67
+ $aE[ 'logged_in_at' ] = $this->formatTimestampField( $entry->logged_in_at );
68
 
69
  try {
70
+ $aE[ 'is_you' ] = $srvIP->checkIp( $you, $entry->ip );
71
  }
72
  catch ( \Exception $e ) {
73
  $aE[ 'is_you' ] = false;
74
  }
75
  $aE[ 'ip' ] = sprintf( '%s%s',
76
+ $this->getIpAnalysisLink( $entry->ip ),
77
  $aE[ 'is_you' ] ? ' <small>('.__( 'You', 'wp-simple-firewall' ).')</small>' : ''
78
  );
79
 
src/lib/src/Tables/Build/Traffic.php CHANGED
@@ -20,13 +20,13 @@ class Traffic extends BaseBuild {
20
  /** @var Databases\Traffic\Select $select */
21
  $select = $this->getWorkingSelector();
22
 
23
- $oIp = Services::IP();
24
  // If an IP is specified, it takes priority
25
- if ( $oIp->isValidIp( $params[ 'fIp' ] ) ) {
26
  $select->filterByIp( inet_pton( $params[ 'fIp' ] ) );
27
  }
28
  elseif ( $params[ 'fExcludeYou' ] == 'Y' ) {
29
- $select->filterByNotIp( inet_pton( $oIp->getRequestIp() ) );
30
  }
31
 
32
  // if username is provided, this takes priority over "logged-in" (even if it's invalid)
@@ -99,7 +99,7 @@ class Traffic extends BaseBuild {
99
  $sCodeType = 'warning';
100
  }
101
 
102
- $e = $record->getRawDataAsArray();
103
  $e[ 'path' ] = $sPath;
104
  $e[ 'code' ] = sprintf( '<span class="badge badge-%s">%s</span>', $sCodeType, $record->code );
105
  $e[ 'trans' ] = sprintf(
@@ -177,7 +177,11 @@ class Traffic extends BaseBuild {
177
  ->lookup();
178
 
179
  $badgeTemplate = '<span class="badge badge-%s">%s</span>';
180
- if ( $record->blocked_at > 0 || $record->list === ModCon::LIST_MANUAL_BLACK ) {
 
 
 
 
181
  $status = sprintf( $badgeTemplate, 'danger', __( 'Blocked', 'wp-simple-firewall' ) );
182
  }
183
  elseif ( $record->list === ModCon::LIST_AUTO_BLACK ) {
@@ -192,9 +196,6 @@ class Traffic extends BaseBuild {
192
  __( 'Bypass', 'wp-simple-firewall' )
193
  );
194
  }
195
- else {
196
- $status = __( 'No Record', 'wp-simple-firewall' );
197
- }
198
  return $status;
199
  }
200
 
20
  /** @var Databases\Traffic\Select $select */
21
  $select = $this->getWorkingSelector();
22
 
23
+ $srvIP = Services::IP();
24
  // If an IP is specified, it takes priority
25
+ if ( $srvIP->isValidIp( $params[ 'fIp' ] ) ) {
26
  $select->filterByIp( inet_pton( $params[ 'fIp' ] ) );
27
  }
28
  elseif ( $params[ 'fExcludeYou' ] == 'Y' ) {
29
+ $select->filterByNotIp( inet_pton( $srvIP->getRequestIp() ) );
30
  }
31
 
32
  // if username is provided, this takes priority over "logged-in" (even if it's invalid)
99
  $sCodeType = 'warning';
100
  }
101
 
102
+ $e = $record->getRawData();
103
  $e[ 'path' ] = $sPath;
104
  $e[ 'code' ] = sprintf( '<span class="badge badge-%s">%s</span>', $sCodeType, $record->code );
105
  $e[ 'trans' ] = sprintf(
177
  ->lookup();
178
 
179
  $badgeTemplate = '<span class="badge badge-%s">%s</span>';
180
+ $status = __( 'No Record', 'wp-simple-firewall' );
181
+ if ( !$record instanceof Databases\IPs\EntryVO ) {
182
+ $status = __( 'No Record', 'wp-simple-firewall' );
183
+ }
184
+ elseif ( $record->blocked_at > 0 || $record->list === ModCon::LIST_MANUAL_BLACK ) {
185
  $status = sprintf( $badgeTemplate, 'danger', __( 'Blocked', 'wp-simple-firewall' ) );
186
  }
187
  elseif ( $record->list === ModCon::LIST_AUTO_BLACK ) {
196
  __( 'Bypass', 'wp-simple-firewall' )
197
  );
198
  }
 
 
 
199
  return $status;
200
  }
201
 
src/lib/src/Tables/Render/WpListTable/AuditTrail.php CHANGED
@@ -18,15 +18,15 @@ class AuditTrail extends Base {
18
  }
19
 
20
  /**
21
- * @param int $nId
22
  * @return string
23
  */
24
- protected function getActionButton_AddParam( $nId ) {
25
  return $this->buildActionButton_Custom(
26
  __( 'Whitelist Param', 'wp-simple-firewall' ),
27
  [ 'custom-action' ],
28
  [
29
- 'rid' => $nId,
30
  'custom-action' => 'item_addparamwhite'
31
  ],
32
  __( 'Add Parameter To Whitelist', 'wp-simple-firewall' )
@@ -49,11 +49,11 @@ class AuditTrail extends Base {
49
  * @return string
50
  */
51
  public function column_user( $item ) {
52
- $sContent = $item[ 'wp_username' ];
53
  if ( isset( $item[ 'meta' ][ 'param' ] ) ) {
54
- $sContent .= $this->buildActions( [ $this->getActionButton_AddParam( $item[ 'id' ] ) ] );
55
  }
56
- return $sContent;
57
  }
58
 
59
  /**
18
  }
19
 
20
  /**
21
+ * @param int $id
22
  * @return string
23
  */
24
+ protected function getActionButton_AddParam( $id ) {
25
  return $this->buildActionButton_Custom(
26
  __( 'Whitelist Param', 'wp-simple-firewall' ),
27
  [ 'custom-action' ],
28
  [
29
+ 'rid' => $id,
30
  'custom-action' => 'item_addparamwhite'
31
  ],
32
  __( 'Add Parameter To Whitelist', 'wp-simple-firewall' )
49
  * @return string
50
  */
51
  public function column_user( $item ) {
52
+ $content = $item[ 'wp_username' ];
53
  if ( isset( $item[ 'meta' ][ 'param' ] ) ) {
54
+ $content .= $this->buildActions( [ $this->getActionButton_AddParam( $item[ 'id' ] ) ] );
55
  }
56
+ return $content;
57
  }
58
 
59
  /**
src/lib/src/Utilities/AdminNotices/Controller.php CHANGED
@@ -18,31 +18,31 @@ class Controller {
18
 
19
  /**
20
  * TODO doesn't handle error message highlighting
21
- * @param string $sMessage
22
  * @return string
23
  */
24
- public function onLoginMessage( $sMessage ) {
25
  $aM = $this->retrieveFlashMessage();
26
  if ( is_array( $aM ) && isset( $aM[ 'show_login' ] ) && $aM[ 'show_login' ] ) {
27
- $sMessage .= sprintf( '<p class="message">%s</p>', sanitize_text_field( $aM[ 'message' ] ) );
28
  $this->clearFlashMessage();
29
  }
30
- return $sMessage;
31
  }
32
 
33
  /**
34
- * @param string $sMessage
35
- * @param bool $bIsError
36
  * @param bool $bShowOnLoginPage
37
  * @return $this
38
  */
39
- public function addFlash( $sMessage, $bIsError = false, $bShowOnLoginPage = false ) {
40
  $oMeta = $this->getCon()->getCurrentUserMeta();
41
  if ( $oMeta instanceof PluginUserMeta ) {
42
  $oMeta->flash_msg = [
43
- 'message' => sanitize_text_field( $sMessage ),
44
  'expires_at' => Services::Request()->ts() + 20,
45
- 'error' => $bIsError,
46
  'show_login' => $bShowOnLoginPage,
47
  ];
48
  }
18
 
19
  /**
20
  * TODO doesn't handle error message highlighting
21
+ * @param string $msg
22
  * @return string
23
  */
24
+ public function onLoginMessage( $msg ) {
25
  $aM = $this->retrieveFlashMessage();
26
  if ( is_array( $aM ) && isset( $aM[ 'show_login' ] ) && $aM[ 'show_login' ] ) {
27
+ $msg .= sprintf( '<p class="message">%s</p>', sanitize_text_field( $aM[ 'message' ] ) );
28
  $this->clearFlashMessage();
29
  }
30
+ return $msg;
31
  }
32
 
33
  /**
34
+ * @param string $msg
35
+ * @param bool $isError
36
  * @param bool $bShowOnLoginPage
37
  * @return $this
38
  */
39
+ public function addFlash( $msg, $isError = false, $bShowOnLoginPage = false ) {
40
  $oMeta = $this->getCon()->getCurrentUserMeta();
41
  if ( $oMeta instanceof PluginUserMeta ) {
42
  $oMeta->flash_msg = [
43
+ 'message' => sanitize_text_field( $msg ),
44
  'expires_at' => Services::Request()->ts() + 20,
45
+ 'error' => $isError,
46
  'show_login' => $bShowOnLoginPage,
47
  ];
48
  }
src/lib/src/Utilities/AdminNotices/NoticeVO.php CHANGED
@@ -1,8 +1,8 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Utilities\AdminNotices;
4
 
5
- use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
6
 
7
  /**
8
  * Class NoticeVO
@@ -24,5 +24,5 @@ use FernleafSystems\Utilities\Data\Adapter\StdClassAdapter;
24
  */
25
  class NoticeVO {
26
 
27
- use StdClassAdapter;
28
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Utilities\AdminNotices;
4
 
5
+ use FernleafSystems\Utilities\Data\Adapter\DynProperties;
6
 
7
  /**
8
  * Class NoticeVO
24
  */
25
  class NoticeVO {
26
 
27
+ use DynProperties;
28
  }
src/lib/src/Utilities/HCaptcha/TestRequest.php CHANGED
@@ -3,8 +3,8 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Utilities\HCaptcha;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\ModCon;
6
- use FernleafSystems\Wordpress\Services\Services;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Utilities\ReCaptcha;
 
8
 
9
  class TestRequest extends ReCaptcha\TestRequest {
10
 
@@ -24,25 +24,25 @@ class TestRequest extends ReCaptcha\TestRequest {
24
  throw new \Exception( __( 'Whoops.', 'wp-simple-firewall' ).' '.__( 'CAPTCHA was not submitted.', 'wp-simple-firewall' ), 1 );
25
  }
26
  else {
27
- $oHTTP = Services::HttpRequest();
28
- $bSuccess = $oHTTP->post( self::URL_VERIFY, [
29
  'body' => [
30
  'secret' => $mod->getCaptchaCfg()->secret,
31
  'response' => $sCaptchaResponse,
32
  'remoteip' => Services::IP()->getRequestIp(),
33
  ]
34
  ] )
35
- && !empty( $oHTTP->lastResponse->body );
36
- $aResponse = $bSuccess ? json_decode( $oHTTP->lastResponse->body, true ) : [];
37
 
38
- if ( empty( $aResponse[ 'success' ] ) ) {
39
- $aMsg = [
40
  __( 'Whoops.', 'wp-simple-firewall' ),
41
  __( 'CAPTCHA verification failed.', 'wp-simple-firewall' ),
42
  Services::WpGeneral()->isAjax() ?
43
  __( 'Maybe refresh the page and try again.', 'wp-simple-firewall' ) : ''
44
  ];
45
- throw new \Exception( implode( ' ', $aMsg ), 2 );
46
  }
47
  }
48
  return true;
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Utilities\HCaptcha;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\ModCon;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Utilities\ReCaptcha;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class TestRequest extends ReCaptcha\TestRequest {
10
 
24
  throw new \Exception( __( 'Whoops.', 'wp-simple-firewall' ).' '.__( 'CAPTCHA was not submitted.', 'wp-simple-firewall' ), 1 );
25
  }
26
  else {
27
+ $HTTPReq = Services::HttpRequest();
28
+ $successRequest = $HTTPReq->post( self::URL_VERIFY, [
29
  'body' => [
30
  'secret' => $mod->getCaptchaCfg()->secret,
31
  'response' => $sCaptchaResponse,
32
  'remoteip' => Services::IP()->getRequestIp(),
33
  ]
34
  ] )
35
+ && !empty( $HTTPReq->lastResponse->body );
36
+ $response = $successRequest ? json_decode( $HTTPReq->lastResponse->body, true ) : [];
37
 
38
+ if ( empty( $response[ 'success' ] ) ) {
39
+ $msg = [
40
  __( 'Whoops.', 'wp-simple-firewall' ),
41
  __( 'CAPTCHA verification failed.', 'wp-simple-firewall' ),
42
  Services::WpGeneral()->isAjax() ?
43
  __( 'Maybe refresh the page and try again.', 'wp-simple-firewall' ) : ''
44
  ];
45
+ throw new \Exception( implode( ' ', $msg ), 2 );
46
  }
47
  }
48
  return true;
src/lib/src/Utilities/HumanSpam/TestContent.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Utilities\HumanSpam;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class TestContent {
9
+
10
+ use Modules\PluginControllerConsumer;
11
+
12
+ /**
13
+ * @var string[]
14
+ */
15
+ private $list;
16
+
17
+ /**
18
+ * @param array $itemsToTest
19
+ * @param bool $finishAfterFirst
20
+ * @return string[][]
21
+ */
22
+ public function findSpam( array $itemsToTest, bool $finishAfterFirst = true ) :array {
23
+ $spamFound = [];
24
+
25
+ $itemsToTest = array_map( 'strval', array_filter( $itemsToTest ) );
26
+ foreach ( $this->getSpamList() as $word ) {
27
+ foreach ( $itemsToTest as $key => $item ) {
28
+ if ( stripos( $item, $word ) !== false ) {
29
+
30
+ if ( !isset( $spamFound[ $word ] ) ) {
31
+ $spamFound[ $word ] = [];
32
+ }
33
+ $spamFound[ $word ][ $key ] = $item;
34
+
35
+ if ( $finishAfterFirst ) {
36
+ break 2;
37
+ }
38
+ }
39
+ }
40
+ }
41
+
42
+ return $spamFound;
43
+ }
44
+
45
+ private function getSpamList() :array {
46
+ if ( empty( $this->list ) ) {
47
+ $FS = Services::WpFs();
48
+ $file = $this->getFile();
49
+ if ( !$FS->exists( $file ) || Services::Request()
50
+ ->ts() - $FS->getModifiedTime( $file ) > WEEK_IN_SECONDS ) {
51
+ $this->importBlacklist();
52
+ }
53
+ $this->list = array_map( 'base64_decode', explode( "\n", $FS->getFileContent( $file, true ) ) );
54
+ }
55
+ return $this->list;
56
+ }
57
+
58
+ private function importBlacklist() :bool {
59
+ $success = false;
60
+ $mod = $this->getCon()->getModule_Comments();
61
+ $rawList = Services::HttpRequest()->getContent( $mod->getOptions()->getDef( 'url_spam_blacklist_terms' ) );
62
+ if ( !empty( $rawList ) ) {
63
+ $success = Services::WpFs()->putFileContent(
64
+ $this->getFile(),
65
+ implode( "\n", array_map( 'base64_encode', array_filter( array_map( 'trim', explode( "\n", $rawList ) ) ) ) ),
66
+ true
67
+ );
68
+ }
69
+ return $success;
70
+ }
71
+
72
+ private function getFile() :string {
73
+ return $this->getCon()->getModule_Comments()->getSpamBlacklistFile();
74
+ }
75
+ }
src/lib/src/Utilities/Options/CleanStorage.php CHANGED
@@ -10,10 +10,10 @@ class CleanStorage {
10
 
11
  public function run() {
12
  foreach ( $this->getCon()->modules as $mod ) {
13
- $oOpts = $mod->getOptions();
14
- foreach ( array_keys( $oOpts->getAllOptionsValues() ) as $sOptKey ) {
15
- if ( !$oOpts->isValidOptionKey( $sOptKey ) ) {
16
- $oOpts->unsetOpt( $sOptKey );
17
  }
18
  }
19
  $mod->saveModOptions();
10
 
11
  public function run() {
12
  foreach ( $this->getCon()->modules as $mod ) {
13
+ $opts = $mod->getOptions();
14
+ foreach ( array_keys( $opts->getAllOptionsValues() ) as $optKey ) {
15
+ if ( !$opts->isValidOptionKey( $optKey ) ) {
16
+ $opts->unsetOpt( $optKey );
17
  }
18
  }
19
  $mod->saveModOptions();
src/lib/src/Utilities/ReCaptcha/Enqueue.php CHANGED
@@ -23,6 +23,9 @@ class Enqueue {
23
  }
24
  }
25
 
 
 
 
26
  public function onWpEnqueueJs() {
27
  /** @var ModCon $oMod */
28
  $oMod = $this->getMod();
@@ -50,10 +53,7 @@ class Enqueue {
50
  add_action( 'login_footer', [ $this, 'maybeDequeueRecaptcha' ], -100 );
51
  }
52
 
53
- /**
54
- * @return string
55
- */
56
- public function getCaptchaHtml() {
57
  return '<div class="icwpg-recaptcha"></div>';
58
  }
59
 
23
  }
24
  }
25
 
26
+ /**
27
+ * TODO: Consider how to move this to our standardised Enqueue system.
28
+ */
29
  public function onWpEnqueueJs() {
30
  /** @var ModCon $oMod */
31
  $oMod = $this->getMod();
53
  add_action( 'login_footer', [ $this, 'maybeDequeueRecaptcha' ], -100 );
54
  }
55
 
56
+ public function getCaptchaHtml() :string {
 
 
 
57
  return '<div class="icwpg-recaptcha"></div>';
58
  }
59
 
src/lib/src/Utilities/ReCaptcha/TestRequest.php CHANGED
@@ -32,18 +32,17 @@ class TestRequest {
32
  * @throws \Exception
33
  */
34
  protected function runTest() {
35
- /** @var ModCon $mod */
36
  $mod = $this->getMod();
37
 
38
- $sCaptchaResponse = Services::Request()->post( 'g-recaptcha-response' );
39
 
40
- if ( empty( $sCaptchaResponse ) ) {
41
  throw new \Exception( __( 'Whoops.', 'wp-simple-firewall' ).' '.__( 'CAPTCHA was not submitted.', 'wp-simple-firewall' ), 1 );
42
  }
43
  else {
44
- $oResponse = ( new ReCaptcha( $mod->getCaptchaCfg()->secret, new WordpressPost() ) )
45
- ->verify( $sCaptchaResponse, Services::IP()->getRequestIp() );
46
- if ( empty( $oResponse ) || !$oResponse->isSuccess() ) {
47
  $aMsg = [
48
  __( 'Whoops.', 'wp-simple-firewall' ),
49
  __( 'CAPTCHA verification failed.', 'wp-simple-firewall' ),
32
  * @throws \Exception
33
  */
34
  protected function runTest() {
 
35
  $mod = $this->getMod();
36
 
37
+ $captchaResponse = Services::Request()->post( 'g-recaptcha-response' );
38
 
39
+ if ( empty( $captchaResponse ) ) {
40
  throw new \Exception( __( 'Whoops.', 'wp-simple-firewall' ).' '.__( 'CAPTCHA was not submitted.', 'wp-simple-firewall' ), 1 );
41
  }
42
  else {
43
+ $response = ( new ReCaptcha( $mod->getCaptchaCfg()->secret, new WordpressPost() ) )
44
+ ->verify( $captchaResponse, Services::IP()->getRequestIp() );
45
+ if ( empty( $response ) || !$response->isSuccess() ) {
46
  $aMsg = [
47
  __( 'Whoops.', 'wp-simple-firewall' ),
48
  __( 'CAPTCHA verification failed.', 'wp-simple-firewall' ),
src/lib/src/Utilities/Resources/Dynamic.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Utilities\Resources;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\PluginControllerConsumer;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class Dynamic {
9
+
10
+ const RESOURCES_DIR = 'resources';
11
+ use PluginControllerConsumer;
12
+
13
+ public function getBaseDir() :string {
14
+ return path_join( $this->getCon()->getPluginCachePath(), self::RESOURCES_DIR );
15
+ }
16
+
17
+ public function getResourcePath( string $resource ) :string {
18
+ $path = path_join( $this->getBaseDir(), $resource );
19
+ Services::WpFs()->mkdir( dirname( $path ) );
20
+ return $path;
21
+ }
22
+
23
+ public function getResourceUrl( string $resource ) :string {
24
+ $this->getResourcePath( $resource );
25
+ $url = content_url(
26
+ sprintf( '%s/%s/%s',
27
+ $this->getCon()->cfg->paths[ 'cache' ],
28
+ self::RESOURCES_DIR,
29
+ $resource
30
+ )
31
+ );
32
+ if ( $this->resourceExists( $resource ) ) {
33
+ $url = add_query_arg(
34
+ [ 'mtime' => Services::WpFs()->getModifiedTime( $this->getResourcePath( $resource ) ) ],
35
+ $url
36
+ );
37
+ }
38
+ return $url;
39
+ }
40
+
41
+ public function getModifiedTime( string $res ) :int {
42
+ return (int)Services::WpFs()->getModifiedTime( $this->getResourcePath( $res ) );
43
+ }
44
+
45
+ public function resourceCreate( string $res, string $content ) {
46
+ Services::WpFs()->putFileContent( $this->getResourcePath( $res ), $content );
47
+ }
48
+
49
+ public function resourceDelete( string $res ) {
50
+ Services::WpFs()->deleteFile( $this->getResourcePath( $res ) );
51
+ }
52
+
53
+ public function resourceExists( string $res ) :bool {
54
+ return Services::WpFs()->exists( $this->getResourcePath( $res ) );
55
+ }
56
+ }
src/lib/src/Utilities/Time/WorldTimeApi.php CHANGED
@@ -12,7 +12,7 @@ class WorldTimeApi {
12
  */
13
  public function current() :int {
14
  $raw = Services::HttpRequest()
15
- ->getContent( 'https://showcase.api.linx.twenty57.net/UnixTime/tounixtimestamp?datetime=now' );
16
  if ( empty( $raw ) ) {
17
  throw new \Exception( 'Request to World Clock Api Failed' );
18
  }
@@ -20,7 +20,7 @@ class WorldTimeApi {
20
  if ( empty( $dec ) ) {
21
  throw new \Exception( 'Failed to decode World Clock Api response' );
22
  }
23
- return (int)$dec[ 'UnixTimeStamp' ];
24
  }
25
 
26
  /**
12
  */
13
  public function current() :int {
14
  $raw = Services::HttpRequest()
15
+ ->getContent( 'https://api.aptoweb.com/api/v1/time' );
16
  if ( empty( $raw ) ) {
17
  throw new \Exception( 'Request to World Clock Api Failed' );
18
  }
20
  if ( empty( $dec ) ) {
21
  throw new \Exception( 'Failed to decode World Clock Api response' );
22
  }
23
+ return (int)$dec[ 'current' ][ 'seconds' ];
24
  }
25
 
26
  /**
src/lib/src/Utilities/Tool/DbTableExport.php ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Utilities\Tool;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base\EntryVO;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base\HandlerConsumer;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+
9
+ class DbTableExport {
10
+
11
+ use HandlerConsumer;
12
+
13
+ public function toCSV() {
14
+ $content = [];
15
+ /** @var EntryVO $entryVO */
16
+ foreach ( $this->getDbHandler()->getIterator() as $entryVO ) {
17
+ $content[] = $this->implodeForCSV( $this->getEntryAsRawArray( $entryVO ) );
18
+ }
19
+ array_unshift( $content, $this->implodeForCSV( $this->getActualColumns() ) );
20
+ Services::Response()->downloadStringAsFile( implode( "\n", $content ), $this->getFileName() );
21
+ }
22
+
23
+ protected function implodeForCSV( array $line ) :string {
24
+ return '"'.implode( '","', $line ).'"';
25
+ }
26
+
27
+ /**
28
+ * @param EntryVO $entryVO
29
+ * @return array
30
+ */
31
+ protected function getEntryAsRawArray( $entryVO ) :array {
32
+ $entry = $entryVO->getRawData();
33
+ $schema = $this->getDbHandler()->getTableSchema();
34
+ if ( $schema->is_ip_binary ) {
35
+ $entry[ 'ip' ] = $entryVO->ip;
36
+ }
37
+ if ( $schema->hasColumn( 'meta' ) ) {
38
+ $entry[ 'meta' ] = serialize( $entryVO->meta );
39
+ }
40
+ return $entry;
41
+ }
42
+
43
+ protected function getActualColumns() :array {
44
+ return Services::WpDb()->getColumnsForTable( $this->getDbHandler()->getTableSchema()->table, 'strtolower' );
45
+ }
46
+
47
+ protected function getFileName() :string {
48
+ return sprintf( 'table_export-%s-%s.csv', $this->getDbHandler()->getTableSchema()->table, date( 'Ymd_His' ) );
49
+ }
50
+ }
src/lib/src/Utilities/Tool/IpListSort.php CHANGED
@@ -9,21 +9,21 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Utilities\Tool;
9
  class IpListSort {
10
 
11
  /**
12
- * @param string[] $aIPs
13
  * @return array
14
  */
15
- public static function Sort( $aIPs ) {
16
- if ( is_array( $aIPs ) ) {
17
- $aIp4 = array_filter( $aIPs, function ( $sIP ) {
18
  return strpos( $sIP, '.' ) > 0;
19
  } );
20
- $aIp6 = array_filter( $aIPs, function ( $sIP ) {
21
  return strpos( $sIP, ':' ) > 0;
22
  } );
23
- asort( $aIp4 );
24
- asort( $aIp6 );
25
- $aIPs = array_merge( $aIp4, $aIp6 );
26
  }
27
- return is_array( $aIPs ) ? $aIPs : [];
28
  }
29
  }
9
  class IpListSort {
10
 
11
  /**
12
+ * @param string[] $IPs
13
  * @return array
14
  */
15
+ public static function Sort( $IPs ) :array {
16
+ if ( is_array( $IPs ) ) {
17
+ $ip4 = array_filter( $IPs, function ( $sIP ) {
18
  return strpos( $sIP, '.' ) > 0;
19
  } );
20
+ $ip6 = array_filter( $IPs, function ( $sIP ) {
21
  return strpos( $sIP, ':' ) > 0;
22
  } );
23
+ asort( $ip4 );
24
+ asort( $ip6 );
25
+ $IPs = array_merge( $ip4, $ip6 );
26
  }
27
+ return is_array( $IPs ) ? $IPs : [];
28
  }
29
  }
src/lib/vendor/composer/autoload_classmap.php CHANGED
@@ -62,6 +62,7 @@ return array(
62
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Ajax\\Init' => $baseDir . '/src/Controller/Ajax/Init.php',
63
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Ajax\\Response' => $baseDir . '/src/Controller/Ajax/Response.php',
64
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Assets\\Enqueue' => $baseDir . '/src/Controller/Assets/Enqueue.php',
 
65
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Assets\\Urls' => $baseDir . '/src/Controller/Assets/Urls.php',
66
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Config\\ConfigVO' => $baseDir . '/src/Controller/Config/ConfigVO.php',
67
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Config\\Ops\\LoadConfig' => $baseDir . '/src/Controller/Config/Ops/LoadConfig.php',
@@ -70,6 +71,7 @@ return array(
70
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Controller' => $baseDir . '/src/Controller/Controller.php',
71
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\I18n\\GetAllAvailableLocales' => $baseDir . '/src/Controller/I18n/GetAllAvailableLocales.php',
72
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\I18n\\LoadTextDomain' => $baseDir . '/src/Controller/I18n/LoadTextDomain.php',
 
73
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Utilities\\DebugMode' => $baseDir . '/src/Controller/Utilities/DebugMode.php',
74
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Utilities\\Upgrade' => $baseDir . '/src/Controller/Utilities/Upgrade.php',
75
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Crons\\BaseCron' => $baseDir . '/src/Crons/BaseCron.php',
@@ -95,8 +97,17 @@ return array(
95
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Handler' => $baseDir . '/src/Databases/Base/Handler.php',
96
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\HandlerConsumer' => $baseDir . '/src/Databases/Base/HandlerConsumer.php',
97
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Insert' => $baseDir . '/src/Databases/Base/Insert.php',
 
98
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Select' => $baseDir . '/src/Databases/Base/Select.php',
 
99
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Update' => $baseDir . '/src/Databases/Base/Update.php',
 
 
 
 
 
 
 
100
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\ChangeTracking\\Delete' => $baseDir . '/src/Databases/ChangeTracking/Delete.php',
101
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\ChangeTracking\\EntryVO' => $baseDir . '/src/Databases/ChangeTracking/EntryVO.php',
102
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\ChangeTracking\\Handler' => $baseDir . '/src/Databases/ChangeTracking/Handler.php',
@@ -159,12 +170,6 @@ return array(
159
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Session\\Insert' => $baseDir . '/src/Databases/Session/Insert.php',
160
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Session\\Select' => $baseDir . '/src/Databases/Session/Select.php',
161
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Session\\Update' => $baseDir . '/src/Databases/Session/Update.php',
162
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Tally\\Delete' => $baseDir . '/src/Databases/Tally/Delete.php',
163
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Tally\\EntryVO' => $baseDir . '/src/Databases/Tally/EntryVO.php',
164
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Tally\\Handler' => $baseDir . '/src/Databases/Tally/Handler.php',
165
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Tally\\Insert' => $baseDir . '/src/Databases/Tally/Insert.php',
166
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Tally\\Select' => $baseDir . '/src/Databases/Tally/Select.php',
167
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Tally\\Update' => $baseDir . '/src/Databases/Tally/Update.php',
168
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Traffic\\BaseTraffic' => $baseDir . '/src/Databases/Traffic/BaseTraffic.php',
169
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Traffic\\Delete' => $baseDir . '/src/Databases/Traffic/Delete.php',
170
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Traffic\\EntryVO' => $baseDir . '/src/Databases/Traffic/EntryVO.php',
@@ -183,6 +188,7 @@ return array(
183
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Auditors\\Users' => $baseDir . '/src/Modules/AuditTrail/Auditors/Users.php',
184
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Auditors\\Wordpress' => $baseDir . '/src/Modules/AuditTrail/Auditors/Wordpress.php',
185
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Insights\\OverviewCards' => $baseDir . '/src/Modules/AuditTrail/Insights/OverviewCards.php',
 
186
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Lib\\AuditWriter' => $baseDir . '/src/Modules/AuditTrail/Lib/AuditWriter.php',
187
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Lib\\Ops\\Commit' => $baseDir . '/src/Modules/AuditTrail/Lib/Ops/Commit.php',
188
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\ModCon' => $baseDir . '/src/Modules/AuditTrail/ModCon.php',
@@ -203,11 +209,9 @@ return array(
203
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\ModCon' => $baseDir . '/src/Modules/BaseShield/ModCon.php',
204
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\Options' => $baseDir . '/src/Modules/BaseShield/Options.php',
205
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\Processor' => $baseDir . '/src/Modules/BaseShield/Processor.php',
206
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\ShieldProcessor' => $baseDir . '/src/Modules/BaseShield/ShieldProcessor.php',
207
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\UI' => $baseDir . '/src/Modules/BaseShield/UI.php',
208
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\AdminNotices' => $baseDir . '/src/Modules/Base/AdminNotices.php',
209
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\AjaxHandler' => $baseDir . '/src/Modules/Base/AjaxHandler.php',
210
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\BaseProcessor' => $baseDir . '/src/Modules/Base/BaseProcessor.php',
211
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\Debug' => $baseDir . '/src/Modules/Base/Debug.php',
212
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\Insights\\OverviewCards' => $baseDir . '/src/Modules/Base/Insights/OverviewCards.php',
213
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\ModCon' => $baseDir . '/src/Modules/Base/ModCon.php',
@@ -229,7 +233,9 @@ return array(
229
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\ModCon' => $baseDir . '/src/Modules/CommentsFilter/ModCon.php',
230
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Options' => $baseDir . '/src/Modules/CommentsFilter/Options.php',
231
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Processor' => $baseDir . '/src/Modules/CommentsFilter/Processor.php',
 
232
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\Bot' => $baseDir . '/src/Modules/CommentsFilter/Scan/Bot.php',
 
233
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\Human' => $baseDir . '/src/Modules/CommentsFilter/Scan/Human.php',
234
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\IsEmailTrusted' => $baseDir . '/src/Modules/CommentsFilter/Scan/IsEmailTrusted.php',
235
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\Scanner' => $baseDir . '/src/Modules/CommentsFilter/Scan/Scanner.php',
@@ -246,9 +252,6 @@ return array(
246
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Email\\Options' => $baseDir . '/src/Modules/Email/Options.php',
247
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Email\\Processor' => $baseDir . '/src/Modules/Email/Processor.php',
248
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Email\\Strings' => $baseDir . '/src/Modules/Email/Strings.php',
249
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\AjaxHandler' => $baseDir . '/src/Modules/Events/AjaxHandler.php',
250
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\Charts\\BuildData' => $baseDir . '/src/Modules/Events/Charts/BuildData.php',
251
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\Charts\\ChartRequestVO' => $baseDir . '/src/Modules/Events/Charts/ChartRequestVO.php',
252
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\Consolidate\\ConsolidateAllEvents' => $baseDir . '/src/Modules/Events/Consolidate/ConsolidateAllEvents.php',
253
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\Lib\\EventsListener' => $baseDir . '/src/Modules/Events/Lib/EventsListener.php',
254
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\Lib\\EventsService' => $baseDir . '/src/Modules/Events/Lib/EventsService.php',
@@ -351,7 +354,9 @@ return array(
351
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\AjaxHandler' => $baseDir . '/src/Modules/IPs/AjaxHandler.php',
352
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\Base' => $baseDir . '/src/Modules/IPs/BotTrack/Base.php',
353
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\Track404' => $baseDir . '/src/Modules/IPs/BotTrack/Track404.php',
 
354
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackFakeWebCrawler' => $baseDir . '/src/Modules/IPs/BotTrack/TrackFakeWebCrawler.php',
 
355
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackLinkCheese' => $baseDir . '/src/Modules/IPs/BotTrack/TrackLinkCheese.php',
356
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackLoginFailed' => $baseDir . '/src/Modules/IPs/BotTrack/TrackLoginFailed.php',
357
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackLoginInvalid' => $baseDir . '/src/Modules/IPs/BotTrack/TrackLoginInvalid.php',
@@ -367,6 +372,13 @@ return array(
367
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\AutoUnblock' => $baseDir . '/src/Modules/IPs/Lib/AutoUnblock.php',
368
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\BlacklistHandler' => $baseDir . '/src/Modules/IPs/Lib/BlacklistHandler.php',
369
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\BlockRequest' => $baseDir . '/src/Modules/IPs/Lib/BlockRequest.php',
 
 
 
 
 
 
 
370
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\IpAnalyse\\BuildDisplay' => $baseDir . '/src/Modules/IPs/Lib/IpAnalyse/BuildDisplay.php',
371
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\IpAnalyse\\FindAllPluginIps' => $baseDir . '/src/Modules/IPs/Lib/IpAnalyse/FindAllPluginIps.php',
372
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\OffenseTracker' => $baseDir . '/src/Modules/IPs/Lib/OffenseTracker.php',
@@ -418,6 +430,19 @@ return array(
418
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\MainWP\\Server\\UI\\PageRender\\NotShieldPro' => $baseDir . '/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/NotShieldPro.php',
419
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\MainWP\\Server\\UI\\PageRender\\PluginOutOfDate' => $baseDir . '/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/PluginOutOfDate.php',
420
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\MainWP\\Server\\UI\\PageRender\\SitesList' => $baseDir . '/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/SitesList.php',
 
 
 
 
 
 
 
 
 
 
 
 
 
421
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\ModCon' => $baseDir . '/src/Modules/Integrations/ModCon.php',
422
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Options' => $baseDir . '/src/Modules/Integrations/Options.php',
423
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Processor' => $baseDir . '/src/Modules/Integrations/Processor.php',
@@ -460,6 +485,7 @@ return array(
460
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\FormProviders\\WPMembers' => $baseDir . '/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WPMembers.php',
461
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\FormProviders\\WooCommerce' => $baseDir . '/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WooCommerce.php',
462
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\FormProviders\\WordPress' => $baseDir . '/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WordPress.php',
 
463
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\ProtectionProviders\\BaseProtectionProvider' => $baseDir . '/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/BaseProtectionProvider.php',
464
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\ProtectionProviders\\CoolDown' => $baseDir . '/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/CoolDown.php',
465
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\ProtectionProviders\\GaspJs' => $baseDir . '/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/GaspJs.php',
@@ -529,6 +555,12 @@ return array(
529
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Plugin\\WpCli\\Import' => $baseDir . '/src/Modules/Plugin/WpCli/Import.php',
530
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Plugin\\WpCli\\Reset' => $baseDir . '/src/Modules/Plugin/WpCli/Reset.php',
531
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Plugin\\WpCli\\ToggleDebug' => $baseDir . '/src/Modules/Plugin/WpCli/ToggleDebug.php',
 
 
 
 
 
 
532
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Debug' => $baseDir . '/src/Modules/Reporting/Debug.php',
533
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Insights\\OverviewCards' => $baseDir . '/src/Modules/Reporting/Insights/OverviewCards.php',
534
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Lib\\ReportingController' => $baseDir . '/src/Modules/Reporting/Lib/ReportingController.php',
@@ -745,11 +777,14 @@ return array(
745
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Consumer\\WpUserConsumer' => $baseDir . '/src/Utilities/Consumer/WpUserConsumer.php',
746
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Github\\ListTags' => $baseDir . '/src/Utilities/Github/ListTags.php',
747
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\HCaptcha\\TestRequest' => $baseDir . '/src/Utilities/HCaptcha/TestRequest.php',
 
748
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Options\\CleanStorage' => $baseDir . '/src/Utilities/Options/CleanStorage.php',
749
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\ReCaptcha\\Enqueue' => $baseDir . '/src/Utilities/ReCaptcha/Enqueue.php',
750
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\ReCaptcha\\TestRequest' => $baseDir . '/src/Utilities/ReCaptcha/TestRequest.php',
751
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\ReCaptcha\\WordpressPost' => $baseDir . '/src/Utilities/ReCaptcha/WordpressPost.php',
 
752
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Time\\WorldTimeApi' => $baseDir . '/src/Utilities/Time/WorldTimeApi.php',
 
753
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Tool\\FormatBytes' => $baseDir . '/src/Utilities/Tool/FormatBytes.php',
754
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Tool\\IpListSort' => $baseDir . '/src/Utilities/Tool/IpListSort.php',
755
  'FernleafSystems\\Wordpress\\Services\\Core\\AdminNotices' => $vendorDir . '/fernleafsystems/wordpress-services/src/Core/AdminNotices.php',
@@ -835,6 +870,7 @@ return array(
835
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Malware\\Whitelist\\RequestVO' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Malware/Whitelist/RequestVO.php',
836
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Services\\Base' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Services/Base.php',
837
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Services\\IPs' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Services/IPs.php',
 
838
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Services\\RequestVO' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Services/RequestVO.php',
839
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Token\\Base' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Token/Base.php',
840
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Token\\RequestVO' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Token/RequestVO.php',
@@ -861,6 +897,7 @@ return array(
861
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Licenses\\Lookup' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Licenses/Lookup.php',
862
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Net\\BaseIP' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Net/BaseIP.php',
863
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Net\\FindSourceFromIp' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Net/FindSourceFromIp.php',
 
864
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Net\\IpIdentify' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Net/IpIdentify.php',
865
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Net\\VisitorIpDetection' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Net/VisitorIpDetection.php',
866
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Obfuscate' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Obfuscate.php',
62
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Ajax\\Init' => $baseDir . '/src/Controller/Ajax/Init.php',
63
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Ajax\\Response' => $baseDir . '/src/Controller/Ajax/Response.php',
64
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Assets\\Enqueue' => $baseDir . '/src/Controller/Assets/Enqueue.php',
65
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Assets\\Paths' => $baseDir . '/src/Controller/Assets/Paths.php',
66
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Assets\\Urls' => $baseDir . '/src/Controller/Assets/Urls.php',
67
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Config\\ConfigVO' => $baseDir . '/src/Controller/Config/ConfigVO.php',
68
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Config\\Ops\\LoadConfig' => $baseDir . '/src/Controller/Config/Ops/LoadConfig.php',
71
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Controller' => $baseDir . '/src/Controller/Controller.php',
72
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\I18n\\GetAllAvailableLocales' => $baseDir . '/src/Controller/I18n/GetAllAvailableLocales.php',
73
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\I18n\\LoadTextDomain' => $baseDir . '/src/Controller/I18n/LoadTextDomain.php',
74
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Utilities\\CaptureMyUpgrade' => $baseDir . '/src/Controller/Utilities/CaptureMyUpgrade.php',
75
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Utilities\\DebugMode' => $baseDir . '/src/Controller/Utilities/DebugMode.php',
76
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Utilities\\Upgrade' => $baseDir . '/src/Controller/Utilities/Upgrade.php',
77
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Crons\\BaseCron' => $baseDir . '/src/Crons/BaseCron.php',
97
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Handler' => $baseDir . '/src/Databases/Base/Handler.php',
98
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\HandlerConsumer' => $baseDir . '/src/Databases/Base/HandlerConsumer.php',
99
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Insert' => $baseDir . '/src/Databases/Base/Insert.php',
100
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Iterator' => $baseDir . '/src/Databases/Base/Iterator.php',
101
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Select' => $baseDir . '/src/Databases/Base/Select.php',
102
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Traits\\Select_IPTable' => $baseDir . '/src/Databases/Base/Traits/Select_IPTable.php',
103
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Update' => $baseDir . '/src/Databases/Base/Update.php',
104
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\BotSignals\\Common' => $baseDir . '/src/Databases/BotSignals/Common.php',
105
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\BotSignals\\Delete' => $baseDir . '/src/Databases/BotSignals/Delete.php',
106
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\BotSignals\\EntryVO' => $baseDir . '/src/Databases/BotSignals/EntryVO.php',
107
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\BotSignals\\Handler' => $baseDir . '/src/Databases/BotSignals/Handler.php',
108
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\BotSignals\\Insert' => $baseDir . '/src/Databases/BotSignals/Insert.php',
109
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\BotSignals\\Select' => $baseDir . '/src/Databases/BotSignals/Select.php',
110
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\BotSignals\\Update' => $baseDir . '/src/Databases/BotSignals/Update.php',
111
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\ChangeTracking\\Delete' => $baseDir . '/src/Databases/ChangeTracking/Delete.php',
112
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\ChangeTracking\\EntryVO' => $baseDir . '/src/Databases/ChangeTracking/EntryVO.php',
113
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\ChangeTracking\\Handler' => $baseDir . '/src/Databases/ChangeTracking/Handler.php',
170
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Session\\Insert' => $baseDir . '/src/Databases/Session/Insert.php',
171
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Session\\Select' => $baseDir . '/src/Databases/Session/Select.php',
172
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Session\\Update' => $baseDir . '/src/Databases/Session/Update.php',
 
 
 
 
 
 
173
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Traffic\\BaseTraffic' => $baseDir . '/src/Databases/Traffic/BaseTraffic.php',
174
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Traffic\\Delete' => $baseDir . '/src/Databases/Traffic/Delete.php',
175
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Traffic\\EntryVO' => $baseDir . '/src/Databases/Traffic/EntryVO.php',
188
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Auditors\\Users' => $baseDir . '/src/Modules/AuditTrail/Auditors/Users.php',
189
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Auditors\\Wordpress' => $baseDir . '/src/Modules/AuditTrail/Auditors/Wordpress.php',
190
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Insights\\OverviewCards' => $baseDir . '/src/Modules/AuditTrail/Insights/OverviewCards.php',
191
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Lib\\AuditMessageBuilder' => $baseDir . '/src/Modules/AuditTrail/Lib/AuditMessageBuilder.php',
192
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Lib\\AuditWriter' => $baseDir . '/src/Modules/AuditTrail/Lib/AuditWriter.php',
193
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Lib\\Ops\\Commit' => $baseDir . '/src/Modules/AuditTrail/Lib/Ops/Commit.php',
194
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\ModCon' => $baseDir . '/src/Modules/AuditTrail/ModCon.php',
209
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\ModCon' => $baseDir . '/src/Modules/BaseShield/ModCon.php',
210
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\Options' => $baseDir . '/src/Modules/BaseShield/Options.php',
211
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\Processor' => $baseDir . '/src/Modules/BaseShield/Processor.php',
 
212
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\UI' => $baseDir . '/src/Modules/BaseShield/UI.php',
213
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\AdminNotices' => $baseDir . '/src/Modules/Base/AdminNotices.php',
214
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\AjaxHandler' => $baseDir . '/src/Modules/Base/AjaxHandler.php',
 
215
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\Debug' => $baseDir . '/src/Modules/Base/Debug.php',
216
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\Insights\\OverviewCards' => $baseDir . '/src/Modules/Base/Insights/OverviewCards.php',
217
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\ModCon' => $baseDir . '/src/Modules/Base/ModCon.php',
233
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\ModCon' => $baseDir . '/src/Modules/CommentsFilter/ModCon.php',
234
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Options' => $baseDir . '/src/Modules/CommentsFilter/Options.php',
235
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Processor' => $baseDir . '/src/Modules/CommentsFilter/Processor.php',
236
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\AntiBot' => $baseDir . '/src/Modules/CommentsFilter/Scan/AntiBot.php',
237
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\Bot' => $baseDir . '/src/Modules/CommentsFilter/Scan/Bot.php',
238
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\CommentAdditiveCleaner' => $baseDir . '/src/Modules/CommentsFilter/Scan/CommentAdditiveCleaner.php',
239
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\Human' => $baseDir . '/src/Modules/CommentsFilter/Scan/Human.php',
240
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\IsEmailTrusted' => $baseDir . '/src/Modules/CommentsFilter/Scan/IsEmailTrusted.php',
241
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\Scanner' => $baseDir . '/src/Modules/CommentsFilter/Scan/Scanner.php',
252
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Email\\Options' => $baseDir . '/src/Modules/Email/Options.php',
253
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Email\\Processor' => $baseDir . '/src/Modules/Email/Processor.php',
254
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Email\\Strings' => $baseDir . '/src/Modules/Email/Strings.php',
 
 
 
255
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\Consolidate\\ConsolidateAllEvents' => $baseDir . '/src/Modules/Events/Consolidate/ConsolidateAllEvents.php',
256
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\Lib\\EventsListener' => $baseDir . '/src/Modules/Events/Lib/EventsListener.php',
257
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\Lib\\EventsService' => $baseDir . '/src/Modules/Events/Lib/EventsService.php',
354
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\AjaxHandler' => $baseDir . '/src/Modules/IPs/AjaxHandler.php',
355
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\Base' => $baseDir . '/src/Modules/IPs/BotTrack/Base.php',
356
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\Track404' => $baseDir . '/src/Modules/IPs/BotTrack/Track404.php',
357
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackCommentSpam' => $baseDir . '/src/Modules/IPs/BotTrack/TrackCommentSpam.php',
358
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackFakeWebCrawler' => $baseDir . '/src/Modules/IPs/BotTrack/TrackFakeWebCrawler.php',
359
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackInvalidScriptLoad' => $baseDir . '/src/Modules/IPs/BotTrack/TrackInvalidScriptLoad.php',
360
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackLinkCheese' => $baseDir . '/src/Modules/IPs/BotTrack/TrackLinkCheese.php',
361
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackLoginFailed' => $baseDir . '/src/Modules/IPs/BotTrack/TrackLoginFailed.php',
362
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackLoginInvalid' => $baseDir . '/src/Modules/IPs/BotTrack/TrackLoginInvalid.php',
372
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\AutoUnblock' => $baseDir . '/src/Modules/IPs/Lib/AutoUnblock.php',
373
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\BlacklistHandler' => $baseDir . '/src/Modules/IPs/Lib/BlacklistHandler.php',
374
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\BlockRequest' => $baseDir . '/src/Modules/IPs/Lib/BlockRequest.php',
375
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\Bots\\BotSignalsController' => $baseDir . '/src/Modules/IPs/Lib/Bots/BotSignalsController.php',
376
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\Bots\\BotSignalsRecord' => $baseDir . '/src/Modules/IPs/Lib/Bots/BotSignalsRecord.php',
377
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\Bots\\Calculator\\BuildScores' => $baseDir . '/src/Modules/IPs/Lib/Bots/Calculator/BuildScores.php',
378
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\Bots\\Calculator\\CalculateVisitorBotScores' => $baseDir . '/src/Modules/IPs/Lib/Bots/Calculator/CalculateVisitorBotScores.php',
379
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\Bots\\EventListener' => $baseDir . '/src/Modules/IPs/Lib/Bots/EventListener.php',
380
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\Bots\\NotBot\\InsertNotBotJs' => $baseDir . '/src/Modules/IPs/Lib/Bots/NotBot/InsertNotBotJs.php',
381
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\Bots\\NotBot\\NotBotHandler' => $baseDir . '/src/Modules/IPs/Lib/Bots/NotBot/NotBotHandler.php',
382
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\IpAnalyse\\BuildDisplay' => $baseDir . '/src/Modules/IPs/Lib/IpAnalyse/BuildDisplay.php',
383
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\IpAnalyse\\FindAllPluginIps' => $baseDir . '/src/Modules/IPs/Lib/IpAnalyse/FindAllPluginIps.php',
384
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\OffenseTracker' => $baseDir . '/src/Modules/IPs/Lib/OffenseTracker.php',
430
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\MainWP\\Server\\UI\\PageRender\\NotShieldPro' => $baseDir . '/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/NotShieldPro.php',
431
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\MainWP\\Server\\UI\\PageRender\\PluginOutOfDate' => $baseDir . '/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/PluginOutOfDate.php',
432
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\MainWP\\Server\\UI\\PageRender\\SitesList' => $baseDir . '/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/SitesList.php',
433
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\Base' => $baseDir . '/src/Modules/Integrations/Lib/Spam/Handlers/Base.php',
434
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\ContactForm7' => $baseDir . '/src/Modules/Integrations/Lib/Spam/Handlers/ContactForm7.php',
435
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\ElementorPro' => $baseDir . '/src/Modules/Integrations/Lib/Spam/Handlers/ElementorPro.php',
436
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\FluentForms' => $baseDir . '/src/Modules/Integrations/Lib/Spam/Handlers/FluentForms.php',
437
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\FormidableForms' => $baseDir . '/src/Modules/Integrations/Lib/Spam/Handlers/FormidableForms.php',
438
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\Forminator' => $baseDir . '/src/Modules/Integrations/Lib/Spam/Handlers/Forminator.php',
439
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\GravityForms' => $baseDir . '/src/Modules/Integrations/Lib/Spam/Handlers/GravityForms.php',
440
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\Helpers\\NinjaForms_ShieldSpamAction' => $baseDir . '/src/Modules/Integrations/Lib/Spam/Handlers/Helpers/NinjaForms_ShieldSpamAction.php',
441
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\KaliForms' => $baseDir . '/src/Modules/Integrations/Lib/Spam/Handlers/KaliForms.php',
442
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\NinjaForms' => $baseDir . '/src/Modules/Integrations/Lib/Spam/Handlers/NinjaForms.php',
443
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\WPForms' => $baseDir . '/src/Modules/Integrations/Lib/Spam/Handlers/WPForms.php',
444
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\WpForo' => $baseDir . '/src/Modules/Integrations/Lib/Spam/Handlers/WpForo.php',
445
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\SpamController' => $baseDir . '/src/Modules/Integrations/Lib/Spam/SpamController.php',
446
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\ModCon' => $baseDir . '/src/Modules/Integrations/ModCon.php',
447
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Options' => $baseDir . '/src/Modules/Integrations/Options.php',
448
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Processor' => $baseDir . '/src/Modules/Integrations/Processor.php',
485
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\FormProviders\\WPMembers' => $baseDir . '/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WPMembers.php',
486
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\FormProviders\\WooCommerce' => $baseDir . '/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WooCommerce.php',
487
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\FormProviders\\WordPress' => $baseDir . '/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WordPress.php',
488
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\ProtectionProviders\\AntiBot' => $baseDir . '/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/AntiBot.php',
489
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\ProtectionProviders\\BaseProtectionProvider' => $baseDir . '/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/BaseProtectionProvider.php',
490
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\ProtectionProviders\\CoolDown' => $baseDir . '/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/CoolDown.php',
491
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\ProtectionProviders\\GaspJs' => $baseDir . '/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/GaspJs.php',
555
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Plugin\\WpCli\\Import' => $baseDir . '/src/Modules/Plugin/WpCli/Import.php',
556
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Plugin\\WpCli\\Reset' => $baseDir . '/src/Modules/Plugin/WpCli/Reset.php',
557
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Plugin\\WpCli\\ToggleDebug' => $baseDir . '/src/Modules/Plugin/WpCli/ToggleDebug.php',
558
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\AjaxHandler' => $baseDir . '/src/Modules/Reporting/AjaxHandler.php',
559
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Charts\\BaseBuildChartData' => $baseDir . '/src/Modules/Reporting/Charts/BaseBuildChartData.php',
560
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Charts\\ChartRequestConsumer' => $baseDir . '/src/Modules/Reporting/Charts/ChartRequestConsumer.php',
561
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Charts\\ChartRequestVO' => $baseDir . '/src/Modules/Reporting/Charts/ChartRequestVO.php',
562
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Charts\\CustomChartData' => $baseDir . '/src/Modules/Reporting/Charts/CustomChartData.php',
563
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Charts\\CustomChartRequestVO' => $baseDir . '/src/Modules/Reporting/Charts/CustomChartRequestVO.php',
564
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Debug' => $baseDir . '/src/Modules/Reporting/Debug.php',
565
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Insights\\OverviewCards' => $baseDir . '/src/Modules/Reporting/Insights/OverviewCards.php',
566
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Lib\\ReportingController' => $baseDir . '/src/Modules/Reporting/Lib/ReportingController.php',
777
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Consumer\\WpUserConsumer' => $baseDir . '/src/Utilities/Consumer/WpUserConsumer.php',
778
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Github\\ListTags' => $baseDir . '/src/Utilities/Github/ListTags.php',
779
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\HCaptcha\\TestRequest' => $baseDir . '/src/Utilities/HCaptcha/TestRequest.php',
780
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\HumanSpam\\TestContent' => $baseDir . '/src/Utilities/HumanSpam/TestContent.php',
781
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Options\\CleanStorage' => $baseDir . '/src/Utilities/Options/CleanStorage.php',
782
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\ReCaptcha\\Enqueue' => $baseDir . '/src/Utilities/ReCaptcha/Enqueue.php',
783
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\ReCaptcha\\TestRequest' => $baseDir . '/src/Utilities/ReCaptcha/TestRequest.php',
784
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\ReCaptcha\\WordpressPost' => $baseDir . '/src/Utilities/ReCaptcha/WordpressPost.php',
785
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Resources\\Dynamic' => $baseDir . '/src/Utilities/Resources/Dynamic.php',
786
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Time\\WorldTimeApi' => $baseDir . '/src/Utilities/Time/WorldTimeApi.php',
787
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Tool\\DbTableExport' => $baseDir . '/src/Utilities/Tool/DbTableExport.php',
788
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Tool\\FormatBytes' => $baseDir . '/src/Utilities/Tool/FormatBytes.php',
789
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Tool\\IpListSort' => $baseDir . '/src/Utilities/Tool/IpListSort.php',
790
  'FernleafSystems\\Wordpress\\Services\\Core\\AdminNotices' => $vendorDir . '/fernleafsystems/wordpress-services/src/Core/AdminNotices.php',
870
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Malware\\Whitelist\\RequestVO' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Malware/Whitelist/RequestVO.php',
871
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Services\\Base' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Services/Base.php',
872
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Services\\IPs' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Services/IPs.php',
873
+ 'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Services\\ProviderIPs' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Services/ProviderIPs.php',
874
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Services\\RequestVO' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Services/RequestVO.php',
875
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Token\\Base' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Token/Base.php',
876
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Token\\RequestVO' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Token/RequestVO.php',
897
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Licenses\\Lookup' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Licenses/Lookup.php',
898
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Net\\BaseIP' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Net/BaseIP.php',
899
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Net\\FindSourceFromIp' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Net/FindSourceFromIp.php',
900
+ 'FernleafSystems\\Wordpress\\Services\\Utilities\\Net\\IpID' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Net/IpID.php',
901
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Net\\IpIdentify' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Net/IpIdentify.php',
902
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Net\\VisitorIpDetection' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Net/VisitorIpDetection.php',
903
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Obfuscate' => $vendorDir . '/fernleafsystems/wordpress-services/src/Utilities/Obfuscate.php',
src/lib/vendor/composer/autoload_files.php CHANGED
@@ -10,4 +10,5 @@ return array(
10
  '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
11
  'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php',
12
  'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php',
 
13
  );
10
  '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
11
  'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php',
12
  'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php',
13
+ 'b14ca0bb4d408c293d6ea9b68f1b4abe' => $baseDir . '/functions/functions.php',
14
  );
src/lib/vendor/composer/autoload_static.php CHANGED
@@ -11,6 +11,7 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
11
  '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
12
  'e39a8b23c42d4e1452234d762b03835a' => __DIR__ . '/..' . '/ramsey/uuid/src/functions.php',
13
  'def43f6c87e4f8dfd0c9e1b1bab14fe8' => __DIR__ . '/..' . '/symfony/polyfill-iconv/bootstrap.php',
 
14
  );
15
 
16
  public static $prefixLengthsPsr4 = array (
@@ -229,6 +230,7 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
229
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Ajax\\Init' => __DIR__ . '/../..' . '/src/Controller/Ajax/Init.php',
230
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Ajax\\Response' => __DIR__ . '/../..' . '/src/Controller/Ajax/Response.php',
231
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Assets\\Enqueue' => __DIR__ . '/../..' . '/src/Controller/Assets/Enqueue.php',
 
232
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Assets\\Urls' => __DIR__ . '/../..' . '/src/Controller/Assets/Urls.php',
233
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Config\\ConfigVO' => __DIR__ . '/../..' . '/src/Controller/Config/ConfigVO.php',
234
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Config\\Ops\\LoadConfig' => __DIR__ . '/../..' . '/src/Controller/Config/Ops/LoadConfig.php',
@@ -237,6 +239,7 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
237
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Controller' => __DIR__ . '/../..' . '/src/Controller/Controller.php',
238
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\I18n\\GetAllAvailableLocales' => __DIR__ . '/../..' . '/src/Controller/I18n/GetAllAvailableLocales.php',
239
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\I18n\\LoadTextDomain' => __DIR__ . '/../..' . '/src/Controller/I18n/LoadTextDomain.php',
 
240
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Utilities\\DebugMode' => __DIR__ . '/../..' . '/src/Controller/Utilities/DebugMode.php',
241
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Utilities\\Upgrade' => __DIR__ . '/../..' . '/src/Controller/Utilities/Upgrade.php',
242
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Crons\\BaseCron' => __DIR__ . '/../..' . '/src/Crons/BaseCron.php',
@@ -262,8 +265,17 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
262
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Handler' => __DIR__ . '/../..' . '/src/Databases/Base/Handler.php',
263
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\HandlerConsumer' => __DIR__ . '/../..' . '/src/Databases/Base/HandlerConsumer.php',
264
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Insert' => __DIR__ . '/../..' . '/src/Databases/Base/Insert.php',
 
265
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Select' => __DIR__ . '/../..' . '/src/Databases/Base/Select.php',
 
266
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Update' => __DIR__ . '/../..' . '/src/Databases/Base/Update.php',
 
 
 
 
 
 
 
267
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\ChangeTracking\\Delete' => __DIR__ . '/../..' . '/src/Databases/ChangeTracking/Delete.php',
268
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\ChangeTracking\\EntryVO' => __DIR__ . '/../..' . '/src/Databases/ChangeTracking/EntryVO.php',
269
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\ChangeTracking\\Handler' => __DIR__ . '/../..' . '/src/Databases/ChangeTracking/Handler.php',
@@ -326,12 +338,6 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
326
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Session\\Insert' => __DIR__ . '/../..' . '/src/Databases/Session/Insert.php',
327
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Session\\Select' => __DIR__ . '/../..' . '/src/Databases/Session/Select.php',
328
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Session\\Update' => __DIR__ . '/../..' . '/src/Databases/Session/Update.php',
329
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Tally\\Delete' => __DIR__ . '/../..' . '/src/Databases/Tally/Delete.php',
330
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Tally\\EntryVO' => __DIR__ . '/../..' . '/src/Databases/Tally/EntryVO.php',
331
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Tally\\Handler' => __DIR__ . '/../..' . '/src/Databases/Tally/Handler.php',
332
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Tally\\Insert' => __DIR__ . '/../..' . '/src/Databases/Tally/Insert.php',
333
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Tally\\Select' => __DIR__ . '/../..' . '/src/Databases/Tally/Select.php',
334
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Tally\\Update' => __DIR__ . '/../..' . '/src/Databases/Tally/Update.php',
335
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Traffic\\BaseTraffic' => __DIR__ . '/../..' . '/src/Databases/Traffic/BaseTraffic.php',
336
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Traffic\\Delete' => __DIR__ . '/../..' . '/src/Databases/Traffic/Delete.php',
337
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Traffic\\EntryVO' => __DIR__ . '/../..' . '/src/Databases/Traffic/EntryVO.php',
@@ -350,6 +356,7 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
350
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Auditors\\Users' => __DIR__ . '/../..' . '/src/Modules/AuditTrail/Auditors/Users.php',
351
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Auditors\\Wordpress' => __DIR__ . '/../..' . '/src/Modules/AuditTrail/Auditors/Wordpress.php',
352
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Insights\\OverviewCards' => __DIR__ . '/../..' . '/src/Modules/AuditTrail/Insights/OverviewCards.php',
 
353
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Lib\\AuditWriter' => __DIR__ . '/../..' . '/src/Modules/AuditTrail/Lib/AuditWriter.php',
354
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Lib\\Ops\\Commit' => __DIR__ . '/../..' . '/src/Modules/AuditTrail/Lib/Ops/Commit.php',
355
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\ModCon' => __DIR__ . '/../..' . '/src/Modules/AuditTrail/ModCon.php',
@@ -370,11 +377,9 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
370
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\ModCon' => __DIR__ . '/../..' . '/src/Modules/BaseShield/ModCon.php',
371
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\Options' => __DIR__ . '/../..' . '/src/Modules/BaseShield/Options.php',
372
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\Processor' => __DIR__ . '/../..' . '/src/Modules/BaseShield/Processor.php',
373
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\ShieldProcessor' => __DIR__ . '/../..' . '/src/Modules/BaseShield/ShieldProcessor.php',
374
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\UI' => __DIR__ . '/../..' . '/src/Modules/BaseShield/UI.php',
375
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\AdminNotices' => __DIR__ . '/../..' . '/src/Modules/Base/AdminNotices.php',
376
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\AjaxHandler' => __DIR__ . '/../..' . '/src/Modules/Base/AjaxHandler.php',
377
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\BaseProcessor' => __DIR__ . '/../..' . '/src/Modules/Base/BaseProcessor.php',
378
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\Debug' => __DIR__ . '/../..' . '/src/Modules/Base/Debug.php',
379
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\Insights\\OverviewCards' => __DIR__ . '/../..' . '/src/Modules/Base/Insights/OverviewCards.php',
380
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\ModCon' => __DIR__ . '/../..' . '/src/Modules/Base/ModCon.php',
@@ -396,7 +401,9 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
396
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\ModCon' => __DIR__ . '/../..' . '/src/Modules/CommentsFilter/ModCon.php',
397
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Options' => __DIR__ . '/../..' . '/src/Modules/CommentsFilter/Options.php',
398
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Processor' => __DIR__ . '/../..' . '/src/Modules/CommentsFilter/Processor.php',
 
399
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\Bot' => __DIR__ . '/../..' . '/src/Modules/CommentsFilter/Scan/Bot.php',
 
400
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\Human' => __DIR__ . '/../..' . '/src/Modules/CommentsFilter/Scan/Human.php',
401
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\IsEmailTrusted' => __DIR__ . '/../..' . '/src/Modules/CommentsFilter/Scan/IsEmailTrusted.php',
402
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\Scanner' => __DIR__ . '/../..' . '/src/Modules/CommentsFilter/Scan/Scanner.php',
@@ -413,9 +420,6 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
413
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Email\\Options' => __DIR__ . '/../..' . '/src/Modules/Email/Options.php',
414
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Email\\Processor' => __DIR__ . '/../..' . '/src/Modules/Email/Processor.php',
415
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Email\\Strings' => __DIR__ . '/../..' . '/src/Modules/Email/Strings.php',
416
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\AjaxHandler' => __DIR__ . '/../..' . '/src/Modules/Events/AjaxHandler.php',
417
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\Charts\\BuildData' => __DIR__ . '/../..' . '/src/Modules/Events/Charts/BuildData.php',
418
- 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\Charts\\ChartRequestVO' => __DIR__ . '/../..' . '/src/Modules/Events/Charts/ChartRequestVO.php',
419
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\Consolidate\\ConsolidateAllEvents' => __DIR__ . '/../..' . '/src/Modules/Events/Consolidate/ConsolidateAllEvents.php',
420
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\Lib\\EventsListener' => __DIR__ . '/../..' . '/src/Modules/Events/Lib/EventsListener.php',
421
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\Lib\\EventsService' => __DIR__ . '/../..' . '/src/Modules/Events/Lib/EventsService.php',
@@ -518,7 +522,9 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
518
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\AjaxHandler' => __DIR__ . '/../..' . '/src/Modules/IPs/AjaxHandler.php',
519
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\Base' => __DIR__ . '/../..' . '/src/Modules/IPs/BotTrack/Base.php',
520
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\Track404' => __DIR__ . '/../..' . '/src/Modules/IPs/BotTrack/Track404.php',
 
521
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackFakeWebCrawler' => __DIR__ . '/../..' . '/src/Modules/IPs/BotTrack/TrackFakeWebCrawler.php',
 
522
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackLinkCheese' => __DIR__ . '/../..' . '/src/Modules/IPs/BotTrack/TrackLinkCheese.php',
523
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackLoginFailed' => __DIR__ . '/../..' . '/src/Modules/IPs/BotTrack/TrackLoginFailed.php',
524
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackLoginInvalid' => __DIR__ . '/../..' . '/src/Modules/IPs/BotTrack/TrackLoginInvalid.php',
@@ -534,6 +540,13 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
534
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\AutoUnblock' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/AutoUnblock.php',
535
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\BlacklistHandler' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/BlacklistHandler.php',
536
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\BlockRequest' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/BlockRequest.php',
 
 
 
 
 
 
 
537
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\IpAnalyse\\BuildDisplay' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/IpAnalyse/BuildDisplay.php',
538
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\IpAnalyse\\FindAllPluginIps' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/IpAnalyse/FindAllPluginIps.php',
539
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\OffenseTracker' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/OffenseTracker.php',
@@ -585,6 +598,19 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
585
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\MainWP\\Server\\UI\\PageRender\\NotShieldPro' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/NotShieldPro.php',
586
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\MainWP\\Server\\UI\\PageRender\\PluginOutOfDate' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/PluginOutOfDate.php',
587
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\MainWP\\Server\\UI\\PageRender\\SitesList' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/SitesList.php',
 
 
 
 
 
 
 
 
 
 
 
 
 
588
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\ModCon' => __DIR__ . '/../..' . '/src/Modules/Integrations/ModCon.php',
589
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Options' => __DIR__ . '/../..' . '/src/Modules/Integrations/Options.php',
590
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Processor' => __DIR__ . '/../..' . '/src/Modules/Integrations/Processor.php',
@@ -627,6 +653,7 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
627
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\FormProviders\\WPMembers' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WPMembers.php',
628
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\FormProviders\\WooCommerce' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WooCommerce.php',
629
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\FormProviders\\WordPress' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WordPress.php',
 
630
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\ProtectionProviders\\BaseProtectionProvider' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/BaseProtectionProvider.php',
631
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\ProtectionProviders\\CoolDown' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/CoolDown.php',
632
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\ProtectionProviders\\GaspJs' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/GaspJs.php',
@@ -696,6 +723,12 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
696
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Plugin\\WpCli\\Import' => __DIR__ . '/../..' . '/src/Modules/Plugin/WpCli/Import.php',
697
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Plugin\\WpCli\\Reset' => __DIR__ . '/../..' . '/src/Modules/Plugin/WpCli/Reset.php',
698
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Plugin\\WpCli\\ToggleDebug' => __DIR__ . '/../..' . '/src/Modules/Plugin/WpCli/ToggleDebug.php',
 
 
 
 
 
 
699
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Debug' => __DIR__ . '/../..' . '/src/Modules/Reporting/Debug.php',
700
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Insights\\OverviewCards' => __DIR__ . '/../..' . '/src/Modules/Reporting/Insights/OverviewCards.php',
701
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Lib\\ReportingController' => __DIR__ . '/../..' . '/src/Modules/Reporting/Lib/ReportingController.php',
@@ -912,11 +945,14 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
912
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Consumer\\WpUserConsumer' => __DIR__ . '/../..' . '/src/Utilities/Consumer/WpUserConsumer.php',
913
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Github\\ListTags' => __DIR__ . '/../..' . '/src/Utilities/Github/ListTags.php',
914
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\HCaptcha\\TestRequest' => __DIR__ . '/../..' . '/src/Utilities/HCaptcha/TestRequest.php',
 
915
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Options\\CleanStorage' => __DIR__ . '/../..' . '/src/Utilities/Options/CleanStorage.php',
916
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\ReCaptcha\\Enqueue' => __DIR__ . '/../..' . '/src/Utilities/ReCaptcha/Enqueue.php',
917
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\ReCaptcha\\TestRequest' => __DIR__ . '/../..' . '/src/Utilities/ReCaptcha/TestRequest.php',
918
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\ReCaptcha\\WordpressPost' => __DIR__ . '/../..' . '/src/Utilities/ReCaptcha/WordpressPost.php',
 
919
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Time\\WorldTimeApi' => __DIR__ . '/../..' . '/src/Utilities/Time/WorldTimeApi.php',
 
920
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Tool\\FormatBytes' => __DIR__ . '/../..' . '/src/Utilities/Tool/FormatBytes.php',
921
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Tool\\IpListSort' => __DIR__ . '/../..' . '/src/Utilities/Tool/IpListSort.php',
922
  'FernleafSystems\\Wordpress\\Services\\Core\\AdminNotices' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Core/AdminNotices.php',
@@ -1002,6 +1038,7 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
1002
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Malware\\Whitelist\\RequestVO' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Malware/Whitelist/RequestVO.php',
1003
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Services\\Base' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Services/Base.php',
1004
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Services\\IPs' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Services/IPs.php',
 
1005
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Services\\RequestVO' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Services/RequestVO.php',
1006
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Token\\Base' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Token/Base.php',
1007
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Token\\RequestVO' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Token/RequestVO.php',
@@ -1028,6 +1065,7 @@ class ComposerStaticInit4fc2c6daaffaf40b64b79b6d26830171
1028
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Licenses\\Lookup' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Licenses/Lookup.php',
1029
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Net\\BaseIP' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Net/BaseIP.php',
1030
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Net\\FindSourceFromIp' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Net/FindSourceFromIp.php',
 
11
  '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
12
  'e39a8b23c42d4e1452234d762b03835a' => __DIR__ . '/..' . '/ramsey/uuid/src/functions.php',
13
  'def43f6c87e4f8dfd0c9e1b1bab14fe8' => __DIR__ . '/..' . '/symfony/polyfill-iconv/bootstrap.php',
14
+ 'b14ca0bb4d408c293d6ea9b68f1b4abe' => __DIR__ . '/../..' . '/functions/functions.php',
15
  );
16
 
17
  public static $prefixLengthsPsr4 = array (
230
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Ajax\\Init' => __DIR__ . '/../..' . '/src/Controller/Ajax/Init.php',
231
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Ajax\\Response' => __DIR__ . '/../..' . '/src/Controller/Ajax/Response.php',
232
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Assets\\Enqueue' => __DIR__ . '/../..' . '/src/Controller/Assets/Enqueue.php',
233
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Assets\\Paths' => __DIR__ . '/../..' . '/src/Controller/Assets/Paths.php',
234
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Assets\\Urls' => __DIR__ . '/../..' . '/src/Controller/Assets/Urls.php',
235
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Config\\ConfigVO' => __DIR__ . '/../..' . '/src/Controller/Config/ConfigVO.php',
236
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Config\\Ops\\LoadConfig' => __DIR__ . '/../..' . '/src/Controller/Config/Ops/LoadConfig.php',
239
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Controller' => __DIR__ . '/../..' . '/src/Controller/Controller.php',
240
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\I18n\\GetAllAvailableLocales' => __DIR__ . '/../..' . '/src/Controller/I18n/GetAllAvailableLocales.php',
241
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\I18n\\LoadTextDomain' => __DIR__ . '/../..' . '/src/Controller/I18n/LoadTextDomain.php',
242
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Utilities\\CaptureMyUpgrade' => __DIR__ . '/../..' . '/src/Controller/Utilities/CaptureMyUpgrade.php',
243
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Utilities\\DebugMode' => __DIR__ . '/../..' . '/src/Controller/Utilities/DebugMode.php',
244
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Controller\\Utilities\\Upgrade' => __DIR__ . '/../..' . '/src/Controller/Utilities/Upgrade.php',
245
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Crons\\BaseCron' => __DIR__ . '/../..' . '/src/Crons/BaseCron.php',
265
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Handler' => __DIR__ . '/../..' . '/src/Databases/Base/Handler.php',
266
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\HandlerConsumer' => __DIR__ . '/../..' . '/src/Databases/Base/HandlerConsumer.php',
267
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Insert' => __DIR__ . '/../..' . '/src/Databases/Base/Insert.php',
268
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Iterator' => __DIR__ . '/../..' . '/src/Databases/Base/Iterator.php',
269
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Select' => __DIR__ . '/../..' . '/src/Databases/Base/Select.php',
270
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Traits\\Select_IPTable' => __DIR__ . '/../..' . '/src/Databases/Base/Traits/Select_IPTable.php',
271
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Base\\Update' => __DIR__ . '/../..' . '/src/Databases/Base/Update.php',
272
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\BotSignals\\Common' => __DIR__ . '/../..' . '/src/Databases/BotSignals/Common.php',
273
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\BotSignals\\Delete' => __DIR__ . '/../..' . '/src/Databases/BotSignals/Delete.php',
274
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\BotSignals\\EntryVO' => __DIR__ . '/../..' . '/src/Databases/BotSignals/EntryVO.php',
275
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\BotSignals\\Handler' => __DIR__ . '/../..' . '/src/Databases/BotSignals/Handler.php',
276
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\BotSignals\\Insert' => __DIR__ . '/../..' . '/src/Databases/BotSignals/Insert.php',
277
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\BotSignals\\Select' => __DIR__ . '/../..' . '/src/Databases/BotSignals/Select.php',
278
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\BotSignals\\Update' => __DIR__ . '/../..' . '/src/Databases/BotSignals/Update.php',
279
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\ChangeTracking\\Delete' => __DIR__ . '/../..' . '/src/Databases/ChangeTracking/Delete.php',
280
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\ChangeTracking\\EntryVO' => __DIR__ . '/../..' . '/src/Databases/ChangeTracking/EntryVO.php',
281
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\ChangeTracking\\Handler' => __DIR__ . '/../..' . '/src/Databases/ChangeTracking/Handler.php',
338
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Session\\Insert' => __DIR__ . '/../..' . '/src/Databases/Session/Insert.php',
339
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Session\\Select' => __DIR__ . '/../..' . '/src/Databases/Session/Select.php',
340
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Session\\Update' => __DIR__ . '/../..' . '/src/Databases/Session/Update.php',
 
 
 
 
 
 
341
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Traffic\\BaseTraffic' => __DIR__ . '/../..' . '/src/Databases/Traffic/BaseTraffic.php',
342
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Traffic\\Delete' => __DIR__ . '/../..' . '/src/Databases/Traffic/Delete.php',
343
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Traffic\\EntryVO' => __DIR__ . '/../..' . '/src/Databases/Traffic/EntryVO.php',
356
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Auditors\\Users' => __DIR__ . '/../..' . '/src/Modules/AuditTrail/Auditors/Users.php',
357
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Auditors\\Wordpress' => __DIR__ . '/../..' . '/src/Modules/AuditTrail/Auditors/Wordpress.php',
358
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Insights\\OverviewCards' => __DIR__ . '/../..' . '/src/Modules/AuditTrail/Insights/OverviewCards.php',
359
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Lib\\AuditMessageBuilder' => __DIR__ . '/../..' . '/src/Modules/AuditTrail/Lib/AuditMessageBuilder.php',
360
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Lib\\AuditWriter' => __DIR__ . '/../..' . '/src/Modules/AuditTrail/Lib/AuditWriter.php',
361
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\Lib\\Ops\\Commit' => __DIR__ . '/../..' . '/src/Modules/AuditTrail/Lib/Ops/Commit.php',
362
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\AuditTrail\\ModCon' => __DIR__ . '/../..' . '/src/Modules/AuditTrail/ModCon.php',
377
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\ModCon' => __DIR__ . '/../..' . '/src/Modules/BaseShield/ModCon.php',
378
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\Options' => __DIR__ . '/../..' . '/src/Modules/BaseShield/Options.php',
379
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\Processor' => __DIR__ . '/../..' . '/src/Modules/BaseShield/Processor.php',
 
380
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\BaseShield\\UI' => __DIR__ . '/../..' . '/src/Modules/BaseShield/UI.php',
381
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\AdminNotices' => __DIR__ . '/../..' . '/src/Modules/Base/AdminNotices.php',
382
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\AjaxHandler' => __DIR__ . '/../..' . '/src/Modules/Base/AjaxHandler.php',
 
383
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\Debug' => __DIR__ . '/../..' . '/src/Modules/Base/Debug.php',
384
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\Insights\\OverviewCards' => __DIR__ . '/../..' . '/src/Modules/Base/Insights/OverviewCards.php',
385
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Base\\ModCon' => __DIR__ . '/../..' . '/src/Modules/Base/ModCon.php',
401
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\ModCon' => __DIR__ . '/../..' . '/src/Modules/CommentsFilter/ModCon.php',
402
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Options' => __DIR__ . '/../..' . '/src/Modules/CommentsFilter/Options.php',
403
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Processor' => __DIR__ . '/../..' . '/src/Modules/CommentsFilter/Processor.php',
404
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\AntiBot' => __DIR__ . '/../..' . '/src/Modules/CommentsFilter/Scan/AntiBot.php',
405
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\Bot' => __DIR__ . '/../..' . '/src/Modules/CommentsFilter/Scan/Bot.php',
406
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\CommentAdditiveCleaner' => __DIR__ . '/../..' . '/src/Modules/CommentsFilter/Scan/CommentAdditiveCleaner.php',
407
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\Human' => __DIR__ . '/../..' . '/src/Modules/CommentsFilter/Scan/Human.php',
408
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\IsEmailTrusted' => __DIR__ . '/../..' . '/src/Modules/CommentsFilter/Scan/IsEmailTrusted.php',
409
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\CommentsFilter\\Scan\\Scanner' => __DIR__ . '/../..' . '/src/Modules/CommentsFilter/Scan/Scanner.php',
420
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Email\\Options' => __DIR__ . '/../..' . '/src/Modules/Email/Options.php',
421
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Email\\Processor' => __DIR__ . '/../..' . '/src/Modules/Email/Processor.php',
422
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Email\\Strings' => __DIR__ . '/../..' . '/src/Modules/Email/Strings.php',
 
 
 
423
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\Consolidate\\ConsolidateAllEvents' => __DIR__ . '/../..' . '/src/Modules/Events/Consolidate/ConsolidateAllEvents.php',
424
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\Lib\\EventsListener' => __DIR__ . '/../..' . '/src/Modules/Events/Lib/EventsListener.php',
425
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Events\\Lib\\EventsService' => __DIR__ . '/../..' . '/src/Modules/Events/Lib/EventsService.php',
522
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\AjaxHandler' => __DIR__ . '/../..' . '/src/Modules/IPs/AjaxHandler.php',
523
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\Base' => __DIR__ . '/../..' . '/src/Modules/IPs/BotTrack/Base.php',
524
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\Track404' => __DIR__ . '/../..' . '/src/Modules/IPs/BotTrack/Track404.php',
525
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackCommentSpam' => __DIR__ . '/../..' . '/src/Modules/IPs/BotTrack/TrackCommentSpam.php',
526
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackFakeWebCrawler' => __DIR__ . '/../..' . '/src/Modules/IPs/BotTrack/TrackFakeWebCrawler.php',
527
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackInvalidScriptLoad' => __DIR__ . '/../..' . '/src/Modules/IPs/BotTrack/TrackInvalidScriptLoad.php',
528
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackLinkCheese' => __DIR__ . '/../..' . '/src/Modules/IPs/BotTrack/TrackLinkCheese.php',
529
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackLoginFailed' => __DIR__ . '/../..' . '/src/Modules/IPs/BotTrack/TrackLoginFailed.php',
530
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\BotTrack\\TrackLoginInvalid' => __DIR__ . '/../..' . '/src/Modules/IPs/BotTrack/TrackLoginInvalid.php',
540
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\AutoUnblock' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/AutoUnblock.php',
541
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\BlacklistHandler' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/BlacklistHandler.php',
542
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\BlockRequest' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/BlockRequest.php',
543
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\Bots\\BotSignalsController' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/Bots/BotSignalsController.php',
544
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\Bots\\BotSignalsRecord' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/Bots/BotSignalsRecord.php',
545
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\Bots\\Calculator\\BuildScores' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/Bots/Calculator/BuildScores.php',
546
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\Bots\\Calculator\\CalculateVisitorBotScores' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/Bots/Calculator/CalculateVisitorBotScores.php',
547
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\Bots\\EventListener' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/Bots/EventListener.php',
548
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\Bots\\NotBot\\InsertNotBotJs' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/Bots/NotBot/InsertNotBotJs.php',
549
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\Bots\\NotBot\\NotBotHandler' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/Bots/NotBot/NotBotHandler.php',
550
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\IpAnalyse\\BuildDisplay' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/IpAnalyse/BuildDisplay.php',
551
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\IpAnalyse\\FindAllPluginIps' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/IpAnalyse/FindAllPluginIps.php',
552
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\IPs\\Lib\\OffenseTracker' => __DIR__ . '/../..' . '/src/Modules/IPs/Lib/OffenseTracker.php',
598
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\MainWP\\Server\\UI\\PageRender\\NotShieldPro' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/NotShieldPro.php',
599
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\MainWP\\Server\\UI\\PageRender\\PluginOutOfDate' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/PluginOutOfDate.php',
600
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\MainWP\\Server\\UI\\PageRender\\SitesList' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/SitesList.php',
601
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\Base' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/Spam/Handlers/Base.php',
602
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\ContactForm7' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/Spam/Handlers/ContactForm7.php',
603
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\ElementorPro' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/Spam/Handlers/ElementorPro.php',
604
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\FluentForms' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/Spam/Handlers/FluentForms.php',
605
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\FormidableForms' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/Spam/Handlers/FormidableForms.php',
606
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\Forminator' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/Spam/Handlers/Forminator.php',
607
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\GravityForms' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/Spam/Handlers/GravityForms.php',
608
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\Helpers\\NinjaForms_ShieldSpamAction' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/Spam/Handlers/Helpers/NinjaForms_ShieldSpamAction.php',
609
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\KaliForms' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/Spam/Handlers/KaliForms.php',
610
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\NinjaForms' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/Spam/Handlers/NinjaForms.php',
611
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\WPForms' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/Spam/Handlers/WPForms.php',
612
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\Handlers\\WpForo' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/Spam/Handlers/WpForo.php',
613
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Lib\\Spam\\SpamController' => __DIR__ . '/../..' . '/src/Modules/Integrations/Lib/Spam/SpamController.php',
614
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\ModCon' => __DIR__ . '/../..' . '/src/Modules/Integrations/ModCon.php',
615
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Options' => __DIR__ . '/../..' . '/src/Modules/Integrations/Options.php',
616
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Integrations\\Processor' => __DIR__ . '/../..' . '/src/Modules/Integrations/Processor.php',
653
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\FormProviders\\WPMembers' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WPMembers.php',
654
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\FormProviders\\WooCommerce' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WooCommerce.php',
655
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\FormProviders\\WordPress' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WordPress.php',
656
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\ProtectionProviders\\AntiBot' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/AntiBot.php',
657
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\ProtectionProviders\\BaseProtectionProvider' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/BaseProtectionProvider.php',
658
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\ProtectionProviders\\CoolDown' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/CoolDown.php',
659
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\LoginGuard\\Lib\\AntiBot\\ProtectionProviders\\GaspJs' => __DIR__ . '/../..' . '/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/GaspJs.php',
723
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Plugin\\WpCli\\Import' => __DIR__ . '/../..' . '/src/Modules/Plugin/WpCli/Import.php',
724
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Plugin\\WpCli\\Reset' => __DIR__ . '/../..' . '/src/Modules/Plugin/WpCli/Reset.php',
725
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Plugin\\WpCli\\ToggleDebug' => __DIR__ . '/../..' . '/src/Modules/Plugin/WpCli/ToggleDebug.php',
726
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\AjaxHandler' => __DIR__ . '/../..' . '/src/Modules/Reporting/AjaxHandler.php',
727
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Charts\\BaseBuildChartData' => __DIR__ . '/../..' . '/src/Modules/Reporting/Charts/BaseBuildChartData.php',
728
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Charts\\ChartRequestConsumer' => __DIR__ . '/../..' . '/src/Modules/Reporting/Charts/ChartRequestConsumer.php',
729
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Charts\\ChartRequestVO' => __DIR__ . '/../..' . '/src/Modules/Reporting/Charts/ChartRequestVO.php',
730
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Charts\\CustomChartData' => __DIR__ . '/../..' . '/src/Modules/Reporting/Charts/CustomChartData.php',
731
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Charts\\CustomChartRequestVO' => __DIR__ . '/../..' . '/src/Modules/Reporting/Charts/CustomChartRequestVO.php',
732
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Debug' => __DIR__ . '/../..' . '/src/Modules/Reporting/Debug.php',
733
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Insights\\OverviewCards' => __DIR__ . '/../..' . '/src/Modules/Reporting/Insights/OverviewCards.php',
734
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Modules\\Reporting\\Lib\\ReportingController' => __DIR__ . '/../..' . '/src/Modules/Reporting/Lib/ReportingController.php',
945
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Consumer\\WpUserConsumer' => __DIR__ . '/../..' . '/src/Utilities/Consumer/WpUserConsumer.php',
946
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Github\\ListTags' => __DIR__ . '/../..' . '/src/Utilities/Github/ListTags.php',
947
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\HCaptcha\\TestRequest' => __DIR__ . '/../..' . '/src/Utilities/HCaptcha/TestRequest.php',
948
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\HumanSpam\\TestContent' => __DIR__ . '/../..' . '/src/Utilities/HumanSpam/TestContent.php',
949
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Options\\CleanStorage' => __DIR__ . '/../..' . '/src/Utilities/Options/CleanStorage.php',
950
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\ReCaptcha\\Enqueue' => __DIR__ . '/../..' . '/src/Utilities/ReCaptcha/Enqueue.php',
951
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\ReCaptcha\\TestRequest' => __DIR__ . '/../..' . '/src/Utilities/ReCaptcha/TestRequest.php',
952
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\ReCaptcha\\WordpressPost' => __DIR__ . '/../..' . '/src/Utilities/ReCaptcha/WordpressPost.php',
953
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Resources\\Dynamic' => __DIR__ . '/../..' . '/src/Utilities/Resources/Dynamic.php',
954
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Time\\WorldTimeApi' => __DIR__ . '/../..' . '/src/Utilities/Time/WorldTimeApi.php',
955
+ 'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Tool\\DbTableExport' => __DIR__ . '/../..' . '/src/Utilities/Tool/DbTableExport.php',
956
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Tool\\FormatBytes' => __DIR__ . '/../..' . '/src/Utilities/Tool/FormatBytes.php',
957
  'FernleafSystems\\Wordpress\\Plugin\\Shield\\Utilities\\Tool\\IpListSort' => __DIR__ . '/../..' . '/src/Utilities/Tool/IpListSort.php',
958
  'FernleafSystems\\Wordpress\\Services\\Core\\AdminNotices' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Core/AdminNotices.php',
1038
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Malware\\Whitelist\\RequestVO' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Malware/Whitelist/RequestVO.php',
1039
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Services\\Base' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Services/Base.php',
1040
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Services\\IPs' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Services/IPs.php',
1041
+ 'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Services\\ProviderIPs' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Services/ProviderIPs.php',
1042
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Services\\RequestVO' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Services/RequestVO.php',
1043
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Token\\Base' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Token/Base.php',
1044
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Integrations\\WpHashes\\Token\\RequestVO' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Integrations/WpHashes/Token/RequestVO.php',
1065
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Licenses\\Lookup' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Licenses/Lookup.php',
1066
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Net\\BaseIP' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Net/BaseIP.php',
1067
  'FernleafSystems\\Wordpress\\Services\\Utilities\\Net\\FindSourceFromIp' => __DIR__ . '/..' . '/fernleafsystems/wordpress-services/src/Utilities/Net/FindSourceFromIp.php',
1068
+ 'FernleafSystems\\Wordpress\\Service