Shield Security for WordPress - Version 10.1.0

Version Description

Download this release

Release Info

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

Code changes from version 10.0.3 to 10.1.0

Files changed (343) hide show
  1. .htaccess +1 -1
  2. cl.json +71 -0
  3. filesnotfound.php +0 -24
  4. icwp-wpsf.php +1 -1
  5. plugin-spec.php +5 -4
  6. readme.txt +1 -1
  7. resources/css/bootstrap-select.min.css +2 -2
  8. resources/css/mainwp.css +12 -0
  9. resources/css/plugin.css +163 -37
  10. resources/images/bootstrap/arrow-down-up.svg +3 -0
  11. resources/images/bootstrap/award.svg +4 -0
  12. resources/images/bootstrap/binoculars.svg +3 -0
  13. resources/images/bootstrap/book-half.svg +3 -0
  14. resources/images/bootstrap/bug.svg +3 -0
  15. resources/images/bootstrap/chat-right-dots-fill.svg +3 -0
  16. resources/images/bootstrap/diagram-3.svg +3 -0
  17. resources/images/bootstrap/emoji-smile.svg +5 -0
  18. resources/images/bootstrap/link-45deg.svg +4 -0
  19. resources/images/bootstrap/pencil-square.svg +4 -0
  20. resources/images/bootstrap/people.svg +3 -0
  21. resources/images/bootstrap/person-badge.svg +4 -0
  22. resources/images/bootstrap/person-lines-fill.svg +3 -0
  23. resources/images/bootstrap/shield-shaded.svg +4 -0
  24. resources/images/bootstrap/sliders.svg +3 -0
  25. resources/images/bootstrap/stickies.svg +5 -0
  26. resources/images/bootstrap/sticky.svg +4 -0
  27. resources/images/bootstrap/stoplights.svg +5 -0
  28. resources/images/shield/dash-background.jpg +0 -0
  29. resources/js/bootstrap-select.min.js +2 -2
  30. resources/js/global-plugin.js +3 -3
  31. resources/js/plugin.js +3 -3
  32. resources/js/shield/ipanalyse.js +20 -7
  33. resources/js/shield/mainwp-extension.js +229 -0
  34. src/config/feature-admin_access_restriction.php +23 -15
  35. src/config/feature-audit_trail.php +31 -94
  36. src/config/feature-autoupdates.php +5 -1
  37. src/config/feature-comments_filter.php +1 -0
  38. src/config/feature-firewall.php +7 -0
  39. src/config/feature-hack_protect.php +3 -0
  40. src/config/feature-headers.php +1 -0
  41. src/config/feature-insights.php +0 -5
  42. src/config/feature-integrations.php +52 -0
  43. src/config/feature-ips.php +20 -0
  44. src/config/feature-lockdown.php +2 -0
  45. src/config/feature-login_protect.php +4 -0
  46. src/config/feature-plugin.php +38 -15
  47. src/config/feature-reporting.php +1 -0
  48. src/config/feature-traffic.php +28 -12
  49. src/config/feature-user_management.php +2 -0
  50. src/features/admin_access_restriction.php +3 -0
  51. src/features/audit_trail.php +4 -4
  52. src/features/autoupdates.php +3 -0
  53. src/features/base.php +97 -223
  54. src/features/base_wpsf.php +11 -7
  55. src/features/comments_filter.php +3 -0
  56. src/features/comms.php +4 -4
  57. src/features/email.php +3 -0
  58. src/features/events.php +4 -0
  59. src/features/firewall.php +3 -0
  60. src/features/hack_protect.php +14 -9
  61. src/features/headers.php +3 -0
  62. src/features/insights.php +14 -179
  63. src/features/ips.php +8 -46
  64. src/features/license.php +3 -0
  65. src/features/lockdown.php +3 -0
  66. src/features/login_protect.php +3 -11
  67. src/features/plugin.php +7 -0
  68. src/features/reporting.php +3 -0
  69. src/features/sessions.php +3 -0
  70. src/features/traffic.php +3 -0
  71. src/features/user_management.php +3 -0
  72. src/lib/src/Controller/Controller.php +175 -229
  73. src/lib/src/Controller/Utilities/Upgrade.php +1 -1
  74. src/lib/src/Crons/PluginCronsConsumer.php +17 -0
  75. src/lib/src/Databases/AdminNotes/Handler.php +1 -1
  76. src/lib/src/Databases/Base/BaseQuery.php +5 -5
  77. src/lib/src/Databases/Base/Handler.php +0 -58
  78. src/lib/src/Databases/Base/Insert.php +5 -10
  79. src/lib/src/Databases/IPs/CommonFilters.php +12 -6
  80. src/lib/src/Databases/IPs/Handler.php +2 -1
  81. src/lib/src/Databases/Reports/Common.php +6 -14
  82. src/lib/src/Databases/Reports/Handler.php +0 -1
  83. src/lib/src/Modules/AuditTrail/AjaxHandler.php +13 -19
  84. src/lib/src/Modules/AuditTrail/Auditors/Upgrades.php +88 -0
  85. src/lib/src/Modules/AuditTrail/Auditors/Users.php +27 -28
  86. src/lib/src/Modules/AuditTrail/Insights/OverviewCards.php +9 -36
  87. src/lib/src/Modules/AuditTrail/Lib/AuditWriter.php +5 -5
  88. src/lib/src/Modules/AuditTrail/ModCon.php +105 -0
  89. src/lib/src/Modules/AuditTrail/Options.php +20 -79
  90. src/lib/src/Modules/AuditTrail/Processor.php +65 -0
  91. src/lib/src/Modules/AuditTrail/Strings.php +6 -0
  92. src/lib/src/Modules/AuditTrail/UI.php +41 -30
  93. src/lib/src/Modules/AuditTrail/WpCli.php +1 -1
  94. src/lib/src/Modules/AuditTrail/WpCli/Display.php +5 -4
  95. src/lib/src/Modules/Autoupdates/AjaxHandler.php +2 -2
  96. src/lib/src/Modules/Autoupdates/ModCon.php +9 -0
  97. src/lib/src/Modules/Autoupdates/Options.php +2 -2
  98. src/lib/src/Modules/Autoupdates/Processor.php +453 -0
  99. src/lib/src/Modules/Autoupdates/UI.php +2 -3
  100. src/lib/src/Modules/Base/AdminNotices.php +39 -45
  101. src/lib/src/Modules/Base/AjaxHandler.php +95 -0
  102. src/lib/src/Modules/Base/AjaxHandlerBase.php +6 -1
  103. src/lib/src/Modules/Base/AjaxHandlerShield.php +5 -0
  104. src/lib/src/Modules/Base/BaseProcessor.php +12 -7
  105. src/lib/src/Modules/Base/BaseReporting.php +5 -0
  106. src/lib/src/Modules/Base/{BaseModCon.php → ModCon.php} +505 -590
  107. src/lib/src/Modules/Base/OneTimeExecute.php +0 -37
  108. src/lib/src/Modules/Base/Options.php +32 -51
  109. src/lib/src/Modules/Base/Processor.php +60 -0
  110. src/lib/src/Modules/Base/Reporting.php +49 -0
  111. src/lib/src/Modules/Base/ShieldOptions.php +5 -0
  112. src/lib/src/Modules/Base/ShieldUI.php +15 -6
  113. src/lib/src/Modules/Base/Strings.php +30 -4
  114. src/lib/src/Modules/Base/UI.php +38 -16
  115. src/lib/src/Modules/Base/Upgrade.php +6 -7
  116. src/lib/src/Modules/Base/WpCli.php +3 -3
  117. src/lib/src/Modules/Base/WpCli/BaseWpCliCmd.php +1 -1
  118. src/lib/src/Modules/BaseShield/AjaxHandler.php +71 -0
  119. src/lib/src/Modules/BaseShield/ModCon.php +256 -0
  120. src/lib/src/Modules/BaseShield/Options.php +30 -0
  121. src/lib/src/Modules/BaseShield/{ShieldModCon.php → Processor.php} +2 -2
  122. src/lib/src/Modules/BaseShield/ShieldProcessor.php +5 -35
  123. src/lib/src/Modules/BaseShield/UI.php +66 -0
  124. src/lib/src/Modules/CommentsFilter/AdminNotices.php +17 -20
  125. src/lib/src/Modules/CommentsFilter/AjaxHandler.php +1 -1
  126. src/lib/src/Modules/CommentsFilter/Forms/Gasp.php +123 -0
  127. src/lib/src/Modules/CommentsFilter/Forms/GoogleRecaptcha.php +44 -0
  128. src/lib/src/Modules/CommentsFilter/Insights/OverviewCards.php +3 -3
  129. src/lib/src/Modules/CommentsFilter/ModCon.php +95 -0
  130. src/lib/src/Modules/CommentsFilter/Options.php +9 -30
  131. src/lib/src/Modules/CommentsFilter/Processor.php +63 -0
  132. src/lib/src/Modules/CommentsFilter/Scan/Human.php +12 -12
  133. src/lib/src/Modules/CommentsFilter/Scan/Scanner.php +11 -5
  134. src/lib/src/Modules/CommentsFilter/Strings.php +5 -5
  135. src/lib/src/Modules/CommentsFilter/UI.php +3 -2
  136. src/lib/src/Modules/Comms/ModCon.php +13 -0
  137. src/lib/src/Modules/Comms/Options.php +2 -2
  138. src/lib/src/Modules/Comms/Processor.php +9 -0
  139. src/lib/src/Modules/Email/ModCon.php +9 -0
  140. src/lib/src/Modules/Email/Options.php +3 -3
  141. src/lib/src/Modules/Email/Processor.php +205 -0
  142. src/lib/src/Modules/Email/Strings.php +1 -1
  143. src/lib/src/Modules/Events/AjaxHandler.php +8 -8
  144. src/lib/src/Modules/Events/Charts/BuildData.php +11 -10
  145. src/lib/src/Modules/Events/Consolidate/ConsolidateAllEvents.php +36 -35
  146. src/lib/src/Modules/Events/Lib/EventsListener.php +5 -5
  147. src/lib/src/Modules/Events/Lib/EventsService.php +31 -26
  148. src/lib/src/Modules/Events/Lib/Reports/KeyStats.php +4 -4
  149. src/lib/src/Modules/Events/Lib/Reports/ScanRepairs.php +4 -4
  150. src/lib/src/Modules/Events/Lib/StatsWriter.php +3 -3
  151. src/lib/src/Modules/Events/ModCon.php +23 -0
  152. src/lib/src/Modules/Events/Options.php +3 -10
  153. src/lib/src/Modules/Events/Processor.php +92 -0
  154. src/lib/src/Modules/Events/Reporting.php +2 -2
  155. src/lib/src/Modules/Events/Strings.php +7 -5
  156. src/lib/src/Modules/Firewall/Insights/OverviewCards.php +2 -1
  157. src/lib/src/Modules/Firewall/ModCon.php +53 -0
  158. src/lib/src/Modules/Firewall/Options.php +2 -2
  159. src/lib/src/Modules/Firewall/Processor.php +435 -0
  160. src/lib/src/Modules/Firewall/Strings.php +1 -1
  161. src/lib/src/Modules/Firewall/UI.php +2 -2
  162. src/lib/src/Modules/HackGuard/AjaxHandler.php +85 -97
  163. src/lib/src/Modules/HackGuard/Insights/OverviewCards.php +7 -7
  164. src/lib/src/Modules/HackGuard/Lib/FileLocker/FileLockerController.php +15 -25
  165. src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/Accept.php +5 -4
  166. src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/AssessLocks.php +9 -8
  167. src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/CreateFileLocks.php +11 -10
  168. src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/DeleteFileLock.php +4 -3
  169. src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/LoadFileLocks.php +5 -5
  170. src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/PerformAction.php +4 -3
  171. src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/Restore.php +4 -3
  172. src/lib/src/Modules/HackGuard/Lib/Reports/FileLockerAlerts.php +6 -6
  173. src/lib/src/Modules/HackGuard/Lib/Reports/Query/ScanCounts.php +88 -0
  174. src/lib/src/Modules/HackGuard/Lib/Reports/ScanAlerts.php +21 -47
  175. src/lib/src/Modules/HackGuard/Lib/Snapshots/StoreAction/BaseAction.php +4 -3
  176. src/lib/src/Modules/HackGuard/Lib/Snapshots/StoreAction/Build.php +2 -3
  177. src/lib/src/Modules/HackGuard/Lib/Snapshots/StoreAction/CleanAll.php +4 -3
  178. src/lib/src/Modules/HackGuard/Lib/Snapshots/StoreAction/DeleteAll.php +4 -3
  179. src/lib/src/Modules/HackGuard/ModCon.php +253 -0
  180. src/lib/src/Modules/HackGuard/Options.php +33 -94
  181. src/lib/src/Modules/HackGuard/Processor.php +45 -0
  182. src/lib/src/Modules/HackGuard/Reporting.php +2 -2
  183. src/lib/src/Modules/HackGuard/Scan/Controller/Base.php +103 -96
  184. src/lib/src/Modules/HackGuard/Scan/Controller/BaseForAssets.php +5 -5
  185. src/lib/src/Modules/HackGuard/Scan/Controller/Mal.php +3 -3
  186. src/lib/src/Modules/HackGuard/Scan/Controller/Ptg.php +25 -28
  187. src/lib/src/Modules/HackGuard/Scan/Controller/Ufc.php +3 -3
  188. src/lib/src/Modules/HackGuard/Scan/Controller/Wcf.php +4 -4
  189. src/lib/src/Modules/HackGuard/Scan/Controller/Wpv.php +46 -0
  190. src/lib/src/Modules/HackGuard/Scan/Queue/BuildScanAction.php +7 -6
  191. src/lib/src/Modules/HackGuard/Scan/Queue/CompleteQueue.php +2 -1
  192. src/lib/src/Modules/HackGuard/Scan/Queue/Controller.php +22 -23
  193. src/lib/src/Modules/HackGuard/Scan/Queue/QueueProcessor.php +4 -3
  194. src/lib/src/Modules/HackGuard/Scan/Queue/ScanExecute.php +4 -4
  195. src/lib/src/Modules/HackGuard/Scan/Queue/ScanInitiate.php +10 -9
  196. src/lib/src/Modules/HackGuard/Scan/ScanActionFromSlug.php +20 -27
  197. src/lib/src/Modules/HackGuard/Scan/ScansController.php +185 -0
  198. src/lib/src/Modules/HackGuard/Scan/Utilities/PtgAddReinstallLinks.php +112 -0
  199. src/lib/src/Modules/HackGuard/Scan/Utilities/WpvAddPluginRows.php +151 -0
  200. src/lib/src/Modules/HackGuard/Strings.php +94 -99
  201. src/lib/src/Modules/HackGuard/UI.php +58 -149
  202. src/lib/src/Modules/Headers/Insights/OverviewCards.php +1 -1
  203. src/lib/src/Modules/Headers/ModCon.php +90 -0
  204. src/lib/src/Modules/Headers/Options.php +5 -7
  205. src/lib/src/Modules/Headers/Processor.php +199 -0
  206. src/lib/src/Modules/Headers/UI.php +2 -2
  207. src/lib/src/Modules/IPs/AdminNotices.php +15 -22
  208. src/lib/src/Modules/IPs/AjaxHandler.php +118 -27
  209. src/lib/src/Modules/IPs/Components/ImportIpsFromFile.php +12 -15
  210. src/lib/src/Modules/IPs/Components/ProcessOffense.php +3 -3
  211. src/lib/src/Modules/IPs/Components/QueryIpBlock.php +8 -8
  212. src/lib/src/Modules/IPs/Components/QueryRemainingOffenses.php +8 -9
  213. src/lib/src/Modules/IPs/Components/UnblockIpByFlag.php +8 -8
  214. src/lib/src/Modules/IPs/Lib/AutoUnblock.php +4 -3
  215. src/lib/src/Modules/IPs/Lib/BlacklistHandler.php +2 -2
  216. src/lib/src/Modules/IPs/Lib/BlockRequest.php +12 -5
  217. src/lib/src/Modules/IPs/Lib/IpAnalyse/BuildDisplay.php +31 -4
  218. src/lib/src/Modules/IPs/Lib/OffenseTracker.php +3 -3
  219. src/lib/src/Modules/IPs/Lib/Ops/AddIp.php +29 -28
  220. src/lib/src/Modules/IPs/Lib/Ops/DeleteIp.php +9 -18
  221. src/lib/src/Modules/IPs/Lib/ProcessOffenses.php +2 -2
  222. src/lib/src/Modules/IPs/ModCon.php +121 -0
  223. src/lib/src/Modules/IPs/Options.php +2 -10
  224. src/lib/src/Modules/IPs/Processor.php +14 -0
  225. src/lib/src/Modules/IPs/Strings.php +4 -4
  226. src/lib/src/Modules/IPs/UI.php +21 -4
  227. src/lib/src/Modules/IPs/Upgrade.php +15 -6
  228. src/lib/src/Modules/IPs/WpCli.php +1 -1
  229. src/lib/src/Modules/IPs/WpCli/Add.php +3 -3
  230. src/lib/src/Modules/IPs/WpCli/Enumerate.php +4 -3
  231. src/lib/src/Modules/IPs/WpCli/Remove.php +5 -5
  232. src/lib/src/Modules/Insights/ModCon.php +230 -0
  233. src/lib/src/Modules/Insights/Options.php +3 -3
  234. src/lib/src/Modules/Insights/UI.php +300 -34
  235. src/lib/src/Modules/Integrations/Lib/MainWP/Client/Actions/ApiActionInit.php +50 -0
  236. src/lib/src/Modules/Integrations/Lib/MainWP/Client/Actions/Init.php +61 -0
  237. src/lib/src/Modules/Integrations/Lib/MainWP/Client/Actions/Sync.php +51 -0
  238. src/lib/src/Modules/Integrations/Lib/MainWP/Client/Auth/ReproduceClientAuthByKey.php +32 -0
  239. src/lib/src/Modules/Integrations/Lib/MainWP/Common/Consumers/MWPSiteConsumer.php +26 -0
  240. src/lib/src/Modules/Integrations/Lib/MainWP/Common/MWPExtensionVO.php +48 -0
  241. src/lib/src/Modules/Integrations/Lib/MainWP/Common/MWPSiteVO.php +59 -0
  242. src/lib/src/Modules/Integrations/Lib/MainWP/Common/MainWPVO.php +56 -0
  243. src/lib/src/Modules/Integrations/Lib/MainWP/Common/SyncMetaVO.php +20 -0
  244. src/lib/src/Modules/Integrations/Lib/MainWP/Common/SyncVO.php +37 -0
  245. src/lib/src/Modules/Integrations/Lib/MainWP/Controller.php +88 -0
  246. src/lib/src/Modules/Integrations/Lib/MainWP/Server/Actions/Action.php +42 -0
  247. src/lib/src/Modules/Integrations/Lib/MainWP/Server/Actions/ShieldApiAction.php +29 -0
  248. src/lib/src/Modules/Integrations/Lib/MainWP/Server/Actions/ShieldPluginAction.php +128 -0
  249. src/lib/src/Modules/Integrations/Lib/MainWP/Server/Ajax/AjaxHandlerMainwp.php +65 -0
  250. src/lib/src/Modules/Integrations/Lib/MainWP/Server/Ajax/PerformSiteAction.php +119 -0
  251. src/lib/src/Modules/Integrations/Lib/MainWP/Server/Data/ClientPluginStatus.php +105 -0
  252. src/lib/src/Modules/Integrations/Lib/MainWP/Server/Data/LoadShieldSyncData.php +23 -0
  253. src/lib/src/Modules/Integrations/Lib/MainWP/Server/Data/SyncHandler.php +36 -0
  254. src/lib/src/Modules/Integrations/Lib/MainWP/Server/Init.php +65 -0
  255. src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/BaseRender.php +37 -0
  256. src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/ExtensionSettingsPage.php +131 -0
  257. src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/ManageSites/SitesListTableHandler.php +95 -0
  258. src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/MwpOutOfDate.php +30 -0
  259. src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/NotShieldPro.php +24 -0
  260. src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/PluginOutOfDate.php +25 -0
  261. src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/SitesList.php +138 -0
  262. src/lib/src/Modules/Integrations/ModCon.php +22 -0
  263. src/lib/src/Modules/Integrations/Options.php +12 -0
  264. src/lib/src/Modules/Integrations/Processor.php +15 -0
  265. src/lib/src/Modules/Integrations/Strings.php +70 -0
  266. src/lib/src/Modules/License/AdminNotices.php +16 -23
  267. src/lib/src/Modules/License/AjaxHandler.php +4 -4
  268. src/lib/src/Modules/License/Lib/LicenseEmails.php +27 -26
  269. src/lib/src/Modules/License/Lib/LicenseHandler.php +11 -10
  270. src/lib/src/Modules/License/Lib/Verify.php +14 -14
  271. src/lib/src/Modules/License/ModCon.php +66 -0
  272. src/lib/src/Modules/License/Options.php +2 -2
  273. src/lib/src/Modules/License/Processor.php +14 -0
  274. src/lib/src/Modules/License/UI.php +32 -13
  275. src/lib/src/Modules/License/WpCli.php +1 -1
  276. src/lib/src/Modules/License/WpCli/License.php +10 -9
  277. src/lib/src/Modules/Lockdown/Insights/OverviewCards.php +4 -4
  278. src/lib/src/Modules/Lockdown/ModCon.php +31 -0
  279. src/lib/src/Modules/Lockdown/Options.php +2 -2
  280. src/lib/src/Modules/Lockdown/Processor.php +134 -0
  281. src/lib/src/Modules/Lockdown/UI.php +2 -2
  282. src/lib/src/Modules/LoginGuard/AdminNotices.php +20 -29
  283. src/lib/src/Modules/LoginGuard/AjaxHandler.php +35 -47
  284. src/lib/src/Modules/LoginGuard/Insights/OverviewCards.php +3 -3
  285. src/lib/src/Modules/LoginGuard/Lib/AntiBot/AntibotSetup.php +4 -4
  286. src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WooCommerce.php +10 -10
  287. src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/GaspJs.php +10 -10
  288. src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/GoogleRecaptcha.php +4 -3
  289. src/lib/src/Modules/LoginGuard/Lib/CooldownRedirect.php +5 -4
  290. src/lib/src/Modules/LoginGuard/Lib/Rename/RenameLogin.php +244 -0
  291. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/LoginIntentPage.php +19 -19
  292. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/MfaController.php +95 -140
  293. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Profiles/CustomForms.php +2 -2
  294. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/Email.php +1 -2
  295. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/ValidateLoginIntentRequest.php +22 -22
  296. src/lib/src/Modules/LoginGuard/ModCon.php +280 -0
  297. src/lib/src/Modules/LoginGuard/Options.php +40 -108
  298. src/lib/src/Modules/LoginGuard/Processor.php +28 -0
  299. src/lib/src/Modules/LoginGuard/Strings.php +93 -97
  300. src/lib/src/Modules/LoginGuard/UI.php +2 -2
  301. src/lib/src/Modules/ModConsumer.php +2 -2
  302. src/lib/src/Modules/Plugin/AdminNotices.php +61 -124
  303. src/lib/src/Modules/Plugin/AjaxHandler.php +78 -114
  304. src/lib/src/Modules/Plugin/Components/BadgeWidget.php +6 -4
  305. src/lib/src/Modules/Plugin/Components/PluginBadge.php +58 -50
  306. src/lib/src/Modules/Plugin/Components/TestForCloudflareAPO.php +1 -1
  307. src/lib/src/Modules/Plugin/Debug.php +10 -1
  308. src/lib/src/Modules/Plugin/Insights/AdminNotes.php +3 -6
  309. src/lib/src/Modules/Plugin/Insights/DashboardCards.php +410 -0
  310. src/lib/src/Modules/Plugin/Insights/OverviewCards.php +3 -2
  311. src/lib/src/Modules/Plugin/Lib/Captcha/CheckCaptchaSettings.php +14 -14
  312. src/lib/src/Modules/Plugin/Lib/ImportExport/Export.php +9 -9
  313. src/lib/src/Modules/Plugin/Lib/ImportExport/Import.php +10 -10
  314. src/lib/src/Modules/Plugin/Lib/ImportExport/ImportExportController.php +14 -11
  315. src/lib/src/Modules/Plugin/Lib/ImportExport/NotifyWhitelist.php +5 -4
  316. src/lib/src/Modules/Plugin/Lib/PluginTelemetry.php +166 -0
  317. src/lib/src/Modules/Plugin/ModCon.php +549 -0
  318. src/lib/src/Modules/Plugin/Options.php +4 -34
  319. src/lib/src/Modules/Plugin/Processor.php +90 -0
  320. src/lib/src/Modules/Plugin/Strings.php +23 -9
  321. src/lib/src/Modules/Plugin/UI.php +26 -10
  322. src/lib/src/Modules/Plugin/Upgrade.php +4 -4
  323. src/lib/src/Modules/Plugin/WpCli.php +1 -1
  324. src/lib/src/Modules/Reporting/Debug.php +3 -0
  325. src/lib/src/Modules/Reporting/Lib/ReportingController.php +4 -2
  326. src/lib/src/Modules/Reporting/Lib/Reports/BaseReporter.php +3 -3
  327. src/lib/src/Modules/Reporting/Lib/Reports/Build/BaseBuilder.php +7 -7
  328. src/lib/src/Modules/Reporting/Lib/Reports/Build/BuilderAlerts.php +10 -11
  329. src/lib/src/Modules/Reporting/Lib/Reports/Build/BuilderInfo.php +10 -11
  330. src/lib/src/Modules/Reporting/Lib/Reports/CreateReportVO.php +41 -41
  331. src/lib/src/Modules/Reporting/ModCon.php +25 -0
  332. src/lib/src/Modules/Reporting/Options.php +5 -13
  333. src/lib/src/Modules/Reporting/Processor.php +14 -0
  334. src/lib/src/Modules/Reporting/UI.php +2 -2
  335. src/lib/src/Modules/SecurityAdmin/AdminNotices.php +17 -27
  336. src/lib/src/Modules/SecurityAdmin/AjaxHandler.php +15 -20
  337. src/lib/src/Modules/SecurityAdmin/Insights/OverviewCards.php +1 -1
  338. src/lib/src/Modules/SecurityAdmin/Lib/Actions/RemoveSecAdmin.php +37 -46
  339. src/lib/src/Modules/SecurityAdmin/Lib/Actions/SetSecAdminPin.php +4 -4
  340. src/lib/src/Modules/SecurityAdmin/Lib/WhiteLabel/ApplyLabels.php +1 -1
  341. src/lib/src/Modules/SecurityAdmin/ModCon.php +384 -0
  342. src/lib/src/Modules/SecurityAdmin/Options.php +3 -3
  343. src/lib/src/Modules/SecurityAdmin/Processor.php +111 -0
.htaccess CHANGED
@@ -1,4 +1,4 @@
1
  Order Allow,Deny
2
- <FilesMatch "^.*\.(css|js|png)$">
3
  Allow from all
4
  </FilesMatch>
1
  Order Allow,Deny
2
+ <FilesMatch "^.*\.(css|js|png|jpg|svg)$">
3
  Allow from all
4
  </FilesMatch>
cl.json CHANGED
@@ -1,4 +1,75 @@
1
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  "10.0": {
3
  "version": "10.0",
4
  "released_at": 1603281600,
1
  {
2
+ "10.1": {
3
+ "version": "10.1",
4
+ "released_at": 1605606920,
5
+ "hrefs": {
6
+ "release": "https://shsec.io/shieldrelease101",
7
+ "upgrade": "https://shsec.io/shieldupgradeguide101"
8
+ },
9
+ "href": "https://shsec.io/",
10
+ "title": "Enhanced Dashboard + MainWP Integration",
11
+ "description": [
12
+ "We're continuing our improvements to the Shield UI with a brand new Dashboard.",
13
+ "The Dashboard is your primary launchpad for all things WordPress Security and Shield.",
14
+ "We're also delighted to bring our first major 3rd party integration - MainWP."
15
+ ],
16
+ "items": [
17
+ {
18
+ "type": "new",
19
+ "pro_only": false,
20
+ "title": "Brand New Shield Dashboard",
21
+ "description": [
22
+ "With the help of some feedback from clients, we've made significant enhancements to the Shield UI.",
23
+ "A brand-new Shield dashboard centralises everything related to Shield giving you a consistent, clean launchpad to perform security tasks."
24
+ ]
25
+ },
26
+ {
27
+ "type": "new",
28
+ "pro_only": true,
29
+ "title": "MainWP Integration/Extension",
30
+ "description": [
31
+ "You can now manage your Shield Security plugin directly from within your MainWP WordPress management control panel.",
32
+ "The Shield Security Extension page will highlight all sites with any scan issues that need your attention.",
33
+ "For now, the functionality is limited to installing, activating and deactivating the Shield plugin."
34
+ ],
35
+ "href": "https://shsec.io/ir"
36
+ },
37
+ {
38
+ "type": "new",
39
+ "pro_only": false,
40
+ "title": "IP Analyse Tool Enhancements",
41
+ "description": [
42
+ "Based on customer feedback we've added links to the IP Analyse tool to let you quickly perform blocks or bypass on an IP.",
43
+ "The identification of a 'known' IP address now also draws information from the IP Bypass labels."
44
+ ]
45
+ },
46
+ {
47
+ "type": "improved",
48
+ "pro_only": false,
49
+ "title": "Enhanced Plugin Badge",
50
+ "description": [
51
+ "Based on customer feedback we've added the ability to customize the plugin badge based on Whitelabel settings.",
52
+ "You'll may also use a WordPress filter to make fine adjustments to settings and styles of the badge."
53
+ ],
54
+ "href": "https://shsec.io/is"
55
+ },
56
+ {
57
+ "type": "improved",
58
+ "pro_only": false,
59
+ "title": "Huge Codebase Refactor",
60
+ "description": [
61
+ "With our earlier move to PHP 7.0, we're continuing with our codebase cleanup and optimisations."
62
+ ]
63
+ },
64
+ {
65
+ "type": "improved",
66
+ "title": "Shield Overview Styles",
67
+ "description": [
68
+ "With some feedback and suggestions provided by clients, we've improved our Shield Overview design."
69
+ ]
70
+ }
71
+ ]
72
+ },
73
  "10.0": {
74
  "version": "10.0",
75
  "released_at": 1603281600,
filesnotfound.php DELETED
@@ -1,24 +0,0 @@
1
- <?php
2
-
3
- foreach (
4
- [ 'ICWP_WPSF_FeatureHandler_Base', 'ICWP_WPSF_FeatureHandler_BaseWpsf', ] as $sClass
5
- ) {
6
- if ( !@class_exists( $sClass ) ) {
7
- add_action( 'admin_notices', 'icwp_wpsf_checkfilesnotfound' );
8
- add_action( 'network_admin_notices', 'icwp_wpsf_checkfilesnotfound' );
9
- return false;
10
- }
11
- }
12
-
13
- function icwp_wpsf_checkfilesnotfound() {
14
- echo sprintf( '<div class="error"><h4>%s</h4><p>%s</p></div>',
15
- 'Shield Security Plugin - Broken Installation',
16
- implode( '<br/>', [
17
- 'It appears the Shield Security plugin was not upgraded/installed correctly.',
18
- "We run a quick check to make sure certain important files are present in-case a faulty installation breaks your site.",
19
- 'Try refreshing this page, and if you continue to see this notice, we recommend that you reinstall the Shield Security plugin.'
20
- ] )
21
- );
22
- }
23
-
24
- return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.0.3
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: 10.1.0
7
  * Text Domain: wp-simple-firewall
8
  * Domain Path: /languages
9
  * Author: Shield Security
plugin-spec.php CHANGED
@@ -1,8 +1,8 @@
1
  {
2
  "properties": {
3
- "version": "10.0.3",
4
- "release_timestamp": 1603551407,
5
- "build": "202010.2401",
6
  "slug_parent": "icwp",
7
  "slug_plugin": "wpsf",
8
  "human_name": "Shield",
@@ -114,7 +114,8 @@
114
  "9.0.5",
115
  "9.1.1",
116
  "9.2.0",
117
- "9.2.2"
 
118
  ],
119
  "action_links": {
120
  "remove": null,
1
  {
2
  "properties": {
3
+ "version": "10.1.0",
4
+ "release_timestamp": 1605606920,
5
+ "build": "202011.1701",
6
  "slug_parent": "icwp",
7
  "slug_plugin": "wpsf",
8
  "human_name": "Shield",
114
  "9.0.5",
115
  "9.1.1",
116
  "9.2.0",
117
+ "9.2.2",
118
+ "10.1.0"
119
  ],
120
  "action_links": {
121
  "remove": null,
readme.txt CHANGED
@@ -8,7 +8,7 @@ Requires at least: 3.5.2
8
  Requires PHP: 7.0
9
  Recommended PHP: 7.4
10
  Tested up to: 5.5
11
- Stable tag: 10.0.3
12
 
13
  The highest rated WordPress Security plugin, delivering unparalleled, all-in-one protection for you and your customers.
14
 
8
  Requires PHP: 7.0
9
  Recommended PHP: 7.4
10
  Tested up to: 5.5
11
+ Stable tag: 10.1.0
12
 
13
  The highest rated WordPress Security plugin, delivering unparalleled, all-in-one protection for you and your customers.
14
 
resources/css/bootstrap-select.min.css CHANGED
@@ -1,6 +1,6 @@
1
  /*!
2
- * Bootstrap-select v1.13.14 (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: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}
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/mainwp.css ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 
2
+ #mainwp-shield-extension-table-sites_wrapper {
3
+ padding: 0 1rem;
4
+ }
5
+
6
+ table.mainwp-shield-ext-table {
7
+ }
8
+ table.mainwp-shield-ext-table td.cell-span-issue {
9
+ background-color: rgba(0, 0, 0, 0.1);
10
+ font-size: smaller;
11
+ text-align: center;
12
+ }
resources/css/plugin.css CHANGED
@@ -364,11 +364,23 @@ label input[type=checkbox] {
364
  .module-icon-support:before {
365
  content: "\f525";
366
  }
367
- #NavItem-SearchOptionsLaunch > a {
368
- width: 35px;
 
 
 
369
  content: "\f179";
370
  font-size: 24px;
371
  color: #008000 !important;
 
 
 
 
 
 
 
 
 
372
  }
373
  .dropdown-item .module-icon:before {
374
  font-size: 1.6rem;
@@ -659,7 +671,6 @@ input:checked + .icwp-slider:before {
659
  }
660
  .carousel-item {
661
  transition: -webkit-transform 0.3s ease;
662
- transition: transform 0.2s ease;
663
  transition: transform 0.2s ease, -webkit-transform 0.2s ease;
664
  }
665
  #AuditTrailTabs .tab-content {
@@ -699,13 +710,6 @@ input:checked + .icwp-slider:before {
699
  left: -17px;
700
  }
701
  }
702
- #FooterWizardBanner {
703
- position: fixed;
704
- bottom: 0;
705
- height: 66px;
706
- width: 100%;
707
- z-index: 5;
708
- }
709
  #FooterWizardBanner .text-left {
710
  margin-bottom: 0;
711
  }
@@ -717,21 +721,6 @@ input:checked + .icwp-slider:before {
717
  background-color: #fafafa;
718
  background: linear-gradient(to top, rgba(250, 250, 250, 0.95), rgba(250, 250, 250, 0.7));
719
  }
720
- #WizardBanner {
721
- height: 66px;
722
- background-color: #eaffea;
723
- border-top: 1px solid #bbbbbb;
724
- padding-top: 15px;
725
- text-align: center;
726
- word-break: keep-all;
727
- white-space: nowrap;
728
- box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.2);
729
- }
730
- #WizardBanner p {
731
- margin: 0;
732
- line-height: 20px;
733
- text-align: left;
734
- }
735
  .icon-flag {
736
  vertical-align: text-bottom;
737
  }
@@ -800,8 +789,6 @@ th.column-request_info {
800
  }
801
  #odp-PageHead .nav-link.active {
802
  font-weight: bold;
803
- border: 1px solid rgba(0, 0, 0, 0.1);
804
- border-radius: 3px;
805
  color: #008000;
806
  }
807
  .nav-link.module.notenabled span.module-name {
@@ -824,7 +811,7 @@ th.column-request_info {
824
  .odp-outercontainer.icwp-wpsf-insights.settings #odp-PageMain {
825
  }
826
  .insights_section {
827
- margin-top: 20px;
828
  }
829
  .insights_widget .card-body {
830
  padding: 0 0 0 0;
@@ -957,7 +944,7 @@ table.scan-table.wp-list-table button.toggle-row {
957
  }
958
  .insights-sub-nav ul.nav.nav-tabs a.nav-link {
959
  color: #666666;
960
- font-size: 1.4rem;
961
  margin-right: 1rem;
962
  background-color: rgba(0, 0, 0, 0.05);
963
  border-color: rgba(0, 0, 0, 0.1);
@@ -1195,9 +1182,6 @@ a:hover {
1195
  a:focus .gravatar, a:focus, a:focus .media-icon img {
1196
  box-shadow: none !important;
1197
  }
1198
- .wp-core-ui select {
1199
- min-height: 38px;
1200
- }
1201
  .col-form-label {
1202
  font-weight: bold;
1203
  padding-top: 0;
@@ -1298,6 +1282,11 @@ dl.pro-features dd {
1298
  background-color: rgba(69, 119, 0, 0.3);
1299
  border-color: rgba(69, 119, 0, 0.3);
1300
  }
 
 
 
 
 
1301
  .importexport-checkbox.custom-switch .custom-control-input:checked ~ .custom-control-label::before {
1302
  background-color: rgba(119, 107, 12, 1);
1303
  border-color: rgba(119, 107, 12, 1);
@@ -1312,7 +1301,15 @@ dl.pro-features dd {
1312
  #FileLockerDiffContents .card-body {
1313
  padding: 1.25rem;
1314
  }
1315
- .overview.card {
 
 
 
 
 
 
 
 
1316
  }
1317
  .overview.card > .card-footer {
1318
  padding: 10px 15px;
@@ -1324,25 +1321,57 @@ dl.pro-features dd {
1324
  .overview.card > .card-footer > a .dashicons {
1325
  font-size: 24px;
1326
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1327
  .overview.card.state-danger,
1328
  .overview.card.state-danger > .card-header,
1329
  .overview.card.state-danger > .card-footer {
1330
- border-color: #cf0000;
1331
  }
1332
  .overview.card.state-warning,
1333
  .overview.card.state-warning > .card-header,
1334
  .overview.card.state-warning > .card-footer {
1335
- border-color: #d26605;
1336
  }
1337
  .overview.card.state-ok,
1338
  .overview.card.state-ok > .card-header,
1339
  .overview.card.state-ok > .card-footer {
1340
- border-color: #0579d2;
1341
  }
1342
  .overview.card.state-good,
1343
  .overview.card.state-good > .card-header,
1344
  .overview.card.state-good > .card-footer {
1345
- border-color: #008000;
1346
  }
1347
  .overview.card.state-good > .card-header {
1348
  background-color: rgba(98, 214, 83, 0.2);
@@ -1438,4 +1467,101 @@ a.card_help {
1438
  }
1439
  #DynamicChangelog dd p {
1440
  margin-bottom: 5px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1441
  }
364
  .module-icon-support:before {
365
  content: "\f525";
366
  }
367
+ #SearchOptionsLaunch > a {
368
+ text-decoration: none !important;
369
+ }
370
+ #SearchOptionsLaunch .dashicons {
371
+ width: 24px;
372
  content: "\f179";
373
  font-size: 24px;
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
  }
385
  .dropdown-item .module-icon:before {
386
  font-size: 1.6rem;
671
  }
672
  .carousel-item {
673
  transition: -webkit-transform 0.3s ease;
 
674
  transition: transform 0.2s ease, -webkit-transform 0.2s ease;
675
  }
676
  #AuditTrailTabs .tab-content {
710
  left: -17px;
711
  }
712
  }
 
 
 
 
 
 
 
713
  #FooterWizardBanner .text-left {
714
  margin-bottom: 0;
715
  }
721
  background-color: #fafafa;
722
  background: linear-gradient(to top, rgba(250, 250, 250, 0.95), rgba(250, 250, 250, 0.7));
723
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
724
  .icon-flag {
725
  vertical-align: text-bottom;
726
  }
789
  }
790
  #odp-PageHead .nav-link.active {
791
  font-weight: bold;
 
 
792
  color: #008000;
793
  }
794
  .nav-link.module.notenabled span.module-name {
811
  .odp-outercontainer.icwp-wpsf-insights.settings #odp-PageMain {
812
  }
813
  .insights_section {
814
+ /*margin-top: 20px;*/
815
  }
816
  .insights_widget .card-body {
817
  padding: 0 0 0 0;
944
  }
945
  .insights-sub-nav ul.nav.nav-tabs a.nav-link {
946
  color: #666666;
947
+ font-size: 0.9rem;
948
  margin-right: 1rem;
949
  background-color: rgba(0, 0, 0, 0.05);
950
  border-color: rgba(0, 0, 0, 0.1);
1182
  a:focus .gravatar, a:focus, a:focus .media-icon img {
1183
  box-shadow: none !important;
1184
  }
 
 
 
1185
  .col-form-label {
1186
  font-weight: bold;
1187
  padding-top: 0;
1282
  background-color: rgba(69, 119, 0, 0.3);
1283
  border-color: rgba(69, 119, 0, 0.3);
1284
  }
1285
+ .custom-control.custom-checkbox .custom-control-input:disabled,
1286
+ .custom-control.custom-radio .custom-control-input:disabled,
1287
+ .option-checkbox.custom-switch .custom-control-input:disabled {
1288
+ opacity: 0;
1289
+ }
1290
  .importexport-checkbox.custom-switch .custom-control-input:checked ~ .custom-control-label::before {
1291
  background-color: rgba(119, 107, 12, 1);
1292
  border-color: rgba(119, 107, 12, 1);
1301
  #FileLockerDiffContents .card-body {
1302
  padding: 1.25rem;
1303
  }
1304
+ #odp-PageContainer .overview.card {
1305
+ box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.1);
1306
+ }
1307
+ .overview.card,
1308
+ .overview.card .card-header,
1309
+ .overview.card .card-body {
1310
+ border: 2px solid white !important;
1311
+ }
1312
+ .overview.card > .card-header {
1313
  }
1314
  .overview.card > .card-footer {
1315
  padding: 10px 15px;
1321
  .overview.card > .card-footer > a .dashicons {
1322
  font-size: 24px;
1323
  }
1324
+ .overview.card.state-good .title-icon:before,
1325
+ .overview.card.state-good .card-title,
1326
+ .overview.card.state-good .card-header .card-link .dashicons {
1327
+ color: #015b01;
1328
+ }
1329
+ .overview.card.state-ok .title-icon:before,
1330
+ .overview.card.state-ok .card-title,
1331
+ .overview.card.state-ok .card-header .card-link .dashicons {
1332
+ color: #345c83;
1333
+ }
1334
+ .overview.card.state-warning .title-icon:before,
1335
+ .overview.card.state-warning .card-title,
1336
+ .overview.card.state-warning .card-header .card-link .dashicons {
1337
+ color: #ae5403;
1338
+ }
1339
+ .overview.card.state-danger .title-icon:before,
1340
+ .overview.card.state-danger .card-title,
1341
+ .overview.card.state-danger .card-header .card-link .dashicons {
1342
+ color: #811313;
1343
+ }
1344
+ .overview.card.state-danger .title-icon:before {
1345
+ content: "\f153";
1346
+ }
1347
+ .overview.card.state-warning .title-icon:before {
1348
+ content: "\f534";
1349
+ }
1350
+ .overview.card.state-ok .title-icon:before {
1351
+ content: "\f348";
1352
+ }
1353
+ .overview.card.state-good .title-icon:before {
1354
+ content: "\f12a";
1355
+ }
1356
  .overview.card.state-danger,
1357
  .overview.card.state-danger > .card-header,
1358
  .overview.card.state-danger > .card-footer {
1359
+ /*border-color: #cf0000;*/
1360
  }
1361
  .overview.card.state-warning,
1362
  .overview.card.state-warning > .card-header,
1363
  .overview.card.state-warning > .card-footer {
1364
+ /*border-color: #d26605;*/
1365
  }
1366
  .overview.card.state-ok,
1367
  .overview.card.state-ok > .card-header,
1368
  .overview.card.state-ok > .card-footer {
1369
+ /*border-color: #0579d2;*/
1370
  }
1371
  .overview.card.state-good,
1372
  .overview.card.state-good > .card-header,
1373
  .overview.card.state-good > .card-footer {
1374
+ /*border-color: #008000;*/
1375
  }
1376
  .overview.card.state-good > .card-header {
1377
  background-color: rgba(98, 214, 83, 0.2);
1467
  }
1468
  #DynamicChangelog dd p {
1469
  margin-bottom: 5px;
1470
+ }
1471
+ #DashboardFeatureCards .card-footer {
1472
+ background-color: transparent;
1473
+ }
1474
+ #DashboardFeatureCards .list-group-item {
1475
+ border: 0 none;
1476
+ padding: .3rem 0.2rem;
1477
+ }
1478
+ #DashboardFeatureCards .card.highlighted img {
1479
+ filter: invert(100%);
1480
+ }
1481
+ #DashboardFeatureCards .card.highlighted .list-group-item {
1482
+ background-color: transparent;
1483
+ }
1484
+ #DashboardFeatureCards .card.highlighted .list-group-item a {
1485
+ color: white;
1486
+ }
1487
+ #DashboardFeatureCards .img-holder {
1488
+ text-align: center;
1489
+ }
1490
+ #DashboardFeatureCards .img-holder .card-img-top {
1491
+ width: 16%;
1492
+ }
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;
1504
+ border: 0 none !important;
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 {
1521
+ background: transparent;
1522
+ border: 0 none !important;
1523
+ color: #008000;
1524
+ vertical-align: inherit;
1525
+ }
1526
+
1527
+ #wpfooter {
1528
+ display: none;
1529
+ }
1530
+ #FooterBannerGoPro {
1531
+ width: 100%;
1532
+ background-color: white;
1533
+ border-color: transparent;
1534
+ border-width: 1px 1px 0 1px;
1535
+ margin: auto;
1536
+ padding-top: 15px;
1537
+ text-align: center;
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;
1545
+ transition: 0.5s ease;
1546
+ }
1547
+ 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
  }
resources/images/bootstrap/arrow-down-up.svg ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-arrow-down-up" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" d="M11.5 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L11 2.707V14.5a.5.5 0 0 0 .5.5zm-7-14a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L4 13.293V1.5a.5.5 0 0 1 .5-.5z"/>
3
+ </svg>
resources/images/bootstrap/award.svg ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-award" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" d="M9.669.864L8 0 6.331.864l-1.858.282-.842 1.68-1.337 1.32L2.6 6l-.306 1.854 1.337 1.32.842 1.68 1.858.282L8 12l1.669-.864 1.858-.282.842-1.68 1.337-1.32L13.4 6l.306-1.854-1.337-1.32-.842-1.68L9.669.864zm1.196 1.193l-1.51-.229L8 1.126l-1.355.702-1.51.229-.684 1.365-1.086 1.072L3.614 6l-.25 1.506 1.087 1.072.684 1.365 1.51.229L8 10.874l1.356-.702 1.509-.229.684-1.365 1.086-1.072L12.387 6l.248-1.506-1.086-1.072-.684-1.365z"/>
3
+ <path d="M4 11.794V16l4-1 4 1v-4.206l-2.018.306L8 13.126 6.018 12.1 4 11.794z"/>
4
+ </svg>
resources/images/bootstrap/binoculars.svg ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-binoculars" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" d="M3 2.5A1.5 1.5 0 0 1 4.5 1h1A1.5 1.5 0 0 1 7 2.5V5h2V2.5A1.5 1.5 0 0 1 10.5 1h1A1.5 1.5 0 0 1 13 2.5v2.382a.5.5 0 0 0 .276.447l.895.447A1.5 1.5 0 0 1 15 7.118V14.5a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 14.5v-3a.5.5 0 0 1 .146-.354l.854-.853V9.5a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5v.793l.854.853A.5.5 0 0 1 7 11.5v3A1.5 1.5 0 0 1 5.5 16h-3A1.5 1.5 0 0 1 1 14.5V7.118a1.5 1.5 0 0 1 .83-1.342l.894-.447A.5.5 0 0 0 3 4.882V2.5zM4.5 2a.5.5 0 0 0-.5.5V3h2v-.5a.5.5 0 0 0-.5-.5h-1zM6 4H4v.882a1.5 1.5 0 0 1-.83 1.342l-.894.447A.5.5 0 0 0 2 7.118V13h4v-1.293l-.854-.853A.5.5 0 0 1 5 10.5v-1A1.5 1.5 0 0 1 6.5 8h3A1.5 1.5 0 0 1 11 9.5v1a.5.5 0 0 1-.146.354l-.854.853V13h4V7.118a.5.5 0 0 0-.276-.447l-.895-.447A1.5 1.5 0 0 1 12 4.882V4h-2v1.5a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5V4zm4-1h2v-.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5V3zm4 11h-4v.5a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5V14zm-8 0H2v.5a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5V14z"/>
3
+ </svg>
resources/images/bootstrap/book-half.svg ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-book-half" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" d="M8.5 2.687v9.746c.935-.53 2.12-.603 3.213-.493 1.18.12 2.37.461 3.287.811V2.828c-.885-.37-2.154-.769-3.388-.893-1.33-.134-2.458.063-3.112.752zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783z"/>
3
+ </svg>
resources/images/bootstrap/bug.svg ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-bug" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" d="M4.355.522a.5.5 0 0 1 .623.333l.291.956A4.979 4.979 0 0 1 8 1c1.007 0 1.946.298 2.731.811l.29-.956a.5.5 0 1 1 .957.29l-.41 1.352A4.985 4.985 0 0 1 13 6h.5a.5.5 0 0 0 .5-.5V5a.5.5 0 0 1 1 0v.5A1.5 1.5 0 0 1 13.5 7H13v1h1.5a.5.5 0 0 1 0 1H13v1h.5a1.5 1.5 0 0 1 1.5 1.5v.5a.5.5 0 1 1-1 0v-.5a.5.5 0 0 0-.5-.5H13a5 5 0 0 1-10 0h-.5a.5.5 0 0 0-.5.5v.5a.5.5 0 1 1-1 0v-.5A1.5 1.5 0 0 1 2.5 10H3V9H1.5a.5.5 0 0 1 0-1H3V7h-.5A1.5 1.5 0 0 1 1 5.5V5a.5.5 0 0 1 1 0v.5a.5.5 0 0 0 .5.5H3c0-1.364.547-2.601 1.432-3.503l-.41-1.352a.5.5 0 0 1 .333-.623zM4 7v4a4 4 0 0 0 3.5 3.97V7H4zm4.5 0v7.97A4 4 0 0 0 12 11V7H8.5zM12 6H4a3.99 3.99 0 0 1 1.333-2.982A3.983 3.983 0 0 1 8 2c1.025 0 1.959.385 2.666 1.018A3.989 3.989 0 0 1 12 6z"/>
3
+ </svg>
resources/images/bootstrap/chat-right-dots-fill.svg ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-chat-right-dots-fill" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" d="M16 2a2 2 0 0 0-2-2H2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h9.586a1 1 0 0 1 .707.293l2.853 2.853a.5.5 0 0 0 .854-.353V2zM5 6a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
3
+ </svg>
resources/images/bootstrap/diagram-3.svg ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-diagram-3" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" d="M6 3.5A1.5 1.5 0 0 1 7.5 2h1A1.5 1.5 0 0 1 10 3.5v1A1.5 1.5 0 0 1 8.5 6v1H14a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0v-1A.5.5 0 0 1 2 7h5.5V6A1.5 1.5 0 0 1 6 4.5v-1zM8.5 5a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1zM0 11.5A1.5 1.5 0 0 1 1.5 10h1A1.5 1.5 0 0 1 4 11.5v1A1.5 1.5 0 0 1 2.5 14h-1A1.5 1.5 0 0 1 0 12.5v-1zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1zm4.5.5A1.5 1.5 0 0 1 7.5 10h1a1.5 1.5 0 0 1 1.5 1.5v1A1.5 1.5 0 0 1 8.5 14h-1A1.5 1.5 0 0 1 6 12.5v-1zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1zm4.5.5a1.5 1.5 0 0 1 1.5-1.5h1a1.5 1.5 0 0 1 1.5 1.5v1a1.5 1.5 0 0 1-1.5 1.5h-1a1.5 1.5 0 0 1-1.5-1.5v-1zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1z"/>
3
+ </svg>
resources/images/bootstrap/emoji-smile.svg ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-emoji-smile" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
3
+ <path fill-rule="evenodd" d="M4.285 9.567a.5.5 0 0 1 .683.183A3.498 3.498 0 0 0 8 11.5a3.498 3.498 0 0 0 3.032-1.75.5.5 0 1 1 .866.5A4.498 4.498 0 0 1 8 12.5a4.498 4.498 0 0 1-3.898-2.25.5.5 0 0 1 .183-.683z"/>
4
+ <path d="M7 6.5C7 7.328 6.552 8 6 8s-1-.672-1-1.5S5.448 5 6 5s1 .672 1 1.5zm4 0c0 .828-.448 1.5-1 1.5s-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5z"/>
5
+ </svg>
resources/images/bootstrap/link-45deg.svg ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-link-45deg" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M4.715 6.542L3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1.001 1.001 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4.018 4.018 0 0 1-.128-1.287z"/>
3
+ <path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 0 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 0 0-4.243-4.243L6.586 4.672z"/>
4
+ </svg>
resources/images/bootstrap/pencil-square.svg ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil-square" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/>
3
+ <path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5v11z"/>
4
+ </svg>
resources/images/bootstrap/people.svg ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-people" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" d="M15 14s1 0 1-1-1-4-5-4-5 3-5 4 1 1 1 1h8zm-7.978-1h7.956a.274.274 0 0 0 .014-.002l.008-.002c-.002-.264-.167-1.03-.76-1.72C13.688 10.629 12.718 10 11 10c-1.717 0-2.687.63-3.24 1.276-.593.69-.759 1.457-.76 1.72a1.05 1.05 0 0 0 .022.004zM11 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm3-2a3 3 0 1 1-6 0 3 3 0 0 1 6 0zM6.936 9.28a5.88 5.88 0 0 0-1.23-.247A7.35 7.35 0 0 0 5 9c-4 0-5 3-5 4 0 .667.333 1 1 1h4.216A2.238 2.238 0 0 1 5 13c0-1.01.377-2.042 1.09-2.904.243-.294.526-.569.846-.816zM4.92 10c-1.668.02-2.615.64-3.16 1.276C1.163 11.97 1 12.739 1 13h3c0-1.045.323-2.086.92-3zM1.5 5.5a3 3 0 1 1 6 0 3 3 0 0 1-6 0zm3-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4z"/>
3
+ </svg>
resources/images/bootstrap/person-badge.svg ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-person-badge" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" d="M2 2.5A2.5 2.5 0 0 1 4.5 0h7A2.5 2.5 0 0 1 14 2.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2.5zM4.5 1A1.5 1.5 0 0 0 3 2.5v10.795a4.2 4.2 0 0 1 .776-.492C4.608 12.387 5.937 12 8 12s3.392.387 4.224.803a4.2 4.2 0 0 1 .776.492V2.5A1.5 1.5 0 0 0 11.5 1h-7z"/>
3
+ <path fill-rule="evenodd" d="M8 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM6 2.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5z"/>
4
+ </svg>
resources/images/bootstrap/person-lines-fill.svg ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-person-lines-fill" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" d="M1 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm7 1.5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5zm2 9a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z"/>
3
+ </svg>
resources/images/bootstrap/shield-shaded.svg ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-shield-shaded" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" d="M5.443 1.991a60.17 60.17 0 0 0-2.725.802.454.454 0 0 0-.315.366C1.87 7.056 3.1 9.9 4.567 11.773c.736.94 1.533 1.636 2.197 2.093.333.228.626.394.857.5.116.053.21.089.282.11A.73.73 0 0 0 8 14.5c.007-.001.038-.005.097-.023.072-.022.166-.058.282-.111.23-.106.525-.272.857-.5a10.197 10.197 0 0 0 2.197-2.093C12.9 9.9 14.13 7.056 13.597 3.159a.454.454 0 0 0-.315-.366c-.626-.2-1.682-.526-2.725-.802C9.491 1.71 8.51 1.5 8 1.5c-.51 0-1.49.21-2.557.491zm-.256-.966C6.23.749 7.337.5 8 .5c.662 0 1.77.249 2.813.525a61.09 61.09 0 0 1 2.772.815c.528.168.926.623 1.003 1.184.573 4.197-.756 7.307-2.367 9.365a11.191 11.191 0 0 1-2.418 2.3 6.942 6.942 0 0 1-1.007.586c-.27.124-.558.225-.796.225s-.526-.101-.796-.225a6.908 6.908 0 0 1-1.007-.586 11.192 11.192 0 0 1-2.417-2.3C2.167 10.331.839 7.221 1.412 3.024A1.454 1.454 0 0 1 2.415 1.84a61.11 61.11 0 0 1 2.772-.815z"/>
3
+ <path d="M8 2.25c.909 0 3.188.685 4.254 1.022a.94.94 0 0 1 .656.773c.814 6.424-4.13 9.452-4.91 9.452V2.25z"/>
4
+ </svg>
resources/images/bootstrap/sliders.svg ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-sliders" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" d="M11.5 2a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM9.05 3a2.5 2.5 0 0 1 4.9 0H16v1h-2.05a2.5 2.5 0 0 1-4.9 0H0V3h9.05zM4.5 7a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM2.05 8a2.5 2.5 0 0 1 4.9 0H16v1H6.95a2.5 2.5 0 0 1-4.9 0H0V8h2.05zm9.45 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm-2.45 1a2.5 2.5 0 0 1 4.9 0H16v1h-2.05a2.5 2.5 0 0 1-4.9 0H0v-1h9.05z"/>
3
+ </svg>
resources/images/bootstrap/stickies.svg ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-stickies" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" d="M0 1.5A1.5 1.5 0 0 1 1.5 0H13a1 1 0 0 1 1 1H1.5a.5.5 0 0 0-.5.5V14a1 1 0 0 1-1-1V1.5z"/>
3
+ <path fill-rule="evenodd" d="M2 3.5A1.5 1.5 0 0 1 3.5 2h11A1.5 1.5 0 0 1 16 3.5v6.086a1.5 1.5 0 0 1-.44 1.06l-4.914 4.915a1.5 1.5 0 0 1-1.06.439H3.5A1.5 1.5 0 0 1 2 14.5v-11zM3.5 3a.5.5 0 0 0-.5.5v11a.5.5 0 0 0 .5.5h6.086a.5.5 0 0 0 .353-.146l4.915-4.915A.5.5 0 0 0 15 9.586V3.5a.5.5 0 0 0-.5-.5h-11z"/>
4
+ <path fill-rule="evenodd" d="M10.5 10a.5.5 0 0 0-.5.5v5H9v-5A1.5 1.5 0 0 1 10.5 9h5v1h-5z"/>
5
+ </svg>
resources/images/bootstrap/sticky.svg ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-sticky" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" d="M1 2.5A1.5 1.5 0 0 1 2.5 1h11A1.5 1.5 0 0 1 15 2.5v6.086a1.5 1.5 0 0 1-.44 1.06l-4.914 4.915a1.5 1.5 0 0 1-1.06.439H2.5A1.5 1.5 0 0 1 1 13.5v-11zM2.5 2a.5.5 0 0 0-.5.5v11a.5.5 0 0 0 .5.5h6.086a.5.5 0 0 0 .353-.146l4.915-4.915A.5.5 0 0 0 14 8.586V2.5a.5.5 0 0 0-.5-.5h-11z"/>
3
+ <path fill-rule="evenodd" d="M9.5 9a.5.5 0 0 0-.5.5v5H8v-5A1.5 1.5 0 0 1 9.5 8h5v1h-5z"/>
4
+ </svg>
resources/images/bootstrap/stoplights.svg ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-stoplights" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M9.5 3.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0 4a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0 4a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/>
3
+ <path fill-rule="evenodd" d="M10 1H6a1 1 0 0 0-1 1v11a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM6 0a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H6z"/>
4
+ <path d="M14 2h-2v2c1.2-.4 1.833-1.5 2-2zM2 2h2v2c-1.2-.4-1.833-1.5-2-2zm12 4h-2v2c1.2-.4 1.833-1.5 2-2zM2 6h2v2c-1.2-.4-1.833-1.5-2-2zm12 4h-2v2c1.2-.4 1.833-1.5 2-2zM2 10h2v2c-1.2-.4-1.833-1.5-2-2z"/>
5
+ </svg>
resources/images/shield/dash-background.jpg CHANGED
Binary file
resources/js/bootstrap-select.min.js CHANGED
@@ -1,9 +1,9 @@
1
  /*!
2
- * Bootstrap-select v1.13.14 (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(z){"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!==z.inArray(i,t))return-1===z.inArray(i,r)||Boolean(e.nodeValue.match(l)||e.nodeValue.match(a));for(var s=z(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 P(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=z(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 T(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:z.valHooks.select.set};z.valHooks.select.set=function(e,t){return t&&!p.useDefault&&z(e).data("selected",!0),p._set.apply(this,arguments)};var A=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 L(e){return parseInt(e,10)||0}z.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"},N=27,D=13,H=32,W=9,B=38,M=40,R={success:!1,major:"3"};try{R.full=(z.fn.dropdown.Constructor.VERSION||"").split(" ")[0].split("."),R.major=R.full[0],R.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},_={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()};_.a.setAttribute("role","option"),"4"===R.major&&(_.a.className="dropdown-item"),_.subtext.className="text-muted",_.text=_.span.cloneNode(!1),_.text.className="text",_.checkMark=_.span.cloneNode(!1);var G=new RegExp(B+"|"+M),q=new RegExp("^"+W+"$|"+N),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(" ")),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}},Y=function(e,t){var i=this;p.useDefault||(z.valHooks.select.set=p._set,p.useDefault=!0),this.$element=z(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),!R.success){try{R.full=(z.fn.dropdown.Constructor.VERSION||"").split(" ")[0].split(".")}catch(e){Y.BootstrapVersion?R.full=Y.BootstrapVersion.split(" ")[0].split("."):(R.full=[R.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))}R.major=R.full[0],R.success=!0}if("4"===R.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=z(this);if(e.is("select")){var t=e.data("selectpicker"),i="object"==typeof c&&c;if(t){if(i)for(var s in i)i.hasOwnProperty(s)&&(t.options[s]=i[s])}else{var n=e.data();for(var o in n)n.hasOwnProperty(o)&&-1!==z.inArray(o,d)&&delete n[o];var r=z.extend({},Y.DEFAULTS,z.fn.selectpicker.defaults||{},n,i);r.template=z.extend({},Y.DEFAULTS.template,z.fn.selectpicker.defaults?z.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.14",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");U++,this.selectId="bs-select-"+U,this.$element[0].classList.add("bs-select-hidden"),this.multiple=this.$element.prop("multiple"),this.autofocus=this.$element.prop("autofocus"),this.$element[0].classList.contains("show-tick")&&(this.options.showTick=!0),this.$newElement=this.createDropdown(),this.buildData(),this.$element.after(this.$newElement).prependTo(this.$newElement),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"),this.$element[0].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)}}),i.$element[0].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":"";R.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" 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"===R.major?"":'<span class="bs-caret">'+this.options.template.caret+"</span>")+'</button><div class="'+V.MENU+" "+("4"===R.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"===R.major?V.SHOW:"")+'" role="presentation"></ul></div>'+a+"</div></div>",z(n)},setPositionData:function(){this.selectpicker.view.canHighlight=[];for(var e=this.selectpicker.view.size=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),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(A,e,t){var L,N,D=this,i=0,H=[];if(this.selectpicker.isSearching=A,this.selectpicker.current=A?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&&(N=D.selectpicker.main.elements[D.prevActiveIndex],H=D.selectpicker.main.elements[D.activeIndex],L=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(L)),void 0!==D.prevActiveIndex&&D.prevActiveIndex!==D.activeIndex&&D.prevActiveIndex!==D.selectedIndex&&D.defocusItem(N),(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(),(A||!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&&P(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(A&&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}),z(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=!1;if(this.options.title&&!this.multiple){this.selectpicker.view.titleOption||(this.selectpicker.view.titleOption=document.createElement("option")),e=!0;var t=this.$element[0],i=!1,s=!this.selectpicker.view.titleOption.parentNode;if(s)this.selectpicker.view.titleOption.className="bs-title-option",this.selectpicker.view.titleOption.value="",i=void 0===z(t.options[t.selectedIndex]).attr("selected")&&void 0===this.$element.data("selected");!s&&0===this.selectpicker.view.titleOption.index||t.insertBefore(this.selectpicker.view.titleOption,t.firstChild),i&&(t.selectedIndex=0)}return e},buildData:function(){var p=':not([hidden]):not([data-hidden="true"])',u=[],f=0,e=this.setPlaceholder()?1:0;this.options.hideDisabled&&(p+=":not(:disabled)");var t=this.$element[0].querySelectorAll("select > *"+p);function m(e){var t=u[u.length-1];t&&"divider"===t.type&&(t.optID||e.optID)||((e=e||{}).type="divider",u.push(e))}function v(e,t){if((t=t||{}).divider="true"===e.getAttribute("data-divider"),t.divider)m({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 i(e,t){var i=t[e],s=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&&m({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),v(h,{headerIndex:r,lastIndex:l,optID:a.optID,optgroupClass:a.optgroupClass,disabled:i.disabled})}n&&m({optID:f})}}for(var s=t.length;e<s;e++){var n=t[e];"OPTGROUP"!==n.tagName?v(n,{}):i(e,t)}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)}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:!T(i,n)),this.tabIndex(),"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&&P([c],t.options.whiteList,t.options.sanitizeFn),l.innerHTML="",l.appendChild(c),R.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,"")),R.major<4&&(n.classList.add("bs3"),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=document.createElement("div"),i=document.createElement("div"),s=document.createElement("div"),n=document.createElement("ul"),o=document.createElement("li"),r=document.createElement("li"),l=document.createElement("li"),a=document.createElement("a"),c=document.createElement("span"),d=this.options.header&&0<this.$menu.find("."+V.POPOVERHEADER).length?this.$menu.find("."+V.POPOVERHEADER)[0].cloneNode(!0):null,h=this.options.liveSearch?document.createElement("div"):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:""),t.className=this.$menu[0].parentNode.className+" "+V.SHOW,t.style.width=0,"auto"===this.options.width&&(i.style.minWidth=0),i.className=V.MENU+" "+V.SHOW,s.className="inner "+V.SHOW,n.className=V.MENU+" inner "+("4"===R.major?V.SHOW:""),o.className=V.DIVIDER,r.className="dropdown-header",c.appendChild(document.createTextNode("\u200b")),a.appendChild(c),l.appendChild(a),r.appendChild(c.cloneNode(!0)),this.selectpicker.view.widestOption&&n.appendChild(this.selectpicker.view.widestOption.cloneNode(!0)),n.appendChild(l),n.appendChild(o),n.appendChild(r),d&&i.appendChild(d),h){var m=document.createElement("input");h.className="bs-searchbox",m.className="form-control",h.appendChild(m),i.appendChild(h)}p&&i.appendChild(p),s.appendChild(n),i.appendChild(s),u&&i.appendChild(u),t.appendChild(i),document.body.appendChild(t);var v,g=l.offsetHeight,b=r?r.offsetHeight:0,w=d?d.offsetHeight:0,I=h?h.offsetHeight:0,x=p?p.offsetHeight:0,k=u?u.offsetHeight:0,y=z(o).outerHeight(!0),$=!!window.getComputedStyle&&window.getComputedStyle(i),S=i.offsetWidth,E=$?null:z(i),C={vert:L($?$.paddingTop:E.css("paddingTop"))+L($?$.paddingBottom:E.css("paddingBottom"))+L($?$.borderTopWidth:E.css("borderTopWidth"))+L($?$.borderBottomWidth:E.css("borderBottomWidth")),horiz:L($?$.paddingLeft:E.css("paddingLeft"))+L($?$.paddingRight:E.css("paddingRight"))+L($?$.borderLeftWidth:E.css("borderLeftWidth"))+L($?$.borderRightWidth:E.css("borderRightWidth"))},O={vert:C.vert+L($?$.marginTop:E.css("marginTop"))+L($?$.marginBottom:E.css("marginBottom"))+2,horiz:C.horiz+L($?$.marginLeft:E.css("marginLeft"))+L($?$.marginRight:E.css("marginRight"))+2};s.style.overflowY="scroll",v=i.offsetWidth-S,document.body.removeChild(t),this.sizeInfo.liHeight=g,this.sizeInfo.dropdownHeaderHeight=b,this.sizeInfo.headerHeight=w,this.sizeInfo.searchHeight=I,this.sizeInfo.actionsHeight=x,this.sizeInfo.doneButtonHeight=k,this.sizeInfo.dividerHeight=y,this.sizeInfo.menuPadding=C,this.sizeInfo.menuExtras=O,this.sizeInfo.menuWidth=S,this.sizeInfo.menuInnerInnerWidth=S-C.horiz,this.sizeInfo.totalMenuWidth=this.sizeInfo.menuWidth,this.sizeInfo.scrollBarWidth=v,this.sizeInfo.selectHeight=this.$newElement[0].offsetHeight,this.setPositionData()}},getSelectPosition:function(){var e,t=z(window),i=this.$newElement.offset(),s=z(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=z(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=z('<div class="bs-container" />');function e(e){var t={},i=r.options.display||!!z.fn.dropdown.Constructor.Default&&z.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,(R.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=z(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))}),z(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"===R.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("tabindex",-1).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)),-1!=this.$button.attr("tabindex")||this.$element.data("tabindex")||this.$button.removeAttr("tabindex"))},tabIndex:function(){this.$element.data("tabindex")!==this.$element.attr("tabindex")&&-98!==this.$element.attr("tabindex")&&"-98"!==this.$element.attr("tabindex")&&(this.$element.data("tabindex",this.$element.attr("tabindex")),this.$button.attr("tabindex",this.$element.data("tabindex"))),this.$element.attr("tabindex",-98)},clickListener:function(){var C=this,t=z(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<R.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<R.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=z(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=T(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=z(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),i.trigger("blur"),!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=z('<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(z("<div>"+$+"</div>")),d=!1,C.$element.trigger("maxReached"+j)),g&&w&&(E.append(z("<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||(A=[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&&!z(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(),z(this).hasClass("bs-select-all")?C.selectAll():C.deselectAll()}),this.$element.on("change"+j,function(){C.render(),C.$element.trigger("changed"+j,A),A=null}).on("focus"+j,function(){C.options.mobile||C.$button.trigger("focus")})},liveSearchListener:function(){var u=this,f=document.createElement("li");this.$button.on("click.bs.dropdown.data-api",function(){u.$searchbox.val()&&u.$searchbox.val("")}),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.val();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),t.length||(f.className="no-results",f.innerHTML=u.options.noneResultsText.replace("{0}",'"'+S(e)+'"'),u.$menuInner[0].firstChild.appendChild(f))}else u.$menuInner.scrollTop(0),u.createView(!1)})},_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=T(t);if(A=[null,null,i],this.$element.val(e).trigger("changed"+j,A),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(),A=null,this.$element},changeAll:function(e){if(this.multiple){void 0===e&&(e=!0);var t=this.$element[0],i=0,s=0,n=T(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(),A=[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=z(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===W&&!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===N&&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!==M&&!h||(++t+f>=a.selectpicker.view.canHighlight.length&&(t=0),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!==M&&!h||(0===t?m=a.$menuInner[0].scrollTop=0: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===H&&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===H&&!a.selectpicker.keydown.keyHistory||e.which===D||e.which===W&&a.options.selectOnTab)&&(e.which!==H&&e.preventDefault(),a.options.liveSearch&&e.which===H||(a.$menuInner.find(".active a").trigger("click",!0),r.trigger("focus"),a.options.liveSearch||(e.preventDefault(),z(document).data("spaceSelect",!0))))}},mobile:function(){this.$element[0].classList.add("mobile-device")},refresh:function(){var e=z.extend({},this.options,this.$element.data());this.options=e,this.checkDisabled(),this.setStyle(),this.render(),this.buildData(),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.$element.off(j).removeData("selectpicker").removeClass("bs-select-hidden selectpicker"),z(window).off(j+"."+this.selectId)}};var J=z.fn.selectpicker;z.fn.selectpicker=Z,z.fn.selectpicker.Constructor=Y,z.fn.selectpicker.noConflict=function(){return z.fn.selectpicker=J,this};var Q=z.fn.dropdown.Constructor._dataApiKeydownHandler||z.fn.dropdown.Constructor.prototype.keydown;z(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()}),z(window).on("load"+j+".data-api",function(){z(".selectpicker").each(function(){var e=z(this);Z.call(e,e.data())})})}(e)});
9
  //# sourceMappingURL=bootstrap-select.min.js.map
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/global-plugin.js CHANGED
@@ -40,7 +40,7 @@ var iCWP_WPSF_SecurityAdmin = new function () {
40
 
41
  var iCWP_WPSF_ParseAjaxResponse = new function () {
42
  this.parseIt = function ( raw ) {
43
- let parsed = {};
44
  try {
45
  parsed = JSON.parse( raw );
46
  }
@@ -63,7 +63,7 @@ var iCWP_WPSF_StandardAjax = new function () {
63
  data: reqData,
64
  dataType: "text",
65
  success: function ( raw ) {
66
- let resp = iCWP_WPSF_ParseAjaxResponse.parseIt( raw );
67
 
68
  if ( typeof iCWP_WPSF_Toaster !== 'undefined' ) {
69
  iCWP_WPSF_Toaster.showMessage( resp.data.message, resp.success );
@@ -334,7 +334,7 @@ var iCWP_WPSF_Growl = new function () {
334
 
335
  var iCWP_WPSF_BodyOverlay = new function () {
336
 
337
- let nOverlays = 0;
338
 
339
  this.show = function () {
340
  nOverlays++;
40
 
41
  var iCWP_WPSF_ParseAjaxResponse = new function () {
42
  this.parseIt = function ( raw ) {
43
+ var parsed = {};
44
  try {
45
  parsed = JSON.parse( raw );
46
  }
63
  data: reqData,
64
  dataType: "text",
65
  success: function ( raw ) {
66
+ var resp = iCWP_WPSF_ParseAjaxResponse.parseIt( raw );
67
 
68
  if ( typeof iCWP_WPSF_Toaster !== 'undefined' ) {
69
  iCWP_WPSF_Toaster.showMessage( resp.data.message, resp.success );
334
 
335
  var iCWP_WPSF_BodyOverlay = new function () {
336
 
337
+ var nOverlays = 0;
338
 
339
  this.show = function () {
340
  nOverlays++;
resources/js/plugin.js CHANGED
@@ -81,13 +81,13 @@ if ( typeof icwp_wpsf_vars_tourmanager !== 'undefined' ) {
81
 
82
  var iCWP_WPSF_Toaster = new function () {
83
 
84
- this.showMessage = function ( sMessage, bSuccess ) {
85
  let $oNewToast = jQuery( '#icwpWpsfOptionsToast' );
86
  let $oToastBody = jQuery( '.toast-body', $oNewToast );
87
  $oToastBody.html( '' );
88
 
89
- jQuery( '<span></span>' ).html( sMessage )
90
- .addClass( bSuccess ? 'text-dark' : 'text-danger' )
91
  .appendTo( $oToastBody );
92
 
93
  $oNewToast.css( 'z-index', 1000 );
81
 
82
  var iCWP_WPSF_Toaster = new function () {
83
 
84
+ this.showMessage = function ( msg, success ) {
85
  let $oNewToast = jQuery( '#icwpWpsfOptionsToast' );
86
  let $oToastBody = jQuery( '.toast-body', $oNewToast );
87
  $oToastBody.html( '' );
88
 
89
+ jQuery( '<span></span>' ).html( msg )
90
+ .addClass( success ? 'text-dark' : 'text-danger' )
91
  .appendTo( $oToastBody );
92
 
93
  $oNewToast.css( 'z-index', 1000 );
resources/js/shield/ipanalyse.js CHANGED
@@ -2,8 +2,8 @@ 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 ( $oThis.val().length > 0 ) {
6
- newUrl += "&analyse_ip=" + $oThis.val();
7
  }
8
  window.history.replaceState(
9
  {},
@@ -11,7 +11,7 @@ jQuery.fn.icwpWpsfIpAnalyse = function ( options ) {
11
  newUrl
12
  );
13
 
14
- sendReq( { 'fIp': $oThis.val() } );
15
  };
16
 
17
  var clearAnalyseIpParam = function () {
@@ -27,7 +27,7 @@ jQuery.fn.icwpWpsfIpAnalyse = function ( options ) {
27
 
28
  jQuery( '#IpReviewContent' ).html( 'loading IP info ...' );
29
 
30
- var aReqData = aOpts[ 'build_ip_analyse' ];
31
  jQuery.post( ajaxurl, jQuery.extend( aReqData, params ),
32
  function ( oResponse ) {
33
 
@@ -61,12 +61,25 @@ jQuery.fn.icwpWpsfIpAnalyse = function ( options ) {
61
  } );
62
 
63
  jQuery( document ).ready( function () {
64
- $oThis.on( 'change', runAnalysis );
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
  let urlParams = new URLSearchParams( window.location.search );
67
  let theIP = urlParams.get( 'analyse_ip' );
68
  if ( theIP ) {
69
- $oThis.selectpicker( 'val', theIP );
70
  runAnalysis();
71
  }
72
  else {
@@ -79,7 +92,7 @@ jQuery.fn.icwpWpsfIpAnalyse = function ( options ) {
79
  } );
80
  };
81
 
82
- var $oThis = this;
83
  var aOpts = jQuery.extend( {}, options );
84
  initialise();
85
 
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
  }
8
  window.history.replaceState(
9
  {},
11
  newUrl
12
  );
13
 
14
+ sendReq( { 'fIp': $oIpSelect.val() } );
15
  };
16
 
17
  var clearAnalyseIpParam = function () {
27
 
28
  jQuery( '#IpReviewContent' ).html( 'loading IP info ...' );
29
 
30
+ var aReqData = aOpts[ 'ajax_build_ip_analyse' ];
31
  jQuery.post( ajaxurl, jQuery.extend( aReqData, params ),
32
  function ( oResponse ) {
33
 
61
  } );
62
 
63
  jQuery( document ).ready( function () {
64
+
65
+ var $aIpActions = jQuery( document ).on( 'click', 'a.ip_analyse_action', function ( evt ) {
66
+ evt.preventDefault();
67
+ if ( confirm( 'Are you sure?' ) ) {
68
+ let $oThis = jQuery( this );
69
+ let params = aOpts[ 'ajax_ip_analyse_action' ];
70
+ params.ip = $oThis.data( 'ip' );
71
+ params.ip_action = $oThis.data( 'ip_action' );
72
+ iCWP_WPSF_StandardAjax.send_ajax_req( params );
73
+ }
74
+ return false;
75
+ } );
76
+
77
+ $oIpSelect.on( 'change', runAnalysis );
78
 
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 {
92
  } );
93
  };
94
 
95
+ var $oIpSelect = jQuery( '#IpReviewSelect' );
96
  var aOpts = jQuery.extend( {}, options );
97
  initialise();
98
 
resources/js/shield/mainwp-extension.js ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function ( $, window, document, undefined ) {
2
+
3
+ var pluginName = 'icwpWpsfMainwpExtension';
4
+
5
+ function Ob_TableActions( element, options ) {
6
+ this.element = element;
7
+ this._name = pluginName;
8
+ this._defaults = $.fn.icwpWpsfMainwpExt.defaults;
9
+ this.options = $.extend(
10
+ {
11
+ 'forms': {
12
+ 'insert': ''
13
+ }
14
+ },
15
+ this._defaults,
16
+ options
17
+ );
18
+ this.init();
19
+ }
20
+
21
+ $.extend(
22
+ Ob_TableActions.prototype,
23
+ {
24
+ init: function () {
25
+ this.buildCache();
26
+ this.bindEvents();
27
+ },
28
+ destroy: function () {
29
+ this.unbindEvents();
30
+ this.$element.removeData();
31
+ },
32
+ buildCache: function () {
33
+ this.$element = $( this.element );
34
+ this.$oFormInsert = this.options[ 'forms' ][ 'insert' ];
35
+ },
36
+ bindEvents: function () {
37
+ var plugin = this;
38
+
39
+ plugin.$element.on(
40
+ 'click' + '.' + plugin._name,
41
+ '.site-dropdown a.site_action',
42
+ function ( evt ) {
43
+ evt.preventDefault();
44
+
45
+ plugin.options[ 'req_params' ] = $.extend(
46
+ plugin.options[ 'ajax_sh_site_action' ],
47
+ {
48
+ 'sid': $( this ).parent().data( 'sid' ),
49
+ 'saction': $( this ).data( 'saction' )
50
+ }
51
+ );
52
+ plugin.site_action.call( plugin );
53
+ }
54
+ );
55
+
56
+ plugin.$element.on(
57
+ 'click' + '.' + plugin._name,
58
+ 'button.action.item_action',
59
+ function ( evt ) {
60
+ evt.preventDefault();
61
+ plugin.options[ 'working_rid' ] = $( this ).data( 'rid' );
62
+ plugin.options[ 'working_item_action' ] = $( this ).data( 'item_action' );
63
+ plugin.itemAction.call( plugin );
64
+ }
65
+ );
66
+
67
+ plugin.$element.on(
68
+ 'click' + '.' + plugin._name,
69
+ '.tablenav.top input[type=submit].button.action',
70
+ function ( evt ) {
71
+ evt.preventDefault();
72
+ var sAction = $( '#bulk-action-selector-top', plugin.$element ).find( ":selected" ).val();
73
+
74
+ if ( sAction === "-1" ) {
75
+ alert( icwp_wpsf_vars_insights.strings.select_action );
76
+ }
77
+ else {
78
+ var aCheckedIds = $( "input:checkbox[name=ids]:checked", plugin.$element ).map(
79
+ function () {
80
+ return $( this ).val()
81
+ } ).get();
82
+
83
+ if ( aCheckedIds.length < 1 ) {
84
+ alert( 'No rows currently selected' );
85
+ }
86
+ else {
87
+ plugin.options[ 'req_params' ][ 'bulk_action' ] = sAction;
88
+ plugin.options[ 'req_params' ][ 'ids' ] = aCheckedIds;
89
+ plugin.bulkAction.call( plugin );
90
+ }
91
+ }
92
+ return false;
93
+ }
94
+ );
95
+
96
+ plugin.$element.on(
97
+ 'click' + '.' + plugin._name,
98
+ 'button.action.custom-action',
99
+ function ( evt ) {
100
+ evt.preventDefault();
101
+ var $oButt = $( this );
102
+ var sCustomAction = $oButt.data( 'custom-action' );
103
+ if ( sCustomAction in plugin.options[ 'custom_actions_ajax' ] ) {
104
+ plugin.options[ 'working_custom_action' ] = plugin.options[ 'custom_actions_ajax' ][ sCustomAction ];
105
+ plugin.options[ 'working_custom_action' ][ 'rid' ] = $oButt.data( 'rid' );
106
+ plugin.customAction.call( plugin );
107
+ }
108
+ else {
109
+ /** This should never be reached live: **/
110
+ alert( 'custom action not supported: ' + sCustomAction );
111
+ }
112
+ }
113
+ );
114
+
115
+ plugin.$element.on(
116
+ 'click' + '.' + plugin._name,
117
+ 'button.action.href-download',
118
+ function ( evt ) {
119
+ evt.preventDefault();
120
+ var $oButt = $( this );
121
+ var sHref = $oButt.data( 'href-download' );
122
+ if ( sHref !== undefined ) {
123
+ plugin.options[ 'working_href_download' ] = sHref;
124
+ plugin.hrefDownload.call( plugin );
125
+ }
126
+ }
127
+ );
128
+
129
+ },
130
+ unbindEvents: function () {
131
+ /*
132
+ Unbind all events in our plugin's namespace that are attached
133
+ to "this.$element".
134
+ */
135
+ this.$element.off( '.' + this._name );
136
+ },
137
+
138
+ bulkAction: function () {
139
+ let reqData = this.options[ 'ajax_bulk_action' ];
140
+ this.sendReq( reqData );
141
+ },
142
+
143
+ site_action: function () {
144
+ this.sendReq();
145
+ },
146
+
147
+ customAction: function () {
148
+ this.sendReq( this.options[ 'working_custom_action' ] );
149
+ },
150
+
151
+ hrefDownload: function () {
152
+ $.fileDownload( this.options[ 'working_href_download' ], {
153
+ preparingMessageHtml: icwp_wpsf_vars_insights.strings.downloading_file,
154
+ failMessageHtml: icwp_wpsf_vars_insights.strings.downloading_file_problem
155
+ } );
156
+ return false;
157
+ },
158
+
159
+ sendReq: function ( reqData = {} ) {
160
+ iCWP_WPSF_BodyOverlay.show();
161
+
162
+ var plugin = this;
163
+
164
+ $.post( ajaxurl, $.extend( reqData, plugin.options[ 'req_params' ] ),
165
+ function ( oR ) {
166
+ if ( oR.success ) {
167
+ iCWP_WPSF_Growl.showMessage( oR.data.message, oR.success );
168
+ if ( oR.data.page_reload ) {
169
+ setTimeout( function () {
170
+ location.reload();
171
+ }, 1500 );
172
+ }
173
+ }
174
+ else {
175
+ let msg = 'Communications error with site.';
176
+ if ( oR.data.message !== undefined ) {
177
+ msg = oR.data.message;
178
+ }
179
+ alert( msg );
180
+ iCWP_WPSF_BodyOverlay.hide();
181
+ }
182
+ }
183
+ ).always( function () {
184
+ iCWP_WPSF_BodyOverlay.hide();
185
+ }
186
+ );
187
+ },
188
+ callback: function () {
189
+ }
190
+ }
191
+ );
192
+
193
+ $.fn.icwpWpsfMainwpExt = function ( options ) {
194
+ /*
195
+ jQuery( '#mainwp-shield-extension-table-sites' ).DataTable( {
196
+ serverSide: true,
197
+ "ajax": {
198
+ "url": ajaxurl,
199
+ "type": "POST",
200
+ "data": function ( d ) {
201
+ return $.extend( {}, d, this.options[ 'ajax_sh_site_action' ] );
202
+ },
203
+ "dataSrc": function ( json ) {
204
+ for ( var i = 0, ien = json.data.length; i < ien; i++ ) {
205
+ json.data[ i ].syncError = json.rowsInfo[ i ].syncError ? json.rowsInfo[ i ].syncError : false;
206
+ json.data[ i ].rowClass = json.rowsInfo[ i ].rowClass;
207
+ json.data[ i ].siteID = json.rowsInfo[ i ].siteID;
208
+ json.data[ i ].siteUrl = json.rowsInfo[ i ].siteUrl;
209
+ }
210
+ return json.data;
211
+ }
212
+ },
213
+ } );
214
+ */
215
+ return this.each(
216
+ function () {
217
+ if ( !$.data( this, "plugin_" + pluginName ) ) {
218
+ $.data( this, "plugin_" + pluginName, new Ob_TableActions( this, options ) );
219
+ }
220
+ }
221
+ );
222
+ };
223
+
224
+ $.fn.icwpWpsfMainwpExt.defaults = {
225
+ 'custom_actions_ajax': {},
226
+ 'req_params': {}
227
+ };
228
+
229
+ })( jQuery );
src/config/feature-admin_access_restriction.php CHANGED
@@ -16,23 +16,23 @@
16
  "run_if_wpcli": false,
17
  "order": 20
18
  },
19
- "wpcli": {
20
- "root": "secadmin"
21
  },
22
  "admin_notices": {
23
  "certain-options-restricted": {
24
- "id": "certain-options-restricted",
25
- "schedule": "conditions",
26
- "plugin_admin": "no",
27
- "per_user": true,
28
- "type": "warning"
29
  },
30
  "admin-users-restricted": {
31
- "id": "admin-users-restricted",
32
- "schedule": "conditions",
33
- "plugin_admin": "no",
34
- "type": "warning",
35
- "per_user": true
36
  }
37
  },
38
  "sections": [
@@ -84,6 +84,7 @@
84
  {
85
  "key": "enable_admin_access_restriction",
86
  "section": "section_enable_plugin_feature_admin_access_restriction",
 
87
  "default": "Y",
88
  "type": "checkbox",
89
  "link_info": "https://shsec.io/40",
@@ -107,6 +108,7 @@
107
  {
108
  "key": "sec_admin_users",
109
  "section": "section_admin_access_restriction_settings",
 
110
  "sensitive": true,
111
  "premium": true,
112
  "default": [],
@@ -120,6 +122,7 @@
120
  {
121
  "key": "admin_access_timeout",
122
  "section": "section_admin_access_restriction_settings",
 
123
  "default": 30,
124
  "type": "integer",
125
  "min": 1,
@@ -132,6 +135,7 @@
132
  {
133
  "key": "allow_email_override",
134
  "section": "section_admin_access_restriction_settings",
 
135
  "default": "Y",
136
  "type": "checkbox",
137
  "link_info": "https://shsec.io/gf",
@@ -154,6 +158,7 @@
154
  {
155
  "key": "admin_access_restrict_admin_users",
156
  "section": "section_admin_access_restriction_areas",
 
157
  "default": "N",
158
  "type": "checkbox",
159
  "link_info": "https://shsec.io/a0",
@@ -165,6 +170,7 @@
165
  {
166
  "key": "admin_access_restrict_plugins",
167
  "section": "section_admin_access_restriction_areas",
 
168
  "type": "multiple_select",
169
  "default": [],
170
  "value_options": [
@@ -193,6 +199,7 @@
193
  {
194
  "key": "admin_access_restrict_themes",
195
  "section": "section_admin_access_restriction_areas",
 
196
  "type": "multiple_select",
197
  "default": [],
198
  "value_options": [
@@ -225,6 +232,7 @@
225
  {
226
  "key": "admin_access_restrict_posts",
227
  "section": "section_admin_access_restriction_areas",
 
228
  "type": "multiple_select",
229
  "default": [],
230
  "value_options": [
@@ -276,9 +284,9 @@
276
  "type": "checkbox",
277
  "link_info": "",
278
  "link_blog": "",
279
- "name": "Replace Badge URL",
280
- "summary": "Replace Plugin Badge URL",
281
- "description": "When using the plugin badge, replace the link with your Whitelabel Home URL."
282
  },
283
  {
284
  "key": "wl_pluginnamemain",
16
  "run_if_wpcli": false,
17
  "order": 20
18
  },
19
+ "wpcli": {
20
+ "root": "secadmin"
21
  },
22
  "admin_notices": {
23
  "certain-options-restricted": {
24
+ "id": "certain-options-restricted",
25
+ "schedule": "conditions",
26
+ "plugin_admin": "no",
27
+ "per_user": true,
28
+ "type": "warning"
29
  },
30
  "admin-users-restricted": {
31
+ "id": "admin-users-restricted",
32
+ "schedule": "conditions",
33
+ "plugin_admin": "no",
34
+ "type": "warning",
35
+ "per_user": true
36
  }
37
  },
38
  "sections": [
84
  {
85
  "key": "enable_admin_access_restriction",
86
  "section": "section_enable_plugin_feature_admin_access_restriction",
87
+ "advanced": true,
88
  "default": "Y",
89
  "type": "checkbox",
90
  "link_info": "https://shsec.io/40",
108
  {
109
  "key": "sec_admin_users",
110
  "section": "section_admin_access_restriction_settings",
111
+ "advanced": true,
112
  "sensitive": true,
113
  "premium": true,
114
  "default": [],
122
  {
123
  "key": "admin_access_timeout",
124
  "section": "section_admin_access_restriction_settings",
125
+ "advanced": true,
126
  "default": 30,
127
  "type": "integer",
128
  "min": 1,
135
  {
136
  "key": "allow_email_override",
137
  "section": "section_admin_access_restriction_settings",
138
+ "advanced": true,
139
  "default": "Y",
140
  "type": "checkbox",
141
  "link_info": "https://shsec.io/gf",
158
  {
159
  "key": "admin_access_restrict_admin_users",
160
  "section": "section_admin_access_restriction_areas",
161
+ "advanced": true,
162
  "default": "N",
163
  "type": "checkbox",
164
  "link_info": "https://shsec.io/a0",
170
  {
171
  "key": "admin_access_restrict_plugins",
172
  "section": "section_admin_access_restriction_areas",
173
+ "advanced": true,
174
  "type": "multiple_select",
175
  "default": [],
176
  "value_options": [
199
  {
200
  "key": "admin_access_restrict_themes",
201
  "section": "section_admin_access_restriction_areas",
202
+ "advanced": true,
203
  "type": "multiple_select",
204
  "default": [],
205
  "value_options": [
232
  {
233
  "key": "admin_access_restrict_posts",
234
  "section": "section_admin_access_restriction_areas",
235
+ "advanced": true,
236
  "type": "multiple_select",
237
  "default": [],
238
  "value_options": [
284
  "type": "checkbox",
285
  "link_info": "",
286
  "link_blog": "",
287
+ "name": "Replace Plugin Badge",
288
+ "summary": "Replace Plugin Badge URL and Images",
289
+ "description": "When using the plugin badge, replace the URL and link with your Whitelabel settings."
290
  },
291
  {
292
  "key": "wl_pluginnamemain",
src/config/feature-audit_trail.php CHANGED
@@ -16,19 +16,26 @@
16
  "run_if_wpcli": true,
17
  "order": 110
18
  },
19
- "sections": [
20
  {
21
- "slug": "section_enable_audit_contexts",
22
- "primary": true,
23
- "title": "Enable Audit Contexts",
24
- "title_short": "Audit Contexts",
25
- "summary": [
26
- "Purpose - Specify which types of actions on your site are logged.",
27
- "Recommendation - These settings are dependent on your requirements."
28
- ]
29
- },
 
 
 
 
 
 
30
  {
31
  "slug": "section_audit_trail_options",
 
32
  "title": "Audit Trail Options",
33
  "title_short": "Options",
34
  "summary": [
@@ -64,6 +71,7 @@
64
  {
65
  "key": "enable_audit_trail",
66
  "section": "section_enable_plugin_feature_audit_trail",
 
67
  "default": "Y",
68
  "type": "checkbox",
69
  "link_info": "https://shsec.io/5p",
@@ -97,83 +105,6 @@
97
  "summary": "Maximum Audit Trail Length To Keep",
98
  "description": "Automatically remove any audit trail entries when this limit is exceeded."
99
  },
100
- {
101
- "key": "enable_audit_context_users",
102
- "section": "section_enable_audit_contexts",
103
- "default": "Y",
104
- "type": "checkbox",
105
- "link_info": "https://shsec.io/a3",
106
- "link_blog": "https://shsec.io/a1",
107
- "name": "Users And Logins",
108
- "summary": "Enable Audit Context - Users And Logins",
109
- "description": "When this context is enabled, the audit trail will track activity relating to: Users And Logins"
110
- },
111
- {
112
- "key": "enable_audit_context_plugins",
113
- "section": "section_enable_audit_contexts",
114
- "default": "Y",
115
- "type": "checkbox",
116
- "link_info": "https://shsec.io/a3",
117
- "link_blog": "https://shsec.io/a1",
118
- "name": "Plugins",
119
- "summary": "Enable Audit Context - Plugins",
120
- "description": "When this context is enabled, the audit trail will track activity relating to: WordPress Plugins"
121
- },
122
- {
123
- "key": "enable_audit_context_themes",
124
- "section": "section_enable_audit_contexts",
125
- "default": "Y",
126
- "type": "checkbox",
127
- "link_info": "https://shsec.io/a3",
128
- "link_blog": "https://shsec.io/a1",
129
- "name": "Themes",
130
- "summary": "Enable Audit Context - Themes",
131
- "description": "When this context is enabled, the audit trail will track activity relating to: WordPress Themes"
132
- },
133
- {
134
- "key": "enable_audit_context_posts",
135
- "section": "section_enable_audit_contexts",
136
- "default": "Y",
137
- "type": "checkbox",
138
- "link_info": "https://shsec.io/a3",
139
- "link_blog": "https://shsec.io/a1",
140
- "name": "Posts And Pages",
141
- "summary": "Enable Audit Context - Posts And Pages",
142
- "description": "When this context is enabled, the audit trail will track activity relating to: Editing and publishing of posts and pages"
143
- },
144
- {
145
- "key": "enable_audit_context_wordpress",
146
- "section": "section_enable_audit_contexts",
147
- "default": "Y",
148
- "type": "checkbox",
149
- "link_info": "https://shsec.io/a3",
150
- "link_blog": "https://shsec.io/a1",
151
- "name": "WordPress And Settings",
152
- "summary": "Enable Audit Context - WordPress And Settings",
153
- "description": "When this context is enabled, the audit trail will track activity relating to: WordPress upgrades and changes to particular WordPress settings"
154
- },
155
- {
156
- "key": "enable_audit_context_emails",
157
- "section": "section_enable_audit_contexts",
158
- "default": "Y",
159
- "type": "checkbox",
160
- "link_info": "https://shsec.io/a3",
161
- "link_blog": "https://shsec.io/a1",
162
- "name": "Emails",
163
- "summary": "Enable Audit Context - Emails",
164
- "description": "When this context is enabled, the audit trail will track activity relating to: Email Sending"
165
- },
166
- {
167
- "key": "enable_audit_context_wpsf",
168
- "section": "section_enable_audit_contexts",
169
- "default": "Y",
170
- "type": "checkbox",
171
- "link_info": "https://shsec.io/a4",
172
- "link_blog": "https://shsec.io/a1",
173
- "name": "Shield",
174
- "summary": "Enable Audit Context - Shield",
175
- "description": "When this context is enabled, the audit trail will track activity relating to: Shield"
176
- },
177
  {
178
  "key": "enable_change_tracking",
179
  "section": "section_change_tracking",
@@ -232,12 +163,12 @@
232
  }
233
  ],
234
  "definitions": {
235
- "db_classes": {
236
  "audit": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\AuditTrail\\Handler"
237
  },
238
- "audit_trail_free_max_entries": 100,
239
- "audit_trail_table_name": "audit_trail",
240
- "audit_trail_table_columns": {
241
  "rid": "varchar(10) NOT NULL DEFAULT '' COMMENT 'Request ID'",
242
  "ip": "varchar(40) NOT NULL DEFAULT 0 COMMENT 'Visitor IP Address'",
243
  "wp_username": "varchar(255) NOT NULL DEFAULT '-' COMMENT 'WP User'",
@@ -252,15 +183,15 @@
252
  "audittrail_table_timestamp_columns": {
253
  "updated_at": "Updated"
254
  },
255
- "table_name_changetracking": "changetracking",
256
- "table_columns_changetracking": [
257
  "id",
258
  "data",
259
  "meta",
260
  "created_at",
261
  "deleted_at"
262
  ],
263
- "events": {
264
  "plugin_activated": {
265
  "context": "plugins",
266
  "audit_multiple": true
@@ -272,12 +203,18 @@
272
  "plugin_file_edited": {
273
  "context": "plugins"
274
  },
 
 
 
275
  "theme_activated": {
276
  "context": "themes"
277
  },
278
  "theme_file_edited": {
279
  "context": "themes"
280
  },
 
 
 
281
  "core_updated": {
282
  "context": "wordpress"
283
  },
16
  "run_if_wpcli": true,
17
  "order": 110
18
  },
19
+ "menu_items": [
20
  {
21
+ "title": "Audit Trail",
22
+ "slug": "audit-redirect",
23
+ "callback": ""
24
+ }
25
+ ],
26
+ "custom_redirects": [
27
+ {
28
+ "source_mod_page": "audit-redirect",
29
+ "target_mod_page": "insights",
30
+ "query_args": {
31
+ "inav": "audit"
32
+ }
33
+ }
34
+ ],
35
+ "sections": [
36
  {
37
  "slug": "section_audit_trail_options",
38
+ "primary": true,
39
  "title": "Audit Trail Options",
40
  "title_short": "Options",
41
  "summary": [
71
  {
72
  "key": "enable_audit_trail",
73
  "section": "section_enable_plugin_feature_audit_trail",
74
+ "advanced": true,
75
  "default": "Y",
76
  "type": "checkbox",
77
  "link_info": "https://shsec.io/5p",
105
  "summary": "Maximum Audit Trail Length To Keep",
106
  "description": "Automatically remove any audit trail entries when this limit is exceeded."
107
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  {
109
  "key": "enable_change_tracking",
110
  "section": "section_change_tracking",
163
  }
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",
171
+ "audit_trail_table_columns": {
172
  "rid": "varchar(10) NOT NULL DEFAULT '' COMMENT 'Request ID'",
173
  "ip": "varchar(40) NOT NULL DEFAULT 0 COMMENT 'Visitor IP Address'",
174
  "wp_username": "varchar(255) NOT NULL DEFAULT '-' COMMENT 'WP User'",
183
  "audittrail_table_timestamp_columns": {
184
  "updated_at": "Updated"
185
  },
186
+ "table_name_changetracking": "changetracking",
187
+ "table_columns_changetracking": [
188
  "id",
189
  "data",
190
  "meta",
191
  "created_at",
192
  "deleted_at"
193
  ],
194
+ "events": {
195
  "plugin_activated": {
196
  "context": "plugins",
197
  "audit_multiple": true
203
  "plugin_file_edited": {
204
  "context": "plugins"
205
  },
206
+ "plugin_upgraded": {
207
+ "context": "plugins"
208
+ },
209
  "theme_activated": {
210
  "context": "themes"
211
  },
212
  "theme_file_edited": {
213
  "context": "themes"
214
  },
215
+ "theme_upgraded": {
216
+ "context": "themes"
217
+ },
218
  "core_updated": {
219
  "context": "wordpress"
220
  },
src/config/feature-autoupdates.php CHANGED
@@ -51,6 +51,7 @@
51
  {
52
  "key": "enable_autoupdates",
53
  "section": "section_enable_plugin_feature_automatic_updates_control",
 
54
  "default": "Y",
55
  "type": "checkbox",
56
  "link_info": "https://shsec.io/3w",
@@ -62,6 +63,7 @@
62
  {
63
  "key": "enable_autoupdate_disable_all",
64
  "section": "section_automatic_updates_for_wordpress_components",
 
65
  "default": "N",
66
  "type": "checkbox",
67
  "link_info": "https://shsec.io/3v",
@@ -109,6 +111,7 @@
109
  {
110
  "key": "enable_autoupdate_themes",
111
  "section": "section_automatic_updates_for_wordpress_components",
 
112
  "default": "N",
113
  "type": "checkbox",
114
  "link_info": "",
@@ -132,6 +135,7 @@
132
  {
133
  "key": "autoupdate_plugin_self",
134
  "section": "section_options",
 
135
  "default": "auto",
136
  "type": "select",
137
  "value_options": [
@@ -157,7 +161,7 @@
157
  {
158
  "key": "enable_upgrade_notification_email",
159
  "section": "section_options",
160
- "default": "Y",
161
  "type": "checkbox",
162
  "link_info": "",
163
  "link_blog": "",
51
  {
52
  "key": "enable_autoupdates",
53
  "section": "section_enable_plugin_feature_automatic_updates_control",
54
+ "advanced": true,
55
  "default": "Y",
56
  "type": "checkbox",
57
  "link_info": "https://shsec.io/3w",
63
  {
64
  "key": "enable_autoupdate_disable_all",
65
  "section": "section_automatic_updates_for_wordpress_components",
66
+ "advanced": true,
67
  "default": "N",
68
  "type": "checkbox",
69
  "link_info": "https://shsec.io/3v",
111
  {
112
  "key": "enable_autoupdate_themes",
113
  "section": "section_automatic_updates_for_wordpress_components",
114
+ "advanced": true,
115
  "default": "N",
116
  "type": "checkbox",
117
  "link_info": "",
135
  {
136
  "key": "autoupdate_plugin_self",
137
  "section": "section_options",
138
+ "advanced": true,
139
  "default": "auto",
140
  "type": "select",
141
  "value_options": [
161
  {
162
  "key": "enable_upgrade_notification_email",
163
  "section": "section_options",
164
+ "default": "N",
165
  "type": "checkbox",
166
  "link_info": "",
167
  "link_blog": "",
src/config/feature-comments_filter.php CHANGED
@@ -80,6 +80,7 @@
80
  {
81
  "key": "enable_comments_filter",
82
  "section": "section_enable_plugin_feature_spam_comments_protection_filter",
 
83
  "default": "Y",
84
  "type": "checkbox",
85
  "link_info": "https://shsec.io/3z",
80
  {
81
  "key": "enable_comments_filter",
82
  "section": "section_enable_plugin_feature_spam_comments_protection_filter",
83
+ "advanced": true,
84
  "default": "Y",
85
  "type": "checkbox",
86
  "link_info": "https://shsec.io/3z",
src/config/feature-firewall.php CHANGED
@@ -73,6 +73,7 @@
73
  {
74
  "key": "enable_firewall",
75
  "section": "section_enable_plugin_feature_wordpress_firewall",
 
76
  "default": "Y",
77
  "type": "checkbox",
78
  "link_info": "https://shsec.io/43",
@@ -84,6 +85,7 @@
84
  {
85
  "key": "include_cookie_checks",
86
  "section": "section_firewall_blocking_options",
 
87
  "default": "N",
88
  "type": "checkbox",
89
  "link_info": "",
@@ -161,6 +163,7 @@
161
  {
162
  "key": "block_leading_schema",
163
  "section": "section_firewall_blocking_options",
 
164
  "default": "N",
165
  "type": "checkbox",
166
  "link_info": "",
@@ -172,6 +175,7 @@
172
  {
173
  "key": "block_aggressive",
174
  "section": "section_firewall_blocking_options",
 
175
  "default": "N",
176
  "type": "checkbox",
177
  "link_info": "",
@@ -183,6 +187,7 @@
183
  {
184
  "key": "block_response",
185
  "section": "section_choose_firewall_block_response",
 
186
  "default": "redirect_die_message",
187
  "type": "select",
188
  "value_options": [
@@ -223,6 +228,7 @@
223
  {
224
  "key": "page_params_whitelist",
225
  "section": "section_whitelist",
 
226
  "default": "",
227
  "type": "comma_separated_lists",
228
  "link_info": "https://shsec.io/2a",
@@ -234,6 +240,7 @@
234
  {
235
  "key": "whitelist_admins",
236
  "section": "section_whitelist",
 
237
  "default": "N",
238
  "type": "checkbox",
239
  "link_info": "",
73
  {
74
  "key": "enable_firewall",
75
  "section": "section_enable_plugin_feature_wordpress_firewall",
76
+ "advanced": true,
77
  "default": "Y",
78
  "type": "checkbox",
79
  "link_info": "https://shsec.io/43",
85
  {
86
  "key": "include_cookie_checks",
87
  "section": "section_firewall_blocking_options",
88
+ "advanced": true,
89
  "default": "N",
90
  "type": "checkbox",
91
  "link_info": "",
163
  {
164
  "key": "block_leading_schema",
165
  "section": "section_firewall_blocking_options",
166
+ "advanced": true,
167
  "default": "N",
168
  "type": "checkbox",
169
  "link_info": "",
175
  {
176
  "key": "block_aggressive",
177
  "section": "section_firewall_blocking_options",
178
+ "advanced": true,
179
  "default": "N",
180
  "type": "checkbox",
181
  "link_info": "",
187
  {
188
  "key": "block_response",
189
  "section": "section_choose_firewall_block_response",
190
+ "advanced": true,
191
  "default": "redirect_die_message",
192
  "type": "select",
193
  "value_options": [
228
  {
229
  "key": "page_params_whitelist",
230
  "section": "section_whitelist",
231
+ "advanced": true,
232
  "default": "",
233
  "type": "comma_separated_lists",
234
  "link_info": "https://shsec.io/2a",
240
  {
241
  "key": "whitelist_admins",
242
  "section": "section_whitelist",
243
+ "advanced": true,
244
  "default": "N",
245
  "type": "checkbox",
246
  "link_info": "",
src/config/feature-hack_protect.php CHANGED
@@ -99,6 +99,7 @@
99
  {
100
  "key": "enable_hack_protect",
101
  "section": "section_enable_plugin_feature_hack_protection_tools",
 
102
  "default": "Y",
103
  "type": "checkbox",
104
  "link_info": "https://shsec.io/wpsf38",
@@ -309,6 +310,7 @@
309
  {
310
  "key": "ufc_scan_uploads",
311
  "section": "section_scan_ufc",
 
312
  "default": "N",
313
  "type": "checkbox",
314
  "link_info": "https://shsec.io/he",
@@ -320,6 +322,7 @@
320
  {
321
  "key": "ufc_exclusions",
322
  "section": "section_scan_ufc",
 
323
  "default": [
324
  "error_log",
325
  "php_error_log",
99
  {
100
  "key": "enable_hack_protect",
101
  "section": "section_enable_plugin_feature_hack_protection_tools",
102
+ "advanced": true,
103
  "default": "Y",
104
  "type": "checkbox",
105
  "link_info": "https://shsec.io/wpsf38",
310
  {
311
  "key": "ufc_scan_uploads",
312
  "section": "section_scan_ufc",
313
+ "advanced": true,
314
  "default": "N",
315
  "type": "checkbox",
316
  "link_info": "https://shsec.io/he",
322
  {
323
  "key": "ufc_exclusions",
324
  "section": "section_scan_ufc",
325
+ "advanced": true,
326
  "default": [
327
  "error_log",
328
  "php_error_log",
src/config/feature-headers.php CHANGED
@@ -54,6 +54,7 @@
54
  {
55
  "key": "enable_headers",
56
  "section": "section_enable_plugin_feature_headers",
 
57
  "default": "Y",
58
  "type": "checkbox",
59
  "link_info": "https://shsec.io/aj",
54
  {
55
  "key": "enable_headers",
56
  "section": "section_enable_plugin_feature_headers",
57
+ "advanced": true,
58
  "default": "Y",
59
  "type": "checkbox",
60
  "link_info": "https://shsec.io/aj",
src/config/feature-insights.php CHANGED
@@ -21,11 +21,6 @@
21
  "wpcli": {
22
  "enabled": false
23
  },
24
- "requirements": {
25
- "php": {
26
- "version": "5.4"
27
- }
28
- },
29
  "sections": [
30
  {
31
  "slug": "section_non_ui",
21
  "wpcli": {
22
  "enabled": false
23
  },
 
 
 
 
 
24
  "sections": [
25
  {
26
  "slug": "section_non_ui",
src/config/feature-integrations.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "slug": "integrations",
3
+ "properties": {
4
+ "slug": "integrations",
5
+ "storage_key": "integrations",
6
+ "name": "Integrations",
7
+ "menu_title": "Integrations",
8
+ "show_module_options": true,
9
+ "show_module_menu_item": false,
10
+ "auto_enabled": true,
11
+ "show_central": true,
12
+ "premium": false,
13
+ "access_restricted": true,
14
+ "run_if_whitelisted": true,
15
+ "run_if_wpcli": true,
16
+ "run_if_verified_bot": true,
17
+ "skip_processor": false,
18
+ "tracking_exclude": false
19
+ },
20
+ "wpcli": {
21
+ "enabled": true
22
+ },
23
+ "sections": [
24
+ {
25
+ "slug": "section_integrations",
26
+ "primary": true,
27
+ "title": "Integrations",
28
+ "title_short": "Integrations"
29
+ },
30
+ {
31
+ "slug": "section_non_ui",
32
+ "hidden": true
33
+ }
34
+ ],
35
+ "options": [
36
+ {
37
+ "key": "enable_mainwp",
38
+ "section": "section_integrations",
39
+ "default": "Y",
40
+ "type": "checkbox",
41
+ "link_info": "https://shsec.io/ir",
42
+ "link_blog": "",
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
+ }
src/config/feature-ips.php CHANGED
@@ -16,6 +16,22 @@
16
  "run_if_wpcli": false,
17
  "order": 100
18
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  "admin_notices": {
20
  "visitor-whitelisted": {
21
  "id": "visitor-whitelisted",
@@ -111,6 +127,7 @@
111
  {
112
  "key": "enable_ips",
113
  "section": "section_enable_plugin_feature_ips",
 
114
  "default": "Y",
115
  "type": "checkbox",
116
  "link_info": "https://shsec.io/ea",
@@ -133,6 +150,7 @@
133
  {
134
  "key": "auto_expire",
135
  "section": "section_auto_black_list",
 
136
  "default": "day",
137
  "type": "select",
138
  "value_options": [
@@ -166,6 +184,7 @@
166
  {
167
  "key": "user_auto_recover",
168
  "section": "section_auto_black_list",
 
169
  "premium": true,
170
  "default": [],
171
  "type": "multiple_select",
@@ -188,6 +207,7 @@
188
  {
189
  "key": "request_whitelist",
190
  "section": "section_auto_black_list",
 
191
  "premium": true,
192
  "default": [],
193
  "type": "array",
16
  "run_if_wpcli": false,
17
  "order": 100
18
  },
19
+ "menu_items": [
20
+ {
21
+ "title": "IP Lists",
22
+ "slug": "ips-redirect",
23
+ "callback": ""
24
+ }
25
+ ],
26
+ "custom_redirects": [
27
+ {
28
+ "source_mod_page": "ips-redirect",
29
+ "target_mod_page": "insights",
30
+ "query_args": {
31
+ "inav": "ips"
32
+ }
33
+ }
34
+ ],
35
  "admin_notices": {
36
  "visitor-whitelisted": {
37
  "id": "visitor-whitelisted",
127
  {
128
  "key": "enable_ips",
129
  "section": "section_enable_plugin_feature_ips",
130
+ "advanced": true,
131
  "default": "Y",
132
  "type": "checkbox",
133
  "link_info": "https://shsec.io/ea",
150
  {
151
  "key": "auto_expire",
152
  "section": "section_auto_black_list",
153
+ "advanced": true,
154
  "default": "day",
155
  "type": "select",
156
  "value_options": [
184
  {
185
  "key": "user_auto_recover",
186
  "section": "section_auto_black_list",
187
+ "advanced": true,
188
  "premium": true,
189
  "default": [],
190
  "type": "multiple_select",
207
  {
208
  "key": "request_whitelist",
209
  "section": "section_auto_black_list",
210
+ "advanced": true,
211
  "premium": true,
212
  "default": [],
213
  "type": "array",
src/config/feature-lockdown.php CHANGED
@@ -63,6 +63,7 @@
63
  {
64
  "key": "enable_lockdown",
65
  "section": "section_enable_plugin_feature_wordpress_lockdown",
 
66
  "default": "Y",
67
  "type": "checkbox",
68
  "link_info": "https://shsec.io/4r",
@@ -85,6 +86,7 @@
85
  {
86
  "key": "disable_anonymous_restapi",
87
  "section": "section_apixml",
 
88
  "default": "N",
89
  "type": "checkbox",
90
  "link_info": "",
63
  {
64
  "key": "enable_lockdown",
65
  "section": "section_enable_plugin_feature_wordpress_lockdown",
66
+ "advanced": true,
67
  "default": "Y",
68
  "type": "checkbox",
69
  "link_info": "https://shsec.io/4r",
86
  {
87
  "key": "disable_anonymous_restapi",
88
  "section": "section_apixml",
89
+ "advanced": true,
90
  "default": "N",
91
  "type": "checkbox",
92
  "link_info": "",
src/config/feature-login_protect.php CHANGED
@@ -114,6 +114,7 @@
114
  {
115
  "key": "enable_login_protect",
116
  "section": "section_enable_plugin_feature_login_protection",
 
117
  "default": "Y",
118
  "type": "checkbox",
119
  "link_info": "https://shsec.io/51",
@@ -125,6 +126,7 @@
125
  {
126
  "key": "rename_wplogin_path",
127
  "section": "section_rename_wplogin",
 
128
  "sensitive": true,
129
  "default": "",
130
  "type": "text",
@@ -195,6 +197,7 @@
195
  {
196
  "key": "two_factor_auth_user_roles",
197
  "section": "section_2fa_email",
 
198
  "type": "multiple_select",
199
  "default": [
200
  "contributor",
@@ -352,6 +355,7 @@
352
  {
353
  "key": "antibot_form_ids",
354
  "section": "section_brute_force_login_protection",
 
355
  "premium": true,
356
  "type": "array",
357
  "default": [
114
  {
115
  "key": "enable_login_protect",
116
  "section": "section_enable_plugin_feature_login_protection",
117
+ "advanced": true,
118
  "default": "Y",
119
  "type": "checkbox",
120
  "link_info": "https://shsec.io/51",
126
  {
127
  "key": "rename_wplogin_path",
128
  "section": "section_rename_wplogin",
129
+ "advanced": true,
130
  "sensitive": true,
131
  "default": "",
132
  "type": "text",
197
  {
198
  "key": "two_factor_auth_user_roles",
199
  "section": "section_2fa_email",
200
+ "advanced": true,
201
  "type": "multiple_select",
202
  "default": [
203
  "contributor",
355
  {
356
  "key": "antibot_form_ids",
357
  "section": "section_brute_force_login_protection",
358
+ "advanced": true,
359
  "premium": true,
360
  "type": "array",
361
  "default": [
src/config/feature-plugin.php CHANGED
@@ -59,14 +59,6 @@
59
  "can_dismiss": false,
60
  "type": "warning"
61
  },
62
- "cloudflare-apo": {
63
- "id": "cloudflare-apo",
64
- "schedule": "conditions",
65
- "valid_admin": true,
66
- "plugin_page_only": true,
67
- "can_dismiss": false,
68
- "type": "error"
69
- },
70
  "wizard_welcome": {
71
  "id": "wizard_welcome",
72
  "per_user": false,
@@ -115,6 +107,11 @@
115
  "title": "Import / Export",
116
  "title_short": "Import / Export"
117
  },
 
 
 
 
 
118
  {
119
  "slug": "section_global_security_options",
120
  "title": "Global Plugin Security Options",
@@ -148,9 +145,21 @@
148
  "summary": "Permit Anonymous Usage Information Gathering",
149
  "description": "Allows us to gather information on statistics and features in-use across our client installations. This information is strictly anonymous and contains no personally, or otherwise, identifiable data."
150
  },
 
 
 
 
 
 
 
 
 
 
 
151
  {
152
  "key": "visitor_address_source",
153
  "section": "section_defaults",
 
154
  "sensitive": false,
155
  "type": "select",
156
  "default": "AUTO_DETECT_IP",
@@ -243,6 +252,7 @@
243
  {
244
  "key": "enable_wpcli",
245
  "section": "section_general_plugin_options",
 
246
  "premium": true,
247
  "default": "Y",
248
  "type": "checkbox",
@@ -266,6 +276,7 @@
266
  {
267
  "key": "importexport_enable",
268
  "section": "section_importexport",
 
269
  "premium": true,
270
  "default": "Y",
271
  "type": "checkbox",
@@ -278,6 +289,7 @@
278
  {
279
  "key": "importexport_masterurl",
280
  "section": "section_importexport",
 
281
  "default": "",
282
  "type": "text",
283
  "link_info": "",
@@ -289,6 +301,7 @@
289
  {
290
  "key": "importexport_whitelist",
291
  "section": "section_importexport",
 
292
  "transferable": false,
293
  "sensitive": true,
294
  "default": [],
@@ -302,6 +315,7 @@
302
  {
303
  "key": "importexport_whitelist_notify",
304
  "section": "section_importexport",
 
305
  "sensitive": true,
306
  "default": "N",
307
  "type": "checkbox",
@@ -314,6 +328,7 @@
314
  {
315
  "key": "importexport_secretkey",
316
  "section": "section_importexport",
 
317
  "transferable": false,
318
  "sensitive": true,
319
  "default": "",
@@ -338,6 +353,7 @@
338
  {
339
  "key": "locale_override",
340
  "section": "section_general_plugin_options",
 
341
  "default": "",
342
  "type": "text",
343
  "link_info": "https://icwp.io/il",
@@ -520,11 +536,11 @@
520
  "db_notes_name": "notes",
521
  "db_notes_table_columns": {
522
  "wp_username": "varchar(255) NOT NULL DEFAULT 'unknown'",
523
- "note": "TEXT"
524
  },
525
  "geoip_table_name": "geoip",
526
  "geoip_table_columns": {
527
- "ip": "varbinary(16) DEFAULT NULL COMMENT 'IP Address'",
528
  "meta": "TEXT"
529
  },
530
  "active_plugin_features": [
@@ -535,11 +551,13 @@
535
  },
536
  {
537
  "slug": "admin_access_restriction",
 
538
  "load_priority": 11
539
  },
540
  {
541
  "slug": "ips",
542
- "load_priority": 15
 
543
  },
544
  {
545
  "slug": "audit_trail",
@@ -547,12 +565,12 @@
547
  "hidden": false
548
  },
549
  {
550
- "slug": "hack_protect"
 
551
  },
552
  {
553
  "slug": "traffic",
554
- "load_priority": 12,
555
- "min_php": "5.4"
556
  },
557
  {
558
  "slug": "firewall",
@@ -560,7 +578,8 @@
560
  },
561
  {
562
  "slug": "login_protect",
563
- "storage_key": "loginprotect"
 
564
  },
565
  {
566
  "slug": "user_management"
@@ -581,6 +600,10 @@
581
  "slug": "sessions",
582
  "load_priority": 5
583
  },
 
 
 
 
584
  {
585
  "slug": "license",
586
  "load_priority": 10
59
  "can_dismiss": false,
60
  "type": "warning"
61
  },
 
 
 
 
 
 
 
 
62
  "wizard_welcome": {
63
  "id": "wizard_welcome",
64
  "per_user": false,
107
  "title": "Import / Export",
108
  "title_short": "Import / Export"
109
  },
110
+ {
111
+ "slug": "section_integrations",
112
+ "title": "Integrations",
113
+ "title_short": "Integrations"
114
+ },
115
  {
116
  "slug": "section_global_security_options",
117
  "title": "Global Plugin Security Options",
145
  "summary": "Permit Anonymous Usage Information Gathering",
146
  "description": "Allows us to gather information on statistics and features in-use across our client installations. This information is strictly anonymous and contains no personally, or otherwise, identifiable data."
147
  },
148
+ {
149
+ "key": "show_advanced",
150
+ "section": "section_non_ui",
151
+ "default": "Y",
152
+ "type": "checkbox",
153
+ "link_info": "",
154
+ "link_blog": "",
155
+ "name": "Show All Options",
156
+ "summary": "Show All Options Including Those Marked As Advanced",
157
+ "description": "Shield hides advanced options from view to simplify display. Turn this option on to display advanced options at all times."
158
+ },
159
  {
160
  "key": "visitor_address_source",
161
  "section": "section_defaults",
162
+ "advanced": true,
163
  "sensitive": false,
164
  "type": "select",
165
  "default": "AUTO_DETECT_IP",
252
  {
253
  "key": "enable_wpcli",
254
  "section": "section_general_plugin_options",
255
+ "advanced": true,
256
  "premium": true,
257
  "default": "Y",
258
  "type": "checkbox",
276
  {
277
  "key": "importexport_enable",
278
  "section": "section_importexport",
279
+ "advanced": true,
280
  "premium": true,
281
  "default": "Y",
282
  "type": "checkbox",
289
  {
290
  "key": "importexport_masterurl",
291
  "section": "section_importexport",
292
+ "advanced": true,
293
  "default": "",
294
  "type": "text",
295
  "link_info": "",
301
  {
302
  "key": "importexport_whitelist",
303
  "section": "section_importexport",
304
+ "advanced": true,
305
  "transferable": false,
306
  "sensitive": true,
307
  "default": [],
315
  {
316
  "key": "importexport_whitelist_notify",
317
  "section": "section_importexport",
318
+ "advanced": true,
319
  "sensitive": true,
320
  "default": "N",
321
  "type": "checkbox",
328
  {
329
  "key": "importexport_secretkey",
330
  "section": "section_importexport",
331
+ "advanced": true,
332
  "transferable": false,
333
  "sensitive": true,
334
  "default": "",
353
  {
354
  "key": "locale_override",
355
  "section": "section_general_plugin_options",
356
+ "advanced": true,
357
  "default": "",
358
  "type": "text",
359
  "link_info": "https://icwp.io/il",
536
  "db_notes_name": "notes",
537
  "db_notes_table_columns": {
538
  "wp_username": "varchar(255) NOT NULL DEFAULT 'unknown'",
539
+ "note": "TEXT"
540
  },
541
  "geoip_table_name": "geoip",
542
  "geoip_table_columns": {
543
+ "ip": "varbinary(16) DEFAULT NULL COMMENT 'IP Address'",
544
  "meta": "TEXT"
545
  },
546
  "active_plugin_features": [
551
  },
552
  {
553
  "slug": "admin_access_restriction",
554
+ "namespace": "SecurityAdmin",
555
  "load_priority": 11
556
  },
557
  {
558
  "slug": "ips",
559
+ "load_priority": 15,
560
+ "namespace": "IPs"
561
  },
562
  {
563
  "slug": "audit_trail",
565
  "hidden": false
566
  },
567
  {
568
+ "slug": "hack_protect",
569
+ "namespace": "HackGuard"
570
  },
571
  {
572
  "slug": "traffic",
573
+ "load_priority": 12
 
574
  },
575
  {
576
  "slug": "firewall",
578
  },
579
  {
580
  "slug": "login_protect",
581
+ "storage_key": "loginprotect",
582
+ "namespace": "LoginGuard"
583
  },
584
  {
585
  "slug": "user_management"
600
  "slug": "sessions",
601
  "load_priority": 5
602
  },
603
+ {
604
+ "slug": "integrations",
605
+ "load_priority": 20
606
+ },
607
  {
608
  "slug": "license",
609
  "load_priority": 10
src/config/feature-reporting.php CHANGED
@@ -42,6 +42,7 @@
42
  {
43
  "key": "enable_reporting",
44
  "section": "section_enable_mod_reporting",
 
45
  "default": "Y",
46
  "type": "checkbox",
47
  "link_info": "https://shsec.io/hb",
42
  {
43
  "key": "enable_reporting",
44
  "section": "section_enable_mod_reporting",
45
+ "advanced": true,
46
  "default": "Y",
47
  "type": "checkbox",
48
  "link_info": "https://shsec.io/hb",
src/config/feature-traffic.php CHANGED
@@ -16,11 +16,22 @@
16
  "run_if_wpcli": false,
17
  "order": 110
18
  },
19
- "requirements": {
20
- "php": {
21
- "version": "5.4"
 
 
22
  }
23
- },
 
 
 
 
 
 
 
 
 
24
  "sections": [
25
  {
26
  "slug": "section_traffic_options",
@@ -59,6 +70,7 @@
59
  {
60
  "key": "enable_traffic",
61
  "section": "section_enable_plugin_feature_traffic",
 
62
  "default": "Y",
63
  "type": "checkbox",
64
  "link_info": "https://shsec.io/ed",
@@ -82,6 +94,7 @@
82
  "key": "type_exclusions",
83
  "section": "section_traffic_options",
84
  "type": "multiple_select",
 
85
  "default": [
86
  "logged_in",
87
  "cron",
@@ -127,6 +140,7 @@
127
  {
128
  "key": "custom_exclusions",
129
  "section": "section_traffic_options",
 
130
  "premium": true,
131
  "default": [],
132
  "type": "array",
@@ -139,6 +153,7 @@
139
  {
140
  "key": "auto_clean",
141
  "section": "section_traffic_options",
 
142
  "default": 3,
143
  "min": 1,
144
  "type": "integer",
@@ -151,6 +166,7 @@
151
  {
152
  "key": "max_entries",
153
  "section": "section_traffic_options",
 
154
  "premium": true,
155
  "default": 1000,
156
  "min": 0,
@@ -199,18 +215,18 @@
199
  }
200
  ],
201
  "definitions": {
202
- "db_classes": {
203
  "traffic": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\Traffic\\Handler"
204
  },
205
  "traffic_table_name": "traffic",
206
  "traffic_table_columns": {
207
- "rid": "varchar(10) NOT NULL DEFAULT '' COMMENT 'Request ID'",
208
- "uid": "int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'User ID'",
209
- "ip": "varbinary(16) DEFAULT NULL COMMENT 'Visitor IP Address'",
210
- "path": "text NOT NULL DEFAULT '' COMMENT 'Request Path or URI'",
211
- "code": "int(5) NOT NULL DEFAULT '200' COMMENT 'HTTP Response Code'",
212
- "verb": "varchar(10) NOT NULL DEFAULT 'get' COMMENT 'HTTP Method'",
213
- "ua": "text COMMENT 'Browser User Agent String'",
214
  "trans": "tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Trangression'"
215
  },
216
  "events": {
16
  "run_if_wpcli": false,
17
  "order": 110
18
  },
19
+ "menu_items": [
20
+ {
21
+ "title": "Traffic Log",
22
+ "slug": "traffic-redirect",
23
+ "callback": ""
24
  }
25
+ ],
26
+ "custom_redirects": [
27
+ {
28
+ "source_mod_page": "traffic-redirect",
29
+ "target_mod_page": "insights",
30
+ "query_args": {
31
+ "inav": "traffic"
32
+ }
33
+ }
34
+ ],
35
  "sections": [
36
  {
37
  "slug": "section_traffic_options",
70
  {
71
  "key": "enable_traffic",
72
  "section": "section_enable_plugin_feature_traffic",
73
+ "advanced": true,
74
  "default": "Y",
75
  "type": "checkbox",
76
  "link_info": "https://shsec.io/ed",
94
  "key": "type_exclusions",
95
  "section": "section_traffic_options",
96
  "type": "multiple_select",
97
+ "advanced": true,
98
  "default": [
99
  "logged_in",
100
  "cron",
140
  {
141
  "key": "custom_exclusions",
142
  "section": "section_traffic_options",
143
+ "advanced": true,
144
  "premium": true,
145
  "default": [],
146
  "type": "array",
153
  {
154
  "key": "auto_clean",
155
  "section": "section_traffic_options",
156
+ "advanced": true,
157
  "default": 3,
158
  "min": 1,
159
  "type": "integer",
166
  {
167
  "key": "max_entries",
168
  "section": "section_traffic_options",
169
+ "advanced": true,
170
  "premium": true,
171
  "default": 1000,
172
  "min": 0,
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'",
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
  "events": {
src/config/feature-user_management.php CHANGED
@@ -84,6 +84,7 @@
84
  {
85
  "key": "enable_user_management",
86
  "section": "section_enable_plugin_feature_user_accounts_management",
 
87
  "default": "Y",
88
  "type": "checkbox",
89
  "link_info": "https://shsec.io/e3",
@@ -144,6 +145,7 @@
144
  {
145
  "key": "session_lock_location",
146
  "section": "section_user_session_management",
 
147
  "default": "N",
148
  "type": "checkbox",
149
  "link_info": "",
84
  {
85
  "key": "enable_user_management",
86
  "section": "section_enable_plugin_feature_user_accounts_management",
87
+ "advanced": true,
88
  "default": "Y",
89
  "type": "checkbox",
90
  "link_info": "https://shsec.io/e3",
145
  {
146
  "key": "session_lock_location",
147
  "section": "section_user_session_management",
148
+ "advanced": true,
149
  "default": "N",
150
  "type": "checkbox",
151
  "link_info": "",
src/features/admin_access_restriction.php CHANGED
@@ -4,6 +4,9 @@ use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin;
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
 
 
 
7
  class ICWP_WPSF_FeatureHandler_AdminAccessRestriction extends ICWP_WPSF_FeatureHandler_BaseWpsf {
8
 
9
  const HASH_DELETE = '32f68a60cef40faedbc6af20298c1a1e';
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin;
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
7
+ /**
8
+ * @deprecated 10.1
9
+ */
10
  class ICWP_WPSF_FeatureHandler_AdminAccessRestriction extends ICWP_WPSF_FeatureHandler_BaseWpsf {
11
 
12
  const HASH_DELETE = '32f68a60cef40faedbc6af20298c1a1e';
src/features/audit_trail.php CHANGED
@@ -3,12 +3,12 @@
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Services\Services;
5
 
 
 
 
6
  class ICWP_WPSF_FeatureHandler_AuditTrail extends ICWP_WPSF_FeatureHandler_BaseWpsf {
7
 
8
- /**
9
- * @return Shield\Databases\AuditTrail\Handler
10
- */
11
- public function getDbHandler_AuditTrail() {
12
  return $this->getDbH( 'audit' );
13
  }
14
 
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Services\Services;
5
 
6
+ /**
7
+ * @deprecated 10.1
8
+ */
9
  class ICWP_WPSF_FeatureHandler_AuditTrail extends ICWP_WPSF_FeatureHandler_BaseWpsf {
10
 
11
+ public function getDbHandler_AuditTrail() :Shield\Databases\AuditTrail\Handler {
 
 
 
12
  return $this->getDbH( 'audit' );
13
  }
14
 
src/features/autoupdates.php CHANGED
@@ -2,6 +2,9 @@
2
 
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
 
 
 
 
5
  class ICWP_WPSF_FeatureHandler_Autoupdates extends ICWP_WPSF_FeatureHandler_BaseWpsf {
6
 
7
  /**
2
 
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
 
5
+ /**
6
+ * @deprecated 10.1
7
+ */
8
  class ICWP_WPSF_FeatureHandler_Autoupdates extends ICWP_WPSF_FeatureHandler_BaseWpsf {
9
 
10
  /**
src/features/base.php CHANGED
@@ -27,6 +27,7 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
27
 
28
  /**
29
  * @var ICWP_WPSF_FeatureHandler_Email
 
30
  */
31
  private static $oEmailHandler;
32
 
@@ -45,11 +46,6 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
45
  */
46
  private $oReporting;
47
 
48
- /**
49
- * @var Shield\Modules\Base\Strings
50
- */
51
- private $oStrings;
52
-
53
  /**
54
  * @var Shield\Modules\Base\UI
55
  */
@@ -119,20 +115,6 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
119
  add_action( $con->prefix( 'daily_cron' ), [ $this, 'runDailyCron' ] );
120
  add_action( $con->prefix( 'hourly_cron' ), [ $this, 'runHourlyCron' ] );
121
 
122
- // supply supported events for this module
123
- add_filter( $con->prefix( 'get_all_events' ), function ( $aEvents ) {
124
- return array_merge(
125
- is_array( $aEvents ) ? $aEvents : [],
126
- array_map(
127
- function ( $aEvt ) {
128
- $aEvt[ 'context' ] = $this->getSlug();
129
- return $aEvt;
130
- },
131
- is_array( $this->getDef( 'events' ) ) ? $this->getDef( 'events' ) : []
132
- )
133
- );
134
- } );
135
-
136
  add_action( 'admin_enqueue_scripts', [ $this, 'onWpEnqueueAdminJs' ], 100 );
137
 
138
  if ( is_admin() || is_network_admin() ) {
@@ -223,7 +205,7 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
223
  * @return false|Shield\Modules\Base\Upgrade|mixed
224
  */
225
  public function getUpgradeHandler() {
226
- return $this->loadClass( 'Upgrade' );
227
  }
228
 
229
  /**
@@ -272,10 +254,7 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
272
  return array_merge( $aAdminNotices, $this->getOptions()->getAdminNotices() );
273
  }
274
 
275
- /**
276
- * @return bool
277
- */
278
- private function verifyModuleMeetRequirements() {
279
  $bMeetsReqs = true;
280
 
281
  $aPhpReqs = $this->getOptions()->getFeatureRequirement( 'php' );
@@ -313,7 +292,7 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
313
  $this->doExecuteProcessor();
314
  }
315
  }
316
- catch ( \Exception $oE ) {
317
  }
318
  }
319
 
@@ -330,48 +309,6 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
330
  }
331
 
332
  public function onWpInit() {
333
-
334
- $sShieldAction = $this->getCon()->getShieldAction();
335
- if ( !empty( $sShieldAction ) ) {
336
- do_action( $this->getCon()->prefix( 'shield_action' ), $sShieldAction );
337
- }
338
-
339
- if ( Services::Data()->getPhpVersionIsAtLeast( '7.0' ) ) {
340
- add_action( 'cli_init', function () {
341
- try {
342
- $this->getWpCli()->execute();
343
- }
344
- catch ( \Exception $oE ) {
345
- }
346
- } );
347
- }
348
-
349
- if ( $this->isModuleRequest() ) {
350
-
351
- if ( Services::WpGeneral()->isAjax() ) {
352
- $this->loadAjaxHandler();
353
- }
354
- else {
355
- try {
356
- if ( $this->verifyModActionRequest() ) {
357
- $this->handleModAction( Services::Request()->request( 'exec' ) );
358
- }
359
- }
360
- catch ( \Exception $oE ) {
361
- wp_nonce_ays( '' );
362
- }
363
- }
364
- }
365
-
366
- $this->runWizards();
367
-
368
- // GDPR
369
- if ( $this->isPremium() ) {
370
- add_filter( $this->prefix( 'wpPrivacyExport' ), [ $this, 'onWpPrivacyExport' ], 10, 3 );
371
- add_filter( $this->prefix( 'wpPrivacyErase' ), [ $this, 'onWpPrivacyErase' ], 10, 3 );
372
- }
373
-
374
- $this->loadDebug();
375
  }
376
 
377
  /**
@@ -395,17 +332,24 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
395
  */
396
  protected function loadProcessor() {
397
  if ( !isset( $this->oProcessor ) ) {
398
- $sClassName = $this->getProcessorClassName();
399
- if ( !class_exists( $sClassName ) ) {
 
 
 
 
 
 
400
  return null;
401
  }
402
- $this->oProcessor = new $sClassName( $this );
403
  }
404
  return $this->oProcessor;
405
  }
406
 
407
  /**
408
- * Override this and adapt per feature
 
409
  */
410
  protected function getProcessorClassName() :string {
411
  return implode( '_',
@@ -431,10 +375,7 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
431
  );
432
  }
433
 
434
- /**
435
- * @return bool
436
- */
437
- public function isUpgrading() {
438
  return $this->getCon()->getIsRebuildOptionsFromFile() || $this->getOptions()->getRebuildFromFile();
439
  }
440
 
@@ -451,10 +392,7 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
451
  }
452
  }
453
 
454
- /**
455
- * @return string
456
- */
457
- public function getOptionsStorageKey() {
458
  return $this->getCon()->prefixOption( $this->sOptionsStoreKey ).'_options';
459
  }
460
 
@@ -483,19 +421,15 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
483
  return add_query_arg( $aActionNonce, $this->getUrl_AdminPage() );
484
  }
485
 
486
- /**
487
- * @param string $sAction
488
- * @return array
489
- */
490
- protected function getModActionParams( $sAction ) {
491
  $con = $this->getCon();
492
  return [
493
  'action' => $con->prefix(),
494
- 'exec' => $sAction,
495
  'mod_slug' => $this->getModSlug(),
496
  'ts' => Services::Request()->ts(),
497
  'exec_nonce' => substr(
498
- hash_hmac( 'md5', $sAction.Services::Request()->ts(), $con->getSiteInstallationId() )
499
  , 0, 6 )
500
  ];
501
  }
@@ -549,12 +483,10 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
549
  * TODO: Get rid of this crap and/or handle the \Exception thrown in loadFeatureHandler()
550
  * @return ICWP_WPSF_FeatureHandler_Email
551
  * @throws \Exception
 
552
  */
553
  public function getEmailHandler() {
554
- if ( is_null( self::$oEmailHandler ) ) {
555
- self::$oEmailHandler = $this->getCon()->getModule( 'email' );
556
- }
557
- return self::$oEmailHandler;
558
  }
559
 
560
  /**
@@ -604,26 +536,16 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
604
  || $opts->isOpt( $this->getEnableModOptKey(), true, true );
605
  }
606
 
607
- /**
608
- * @return string
609
- */
610
- public function getEnableModOptKey() {
611
  return 'enable_'.$this->getSlug();
612
  }
613
 
614
- /**
615
- * @return string
616
- */
617
- public function getMainFeatureName() {
618
  return __( $this->getOptions()->getFeatureProperty( 'name' ), 'wp-simple-firewall' );
619
  }
620
 
621
- /**
622
- * @param bool $bWithPrefix
623
- * @return string
624
- */
625
- public function getModSlug( $bWithPrefix = true ) {
626
- return $bWithPrefix ? $this->prefix( $this->getSlug() ) : $this->getSlug();
627
  }
628
 
629
  /**
@@ -786,11 +708,11 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
786
 
787
  /**
788
  * Get config 'definition'.
789
- * @param string $sKey
790
  * @return mixed|null
791
  */
792
- public function getDef( $sKey ) {
793
- return $this->getOptions()->getDef( $sKey );
794
  }
795
 
796
  /**
@@ -813,24 +735,10 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
813
  return $bAsString ? implode( $sGlue, $errors ) : $errors;
814
  }
815
 
816
- /**
817
- * @return bool
818
- */
819
- public function hasLastErrors() {
820
  return count( $this->getLastErrors( false ) ) > 0;
821
  }
822
 
823
- /**
824
- * @param string $sOptionKey
825
- * @param mixed $mValueToTest
826
- * @param bool $bStrict
827
- * @return bool
828
- */
829
- public function isOpt( $sOptionKey, $mValueToTest, $bStrict = false ) {
830
- $mOptionValue = $this->getOptions()->getOpt( $sOptionKey );
831
- return $bStrict ? $mOptionValue === $mValueToTest : $mOptionValue == $mValueToTest;
832
- }
833
-
834
  public function getTextOpt( string $key ) :string {
835
  $sValue = $this->getOptions()->getOpt( $key, 'default' );
836
  if ( $sValue == 'default' ) {
@@ -872,11 +780,8 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
872
  }
873
  }
874
 
875
- /**
876
- * @return bool
877
- */
878
- public function isModuleRequest() {
879
- return ( $this->getModSlug() == Services::Request()->request( 'mod_slug' ) );
880
  }
881
 
882
  /**
@@ -911,13 +816,13 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
911
  /**
912
  * @return string[]
913
  */
914
- public function getUiTrack() {
915
- $aDN = $this->getOptions()->getOpt( 'ui_track' );
916
- return is_array( $aDN ) ? $aDN : [];
917
  }
918
 
919
- public function setDismissedNotices( array $aDismissed ) {
920
- $this->getOptions()->setOpt( 'dismissed_notices', $aDismissed );
921
  }
922
 
923
  public function setUiTrack( array $UI ) {
@@ -1054,11 +959,8 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
1054
  return $this;
1055
  }
1056
 
1057
- /**
1058
- * @return bool
1059
- */
1060
- protected function isAdminOptionsPage() {
1061
- return ( is_admin() && !Services::WpGeneral()->isAjax() && $this->isThisModulePage() );
1062
  }
1063
 
1064
  /**
@@ -1423,6 +1325,12 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
1423
  $oRndr->setTemplateEngineTwig();
1424
  }
1425
 
 
 
 
 
 
 
1426
  $render = $oRndr->setTemplate( $template )
1427
  ->setRenderVars( $data )
1428
  ->render();
@@ -1448,23 +1356,29 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
1448
  }
1449
 
1450
  public function collectOptionsForTracking() :array {
1451
- $oVO = $this->getOptions();
1452
  $aOptionsData = $this->getOptions()->getOptionsForTracking();
1453
- foreach ( $aOptionsData as $sOption => $mValue ) {
1454
- unset( $aOptionsData[ $sOption ] );
1455
  // some cleaning to ensure we don't have disallowed characters
1456
- $sOption = preg_replace( '#[^_a-z]#', '', strtolower( $sOption ) );
1457
- $sType = $oVO->getOptionType( $sOption );
1458
  if ( $sType == 'checkbox' ) { // only want a boolean 1 or 0
1459
- $aOptionsData[ $sOption ] = (int)( $mValue == 'Y' );
1460
  }
1461
  else {
1462
- $aOptionsData[ $sOption ] = $mValue;
1463
  }
1464
  }
1465
  return $aOptionsData;
1466
  }
1467
 
 
 
 
 
 
 
1468
  /**
1469
  * See plugin controller for the nature of $aData wpPrivacyExport()
1470
  * @param array $aExportItems
@@ -1493,7 +1407,7 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
1493
  public function getOptions() {
1494
  if ( !isset( $this->oOpts ) ) {
1495
  $oCon = $this->getCon();
1496
- $this->oOpts = $this->loadOptions()->setMod( $this );
1497
  $this->oOpts->setPathToConfig( $oCon->getPath_ConfigFile( $this->getSlug() ) )
1498
  ->setRebuildFromFile( $oCon->getIsRebuildOptionsFromFile() )
1499
  ->setOptionsStorageKey( $this->getOptionsStorageKey() )
@@ -1508,14 +1422,10 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
1508
  */
1509
  public function getWpCli() {
1510
  if ( !isset( $this->oWpCli ) ) {
1511
- $this->oWpCli = $this->loadClass( 'WpCli' );
1512
  if ( !$this->oWpCli instanceof Shield\Modules\Base\WpCli ) {
1513
- $this->oWpCli = $this->loadClassFromBase( 'WpCli' );
1514
- if ( !$this->oWpCli instanceof Shield\Modules\Base\WpCli ) {
1515
- throw new \Exception( 'WP-CLI not supported' );
1516
- }
1517
  }
1518
- $this->oWpCli->setMod( $this );
1519
  }
1520
  return $this->oWpCli;
1521
  }
@@ -1524,10 +1434,7 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
1524
  * @return null|Shield\Modules\Base\Strings
1525
  */
1526
  public function getStrings() {
1527
- if ( !isset( $this->oStrings ) ) {
1528
- $this->oStrings = $this->loadStrings()->setMod( $this );
1529
- }
1530
- return $this->oStrings;
1531
  }
1532
 
1533
  /**
@@ -1535,12 +1442,11 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
1535
  */
1536
  public function getUIHandler() {
1537
  if ( !isset( $this->oUI ) ) {
1538
- $this->oUI = $this->loadClass( 'UI' );
1539
  if ( !$this->oUI instanceof Shield\Modules\Base\UI ) {
1540
  // TODO: autoloader for base classes
1541
- $this->oUI = $this->loadClassFromBase( 'ShieldUI' );
1542
  }
1543
- $this->oUI->setMod( $this );
1544
  }
1545
  return $this->oUI;
1546
  }
@@ -1550,42 +1456,31 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
1550
  */
1551
  public function getReportingHandler() {
1552
  if ( !isset( $this->oReporting ) ) {
1553
- $this->oReporting = $this->loadClass( 'Reporting' );
1554
- if ( $this->oReporting instanceof Shield\Modules\Base\BaseReporting ) {
1555
- $this->oReporting->setMod( $this );
1556
- }
1557
  }
1558
  return $this->oReporting;
1559
  }
1560
 
1561
- /**
1562
- * @return $this
1563
- */
1564
  protected function loadAdminNotices() {
1565
- $oNotices = $this->loadClass( 'AdminNotices' );
1566
- if ( $oNotices instanceof Shield\Modules\Base\AdminNotices ) {
1567
- $oNotices->setMod( $this )->run();
1568
  }
1569
- return $this;
1570
  }
1571
 
1572
- /**
1573
- * @return $this
1574
- */
1575
  protected function loadAjaxHandler() {
1576
- $oAj = $this->loadClass( 'AjaxHandler' );
1577
  if ( !$oAj instanceof Shield\Modules\Base\AjaxHandlerBase ) {
1578
- $oAj = new Shield\Modules\Base\AjaxHandlerShield();
1579
  }
1580
- $oAj->setMod( $this );
1581
- return $this;
1582
  }
1583
 
1584
  /**
1585
  * @return Shield\Modules\Base\ShieldOptions|mixed
 
1586
  */
1587
  protected function loadOptions() {
1588
- return $this->loadClass( 'Options' );
1589
  }
1590
 
1591
  protected function loadDebug() {
@@ -1602,12 +1497,13 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
1602
  * @return Shield\Modules\Base\Strings|mixed
1603
  */
1604
  protected function loadStrings() {
1605
- return $this->loadClass( 'Strings' );
1606
  }
1607
 
1608
  /**
1609
  * @param $sClass
1610
  * @return \stdClass|mixed|false
 
1611
  */
1612
  private function loadClass( $sClass ) {
1613
  $sC = $this->getNamespace().$sClass;
@@ -1619,10 +1515,10 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
1619
  * @param false $injectMod
1620
  * @return false|Shield\Modules\ModConsumer
1621
  */
1622
- private function loadModElement( $class, $injectMod = true ) {
1623
  $element = false;
1624
  try {
1625
- $C = $this->findElementClass( $class, true, true );
1626
  /** @var Shield\Modules\ModConsumer $element */
1627
  $element = @class_exists( $C ) ? new $C() : false;
1628
  if ( $injectMod && method_exists( $element, 'setMod' ) ) {
@@ -1637,6 +1533,7 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
1637
  /**
1638
  * @param $sClass
1639
  * @return \stdClass|mixed|false
 
1640
  */
1641
  private function loadClassFromBase( $sClass ) {
1642
  $sC = $this->getBaseNamespace().$sClass;
@@ -1646,24 +1543,19 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
1646
  /**
1647
  * @param string $element
1648
  * @param bool $bThrowException
1649
- * @param bool $bUseFoundationBase
1650
  * @return string|null
1651
  * @throws \Exception
1652
  */
1653
- protected function findElementClass( $element, $bUseFoundationBase = true, $bThrowException = true ) {
1654
- $namespace = [
1655
- $this->getNamespace(),
1656
- ];
1657
- if ( $bUseFoundationBase ) {
1658
- $namespace[] = $this->getBaseNamespace();
1659
- }
1660
-
1661
  $theClass = null;
1662
- foreach ( $namespace as $NS ) {
 
1663
  $maybe = $NS.$element;
1664
  if ( @class_exists( $maybe ) ) {
1665
- $theClass = $maybe;
1666
- break;
 
 
1667
  }
1668
  }
1669
 
@@ -1674,11 +1566,22 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
1674
  }
1675
 
1676
  private function getBaseNamespace() {
1677
- return '\FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\\';
 
 
 
 
1678
  }
1679
 
1680
  protected function getNamespace() :string {
1681
- return '\FernleafSystems\Wordpress\Plugin\Shield\Modules\\'.$this->getNamespaceBase().'\\';
 
 
 
 
 
 
 
1682
  }
1683
 
1684
  protected function getNamespaceBase() :string {
@@ -1693,33 +1596,4 @@ abstract class ICWP_WPSF_FeatureHandler_Base {
1693
  public function savePluginOptions() {
1694
  $this->saveModOptions();
1695
  }
1696
-
1697
- /**
1698
- * @deprecated 10.0
1699
- */
1700
- public function getOptionStoragePrefix() :string {
1701
- return $this->getCon()->getOptionStoragePrefix();
1702
- }
1703
-
1704
- /**
1705
- * @param string $sOptionKey
1706
- * @param mixed $mDefault
1707
- * @return mixed
1708
- * @deprecated 10.0
1709
- */
1710
- public function getOpt( $sOptionKey, $mDefault = false ) {
1711
- return $this->getOptions()->getOpt( $sOptionKey, $mDefault );
1712
- }
1713
-
1714
- /**
1715
- * Sets the value for the given option key
1716
- * @param string $key
1717
- * @param mixed $value
1718
- * @return $this
1719
- * @deprecated 10.0
1720
- */
1721
- protected function setOpt( string $key, $value ) {
1722
- $this->getOptions()->setOpt( $key, $value );
1723
- return $this;
1724
- }
1725
  }
27
 
28
  /**
29
  * @var ICWP_WPSF_FeatureHandler_Email
30
+ * @deprecated 10.1
31
  */
32
  private static $oEmailHandler;
33
 
46
  */
47
  private $oReporting;
48
 
 
 
 
 
 
49
  /**
50
  * @var Shield\Modules\Base\UI
51
  */
115
  add_action( $con->prefix( 'daily_cron' ), [ $this, 'runDailyCron' ] );
116
  add_action( $con->prefix( 'hourly_cron' ), [ $this, 'runHourlyCron' ] );
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  add_action( 'admin_enqueue_scripts', [ $this, 'onWpEnqueueAdminJs' ], 100 );
119
 
120
  if ( is_admin() || is_network_admin() ) {
205
  * @return false|Shield\Modules\Base\Upgrade|mixed
206
  */
207
  public function getUpgradeHandler() {
208
+ return $this->loadModElement( 'Upgrade' );
209
  }
210
 
211
  /**
254
  return array_merge( $aAdminNotices, $this->getOptions()->getAdminNotices() );
255
  }
256
 
257
+ private function verifyModuleMeetRequirements() :bool {
 
 
 
258
  $bMeetsReqs = true;
259
 
260
  $aPhpReqs = $this->getOptions()->getFeatureRequirement( 'php' );
292
  $this->doExecuteProcessor();
293
  }
294
  }
295
+ catch ( \Exception $e ) {
296
  }
297
  }
298
 
309
  }
310
 
311
  public function onWpInit() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  }
313
 
314
  /**
332
  */
333
  protected function loadProcessor() {
334
  if ( !isset( $this->oProcessor ) ) {
335
+ try {
336
+ // TODO: Remove 'abstract' from base processor after transition to new processors is complete
337
+ $class = $this->findElementClass( 'Processor', true );
338
+ }
339
+ catch ( Exception $e ) {
340
+ $class = $this->getProcessorClassName();
341
+ }
342
+ if ( !@class_exists( $class ) ) {
343
  return null;
344
  }
345
+ $this->oProcessor = new $class( $this );
346
  }
347
  return $this->oProcessor;
348
  }
349
 
350
  /**
351
+ * This is the old method
352
+ * @deprecated 10.1
353
  */
354
  protected function getProcessorClassName() :string {
355
  return implode( '_',
375
  );
376
  }
377
 
378
+ public function isUpgrading() :bool {
 
 
 
379
  return $this->getCon()->getIsRebuildOptionsFromFile() || $this->getOptions()->getRebuildFromFile();
380
  }
381
 
392
  }
393
  }
394
 
395
+ public function getOptionsStorageKey() :string {
 
 
 
396
  return $this->getCon()->prefixOption( $this->sOptionsStoreKey ).'_options';
397
  }
398
 
421
  return add_query_arg( $aActionNonce, $this->getUrl_AdminPage() );
422
  }
423
 
424
+ protected function getModActionParams( string $action ) :array {
 
 
 
 
425
  $con = $this->getCon();
426
  return [
427
  'action' => $con->prefix(),
428
+ 'exec' => $action,
429
  'mod_slug' => $this->getModSlug(),
430
  'ts' => Services::Request()->ts(),
431
  'exec_nonce' => substr(
432
+ hash_hmac( 'md5', $action.Services::Request()->ts(), $con->getSiteInstallationId() )
433
  , 0, 6 )
434
  ];
435
  }
483
  * TODO: Get rid of this crap and/or handle the \Exception thrown in loadFeatureHandler()
484
  * @return ICWP_WPSF_FeatureHandler_Email
485
  * @throws \Exception
486
+ * @deprecated 10.1
487
  */
488
  public function getEmailHandler() {
489
+ return $this->getCon()->getModule( 'email' );
 
 
 
490
  }
491
 
492
  /**
536
  || $opts->isOpt( $this->getEnableModOptKey(), true, true );
537
  }
538
 
539
+ public function getEnableModOptKey() :string {
 
 
 
540
  return 'enable_'.$this->getSlug();
541
  }
542
 
543
+ public function getMainFeatureName() :string {
 
 
 
544
  return __( $this->getOptions()->getFeatureProperty( 'name' ), 'wp-simple-firewall' );
545
  }
546
 
547
+ public function getModSlug( bool $prefix = true ) :string {
548
+ return $prefix ? $this->prefix( $this->getSlug() ) : $this->getSlug();
 
 
 
 
549
  }
550
 
551
  /**
708
 
709
  /**
710
  * Get config 'definition'.
711
+ * @param string $key
712
  * @return mixed|null
713
  */
714
+ public function getDef( string $key ) {
715
+ return $this->getOptions()->getDef( $key );
716
  }
717
 
718
  /**
735
  return $bAsString ? implode( $sGlue, $errors ) : $errors;
736
  }
737
 
738
+ public function hasLastErrors() :bool {
 
 
 
739
  return count( $this->getLastErrors( false ) ) > 0;
740
  }
741
 
 
 
 
 
 
 
 
 
 
 
 
742
  public function getTextOpt( string $key ) :string {
743
  $sValue = $this->getOptions()->getOpt( $key, 'default' );
744
  if ( $sValue == 'default' ) {
780
  }
781
  }
782
 
783
+ public function isModuleRequest() :bool {
784
+ return $this->getModSlug() === Services::Request()->request( 'mod_slug' );
 
 
 
785
  }
786
 
787
  /**
816
  /**
817
  * @return string[]
818
  */
819
+ public function getUiTrack() :array {
820
+ $a = $this->getOptions()->getOpt( 'ui_track' );
821
+ return is_array( $a ) ? $a : [];
822
  }
823
 
824
+ public function setDismissedNotices( array $dis ) {
825
+ $this->getOptions()->setOpt( 'dismissed_notices', $dis );
826
  }
827
 
828
  public function setUiTrack( array $UI ) {
959
  return $this;
960
  }
961
 
962
+ protected function isAdminOptionsPage() :bool {
963
+ return is_admin() && !Services::WpGeneral()->isAjax() && $this->isThisModulePage();
 
 
 
964
  }
965
 
966
  /**
1325
  $oRndr->setTemplateEngineTwig();
1326
  }
1327
 
1328
+ $data[ 'strings' ] = Services::DataManipulation()
1329
+ ->mergeArraysRecursive(
1330
+ $this->getStrings()->getDisplayStrings(),
1331
+ $data[ 'strings' ] ?? []
1332
+ );
1333
+
1334
  $render = $oRndr->setTemplate( $template )
1335
  ->setRenderVars( $data )
1336
  ->render();
1356
  }
1357
 
1358
  public function collectOptionsForTracking() :array {
1359
+ $opts = $this->getOptions();
1360
  $aOptionsData = $this->getOptions()->getOptionsForTracking();
1361
+ foreach ( $aOptionsData as $opt => $mValue ) {
1362
+ unset( $aOptionsData[ $opt ] );
1363
  // some cleaning to ensure we don't have disallowed characters
1364
+ $opt = preg_replace( '#[^_a-z]#', '', strtolower( $opt ) );
1365
+ $sType = $opts->getOptionType( $opt );
1366
  if ( $sType == 'checkbox' ) { // only want a boolean 1 or 0
1367
+ $aOptionsData[ $opt ] = (int)( $mValue == 'Y' );
1368
  }
1369
  else {
1370
+ $aOptionsData[ $opt ] = $mValue;
1371
  }
1372
  }
1373
  return $aOptionsData;
1374
  }
1375
 
1376
+ public function getMainWpData() :array {
1377
+ return [
1378
+ 'options' => $this->getOptions()->getTransferableOptions()
1379
+ ];
1380
+ }
1381
+
1382
  /**
1383
  * See plugin controller for the nature of $aData wpPrivacyExport()
1384
  * @param array $aExportItems
1407
  public function getOptions() {
1408
  if ( !isset( $this->oOpts ) ) {
1409
  $oCon = $this->getCon();
1410
+ $this->oOpts = $this->loadModElement( 'Options' );
1411
  $this->oOpts->setPathToConfig( $oCon->getPath_ConfigFile( $this->getSlug() ) )
1412
  ->setRebuildFromFile( $oCon->getIsRebuildOptionsFromFile() )
1413
  ->setOptionsStorageKey( $this->getOptionsStorageKey() )
1422
  */
1423
  public function getWpCli() {
1424
  if ( !isset( $this->oWpCli ) ) {
1425
+ $this->oWpCli = $this->loadModElement( 'WpCli' );
1426
  if ( !$this->oWpCli instanceof Shield\Modules\Base\WpCli ) {
1427
+ throw new \Exception( 'WP-CLI not supported' );
 
 
 
1428
  }
 
1429
  }
1430
  return $this->oWpCli;
1431
  }
1434
  * @return null|Shield\Modules\Base\Strings
1435
  */
1436
  public function getStrings() {
1437
+ return $this->loadStrings()->setMod( $this );
 
 
 
1438
  }
1439
 
1440
  /**
1442
  */
1443
  public function getUIHandler() {
1444
  if ( !isset( $this->oUI ) ) {
1445
+ $this->oUI = $this->loadModElement( 'UI' );
1446
  if ( !$this->oUI instanceof Shield\Modules\Base\UI ) {
1447
  // TODO: autoloader for base classes
1448
+ $this->oUI = $this->loadModElement( 'ShieldUI' );
1449
  }
 
1450
  }
1451
  return $this->oUI;
1452
  }
1456
  */
1457
  public function getReportingHandler() {
1458
  if ( !isset( $this->oReporting ) ) {
1459
+ $this->oReporting = $this->loadModElement( 'Reporting' );
 
 
 
1460
  }
1461
  return $this->oReporting;
1462
  }
1463
 
 
 
 
1464
  protected function loadAdminNotices() {
1465
+ $N = $this->loadModElement( 'AdminNotices' );
1466
+ if ( $N instanceof Shield\Modules\Base\AdminNotices ) {
1467
+ $N->run();
1468
  }
 
1469
  }
1470
 
 
 
 
1471
  protected function loadAjaxHandler() {
1472
+ $oAj = $this->loadModElement( 'AjaxHandler' );
1473
  if ( !$oAj instanceof Shield\Modules\Base\AjaxHandlerBase ) {
1474
+ $this->loadModElement( 'AjaxHandlerShield' );
1475
  }
 
 
1476
  }
1477
 
1478
  /**
1479
  * @return Shield\Modules\Base\ShieldOptions|mixed
1480
+ * @deprecated 10.1
1481
  */
1482
  protected function loadOptions() {
1483
+ return $this->loadModElement( 'Options' );
1484
  }
1485
 
1486
  protected function loadDebug() {
1497
  * @return Shield\Modules\Base\Strings|mixed
1498
  */
1499
  protected function loadStrings() {
1500
+ return $this->loadModElement( 'Strings', true );
1501
  }
1502
 
1503
  /**
1504
  * @param $sClass
1505
  * @return \stdClass|mixed|false
1506
+ * @deprecated 10.1
1507
  */
1508
  private function loadClass( $sClass ) {
1509
  $sC = $this->getNamespace().$sClass;
1515
  * @param false $injectMod
1516
  * @return false|Shield\Modules\ModConsumer
1517
  */
1518
+ private function loadModElement( string $class, $injectMod = true ) {
1519
  $element = false;
1520
  try {
1521
+ $C = $this->findElementClass( $class, true );
1522
  /** @var Shield\Modules\ModConsumer $element */
1523
  $element = @class_exists( $C ) ? new $C() : false;
1524
  if ( $injectMod && method_exists( $element, 'setMod' ) ) {
1533
  /**
1534
  * @param $sClass
1535
  * @return \stdClass|mixed|false
1536
+ * @deprecated 10.1
1537
  */
1538
  private function loadClassFromBase( $sClass ) {
1539
  $sC = $this->getBaseNamespace().$sClass;
1543
  /**
1544
  * @param string $element
1545
  * @param bool $bThrowException
 
1546
  * @return string|null
1547
  * @throws \Exception
1548
  */
1549
+ protected function findElementClass( string $element, $bThrowException = true ) {
 
 
 
 
 
 
 
1550
  $theClass = null;
1551
+
1552
+ foreach ( $this->getNamespaceRoots() as $NS ) {
1553
  $maybe = $NS.$element;
1554
  if ( @class_exists( $maybe ) ) {
1555
+ if ( ( new ReflectionClass( $maybe ) )->isInstantiable() ) {
1556
+ $theClass = $maybe;
1557
+ break;
1558
+ }
1559
  }
1560
  }
1561
 
1566
  }
1567
 
1568
  private function getBaseNamespace() {
1569
+ return $this->getModulesNamespace().'Base\\';
1570
+ }
1571
+
1572
+ protected function getModulesNamespace() :string {
1573
+ return '\FernleafSystems\Wordpress\Plugin\Shield\Modules\\';
1574
  }
1575
 
1576
  protected function getNamespace() :string {
1577
+ return $this->getModulesNamespace().$this->getNamespaceBase().'\\';
1578
+ }
1579
+
1580
+ protected function getNamespaceRoots() :array {
1581
+ return [
1582
+ $this->getNamespace(),
1583
+ $this->getBaseNamespace()
1584
+ ];
1585
  }
1586
 
1587
  protected function getNamespaceBase() :string {
1596
  public function savePluginOptions() {
1597
  $this->saveModOptions();
1598
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1599
  }
src/features/base_wpsf.php CHANGED
@@ -110,13 +110,6 @@ class ICWP_WPSF_FeatureHandler_BaseWpsf extends ICWP_WPSF_FeatureHandler_Base {
110
  return $oCfg;
111
  }
112
 
113
- /**
114
- * @return bool
115
- */
116
- public function isWlEnabled() {
117
- return $this->getCon()->getModule_SecAdmin()->isWlEnabled();
118
- }
119
-
120
  /**
121
  * @return array
122
  */
@@ -255,4 +248,15 @@ class ICWP_WPSF_FeatureHandler_BaseWpsf extends ICWP_WPSF_FeatureHandler_Base {
255
  }
256
  return array_unique( array_filter( $aCleaned ) );
257
  }
 
 
 
 
 
 
 
 
 
 
 
258
  }
110
  return $oCfg;
111
  }
112
 
 
 
 
 
 
 
 
113
  /**
114
  * @return array
115
  */
248
  }
249
  return array_unique( array_filter( $aCleaned ) );
250
  }
251
+
252
+ protected function getNamespaceRoots() :array {
253
+ // Ensure order of namespaces is 'Module', 'Shield', then 'Base'
254
+ return array_unique( array_merge(
255
+ [
256
+ $this->getNamespace(),
257
+ $this->getModulesNamespace().'BaseShield\\',
258
+ ],
259
+ parent::getNamespaceRoots()
260
+ ) );
261
+ }
262
  }
src/features/comments_filter.php CHANGED
@@ -3,6 +3,9 @@
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
5
 
 
 
 
6
  class ICWP_WPSF_FeatureHandler_CommentsFilter extends ICWP_WPSF_FeatureHandler_BaseWpsf {
7
 
8
  /**
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
5
 
6
+ /**
7
+ * @deprecated 10.1
8
+ */
9
  class ICWP_WPSF_FeatureHandler_CommentsFilter extends ICWP_WPSF_FeatureHandler_BaseWpsf {
10
 
11
  /**
src/features/comms.php CHANGED
@@ -2,12 +2,12 @@
2
 
3
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Comms;
4
 
 
 
 
 
5
  class ICWP_WPSF_FeatureHandler_Comms extends ICWP_WPSF_FeatureHandler_BaseWpsf {
6
 
7
- protected function doPostConstruction() {
8
- parent::doPostConstruction(); // TODO: Change the autogenerated stub
9
- }
10
-
11
  public function getSureSendController() :Comms\Lib\SureSend\SureSendController {
12
  return ( new Comms\Lib\SureSend\SureSendController() )
13
  ->setMod( $this );
2
 
3
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Comms;
4
 
5
+ /**
6
+ * Class ICWP_WPSF_FeatureHandler_Comms
7
+ * @deprecated 10.1
8
+ */
9
  class ICWP_WPSF_FeatureHandler_Comms extends ICWP_WPSF_FeatureHandler_BaseWpsf {
10
 
 
 
 
 
11
  public function getSureSendController() :Comms\Lib\SureSend\SureSendController {
12
  return ( new Comms\Lib\SureSend\SureSendController() )
13
  ->setMod( $this );
src/features/email.php CHANGED
@@ -1,5 +1,8 @@
1
  <?php declare( strict_types=1 );
2
 
 
 
 
3
  class ICWP_WPSF_FeatureHandler_Email extends ICWP_WPSF_FeatureHandler_BaseWpsf {
4
 
5
  protected function getNamespaceBase() :string {
1
  <?php declare( strict_types=1 );
2
 
3
+ /**
4
+ * @deprecated 10.1
5
+ */
6
  class ICWP_WPSF_FeatureHandler_Email extends ICWP_WPSF_FeatureHandler_BaseWpsf {
7
 
8
  protected function getNamespaceBase() :string {
src/features/events.php CHANGED
@@ -2,6 +2,10 @@
2
 
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
 
 
 
 
 
5
  class ICWP_WPSF_FeatureHandler_Events extends ICWP_WPSF_FeatureHandler_BaseWpsf {
6
 
7
  /**
2
 
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
 
5
+ /**
6
+ * Class ICWP_WPSF_FeatureHandler_Events
7
+ * @deprecated 10.1
8
+ */
9
  class ICWP_WPSF_FeatureHandler_Events extends ICWP_WPSF_FeatureHandler_BaseWpsf {
10
 
11
  /**
src/features/firewall.php CHANGED
@@ -3,6 +3,9 @@
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall;
5
 
 
 
 
6
  class ICWP_WPSF_FeatureHandler_Firewall extends ICWP_WPSF_FeatureHandler_BaseWpsf {
7
 
8
  /**
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall;
5
 
6
+ /**
7
+ * @deprecated 10.1
8
+ */
9
  class ICWP_WPSF_FeatureHandler_Firewall extends ICWP_WPSF_FeatureHandler_BaseWpsf {
10
 
11
  /**
src/features/hack_protect.php CHANGED
@@ -4,6 +4,9 @@ use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
 
 
 
7
  class ICWP_WPSF_FeatureHandler_HackProtect extends ICWP_WPSF_FeatureHandler_BaseWpsf {
8
 
9
  /**
@@ -80,6 +83,14 @@ class ICWP_WPSF_FeatureHandler_HackProtect extends ICWP_WPSF_FeatureHandler_Base
80
  return $this->aScanCons[ $slug ];
81
  }
82
 
 
 
 
 
 
 
 
 
83
  protected function handleModAction( string $sAction ) {
84
  switch ( $sAction ) {
85
  case 'scan_file_download':
@@ -102,12 +113,6 @@ class ICWP_WPSF_FeatureHandler_HackProtect extends ICWP_WPSF_FeatureHandler_Base
102
 
103
  $this->cleanFileExclusions();
104
 
105
- if ( $opts->isOptChanged( 'scan_frequency' ) ) {
106
- /** @var \ICWP_WPSF_Processor_HackProtect $oPro */
107
- $oPro = $this->getProcessor();
108
- $oPro->getSubProScanner()->deleteCron();
109
- }
110
-
111
  if ( count( $opts->getFilesToLock() ) === 0 || !$this->getCon()
112
  ->getModule_Plugin()
113
  ->getShieldNetApiController()
@@ -151,16 +156,16 @@ class ICWP_WPSF_FeatureHandler_HackProtect extends ICWP_WPSF_FeatureHandler_Base
151
  }
152
 
153
  /**
154
- * @param string $sScan ptg, wcf, ufc, wpv
155
  * @return int
156
  */
157
- public function getLastScanAt( $sScan ) {
158
  /** @var Shield\Databases\Events\Select $oSel */
159
  $oSel = $this->getCon()
160
  ->getModule_Events()
161
  ->getDbHandler_Events()
162
  ->getQuerySelector();
163
- $oEntry = $oSel->getLatestForEvent( $sScan.'_scan_run' );
164
  return ( $oEntry instanceof Shield\Databases\Events\EntryVO ) ? $oEntry->created_at : 0;
165
  }
166
 
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
7
+ /**
8
+ * @deprecated 10.1
9
+ */
10
  class ICWP_WPSF_FeatureHandler_HackProtect extends ICWP_WPSF_FeatureHandler_BaseWpsf {
11
 
12
  /**
83
  return $this->aScanCons[ $slug ];
84
  }
85
 
86
+ public function getMainWpData() :array {
87
+ $issues = ( new HackGuard\Lib\Reports\Query\ScanCounts() )->setMod( $this );
88
+ $issues->notified = null;
89
+ return array_merge( parent::getMainWpData(), [
90
+ 'scan_issues' => array_filter( $issues->all() )
91
+ ] );
92
+ }
93
+
94
  protected function handleModAction( string $sAction ) {
95
  switch ( $sAction ) {
96
  case 'scan_file_download':
113
 
114
  $this->cleanFileExclusions();
115
 
 
 
 
 
 
 
116
  if ( count( $opts->getFilesToLock() ) === 0 || !$this->getCon()
117
  ->getModule_Plugin()
118
  ->getShieldNetApiController()
156
  }
157
 
158
  /**
159
+ * @param string $scan ptg, wcf, ufc, wpv
160
  * @return int
161
  */
162
+ public function getLastScanAt( $scan ) {
163
  /** @var Shield\Databases\Events\Select $oSel */
164
  $oSel = $this->getCon()
165
  ->getModule_Events()
166
  ->getDbHandler_Events()
167
  ->getQuerySelector();
168
+ $oEntry = $oSel->getLatestForEvent( $scan.'_scan_run' );
169
  return ( $oEntry instanceof Shield\Databases\Events\EntryVO ) ? $oEntry->created_at : 0;
170
  }
171
 
src/features/headers.php CHANGED
@@ -3,6 +3,9 @@
3
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Headers;
4
  use FernleafSystems\Wordpress\Services\Services;
5
 
 
 
 
6
  class ICWP_WPSF_FeatureHandler_Headers extends ICWP_WPSF_FeatureHandler_BaseWpsf {
7
 
8
  protected function preProcessOptions() {
3
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Headers;
4
  use FernleafSystems\Wordpress\Services\Services;
5
 
6
+ /**
7
+ * @deprecated 10.1
8
+ */
9
  class ICWP_WPSF_FeatureHandler_Headers extends ICWP_WPSF_FeatureHandler_BaseWpsf {
10
 
11
  protected function preProcessOptions() {
src/features/insights.php CHANGED
@@ -3,6 +3,9 @@
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Services\Services;
5
 
 
 
 
6
  class ICWP_WPSF_FeatureHandler_Insights extends ICWP_WPSF_FeatureHandler_BaseWpsf {
7
 
8
  protected function onModulesLoaded() {
@@ -24,183 +27,14 @@ class ICWP_WPSF_FeatureHandler_Insights extends ICWP_WPSF_FeatureHandler_BaseWps
24
  public function getUrl_SubInsightsPage( string $subPage ) :string {
25
  return add_query_arg(
26
  [ 'inav' => sanitize_key( $subPage ) ],
27
- $this->getCon()->getModule_Insights()->getUrl_AdminPage()
28
  );
29
  }
30
 
31
  protected function renderModulePage( array $aData = [] ) :string {
32
- $con = $this->getCon();
33
- $oReq = Services::Request();
34
-
35
- $sNavSection = $oReq->query( 'inav', 'overview' );
36
- $sSubNavSection = $oReq->query( 'subnav' );
37
-
38
- $oModPlugin = $con->getModule_Plugin();
39
- $oTourManager = $oModPlugin->getTourManager();
40
- if ( !$oTourManager->isCompleted( 'insights_overview' ) && $oModPlugin->getActivateLength() > 600 ) {
41
- $oTourManager->setCompleted( 'insights_overview' );
42
- }
43
-
44
- $bIsPro = $this->isPremium();
45
- switch ( $sNavSection ) {
46
-
47
- case 'audit':
48
- /** @var Shield\Modules\AuditTrail\UI $UI */
49
- $UI = $con->getModule_AuditTrail()->getUIHandler();
50
- $aData = $UI->buildInsightsVars();
51
- break;
52
-
53
- case 'debug':
54
- /** @var Shield\Modules\Plugin\UI $UI */
55
- $UI = $con->getModule_Plugin()->getUIHandler();
56
- $aData = $UI->buildInsightsVars_Debug();
57
- break;
58
-
59
- case 'ips':
60
- /** @var Shield\Modules\IPs\UI $UI */
61
- $UI = $con->getModule_IPs()->getUIHandler();
62
- $aData = $UI->buildInsightsVars();
63
- break;
64
-
65
- case 'traffic':
66
- /** @var Shield\Modules\Traffic\UI $UI */
67
- $UI = $con->getModule_Traffic()->getUIHandler();
68
- $aData = $UI->buildInsightsVars();
69
- break;
70
-
71
- case 'license':
72
- /** @var Shield\Modules\License\UI $UILicense */
73
- $UILicense = $con->getModule_License()->getUIHandler();
74
- $aData = $UILicense->buildInsightsVars();
75
- break;
76
-
77
- case 'scans':
78
- /** @var Shield\Modules\HackGuard\UI $UIHackGuard */
79
- $UIHackGuard = $con->getModule_HackGuard()->getUIHandler();
80
- $aData = $UIHackGuard->buildInsightsVars();
81
- break;
82
-
83
- case 'importexport':
84
- $aData = $oModPlugin->getImpExpController()->buildInsightsVars();
85
- break;
86
-
87
- case 'reports':
88
- /** @var Shield\Modules\Reporting\UI $UIReporting */
89
- $UIReporting = $con->getModule_Reporting()->getUIHandler();
90
- $aData = $UIReporting->buildInsightsVars();
91
- break;
92
-
93
- case 'users':
94
- /** @var Shield\Modules\UserManagement\UI $UIUsers */
95
- $UIUsers = $con->getModule( 'user_management' )->getUIHandler();
96
- $aData = $UIUsers->buildInsightsVars();
97
- break;
98
-
99
- case 'settings':
100
- $aData = [
101
- 'ajax' => [
102
- 'mod_options' => $con->getModule( Services::Request()->query( 'subnav' ) )
103
- ->getAjaxActionData( 'mod_options', true ),
104
- 'mod_opts_form_render' => $con->getModule( Services::Request()->query( 'subnav' ) )
105
- ->getAjaxActionData( 'mod_opts_form_render', true ),
106
- ],
107
- ];
108
- break;
109
-
110
- default:
111
- case 'overview':
112
- case 'index':
113
- /** @var Shield\Modules\Insights\UI $UIInsights */
114
- $UIInsights = $this->getUIHandler();
115
- $aData = $UIInsights->buildInsightsVars();
116
- break;
117
- }
118
-
119
- $aTopNav = [
120
- 'settings' => __( 'Settings', 'wp-simple-firewall' ),
121
- 'overview' => __( 'Overview', 'wp-simple-firewall' ),
122
- 'scans' => __( 'Scans', 'wp-simple-firewall' ),
123
- 'ips' => __( 'IPs', 'wp-simple-firewall' ),
124
- 'audit' => __( 'Logs', 'wp-simple-firewall' ),
125
- 'users' => __( 'Users', 'wp-simple-firewall' ),
126
- 'license' => __( 'Pro', 'wp-simple-firewall' ),
127
- 'traffic' => __( 'Traffic', 'wp-simple-firewall' ),
128
- 'importexport' => __( 'Import', 'wp-simple-firewall' ),
129
- 'reports' => __( 'Reports', 'wp-simple-firewall' ),
130
- 'debug' => __( 'Debug', 'wp-simple-firewall' ),
131
- // 'importexport' => sprintf( '%s/%s', __( 'Import', 'wp-simple-firewall' ), __( 'Export', 'wp-simple-firewall' ) ),
132
- ];
133
- if ( $bIsPro ) {
134
- unset( $aTopNav[ 'license' ] );
135
- $aTopNav[ 'license' ] = __( 'Pro', 'wp-simple-firewall' );
136
- }
137
-
138
- array_walk( $aTopNav, function ( &$sName, $sKey ) use ( $sNavSection ) {
139
- $sName = [
140
- 'href' => add_query_arg( [ 'inav' => $sKey ], $this->getUrl_AdminPage() ),
141
- 'name' => $sName,
142
- 'slug' => $sKey,
143
- 'active' => $sKey === $sNavSection,
144
- 'subnavs' => [],
145
- 'icon' => ''
146
- ];
147
- } );
148
-
149
- $aSearchSelect = [];
150
- $aSettingsSubNav = [];
151
- foreach ( $this->getModulesSummaryData() as $slug => $summary ) {
152
- if ( $summary[ 'show_mod_opts' ] ) {
153
- $aSettingsSubNav[ $slug ] = [
154
- 'href' => add_query_arg( [ 'subnav' => $slug ], $aTopNav[ 'settings' ][ 'href' ] ),
155
- 'name' => $summary[ 'name' ],
156
- 'active' => $slug === $sSubNavSection,
157
- 'slug' => $slug
158
- ];
159
-
160
- $aSearchSelect[ $summary[ 'name' ] ] = $summary[ 'options' ];
161
- }
162
- }
163
-
164
- if ( empty( $aSettingsSubNav ) ) {
165
- unset( $aTopNav[ 'settings' ] );
166
- }
167
- else {
168
- $aTopNav[ 'settings' ][ 'subnavs' ] = $aSettingsSubNav;
169
- }
170
-
171
- $DP = Services::DataManipulation();
172
- $aData = $DP->mergeArraysRecursive(
173
- $this->getUIHandler()->getBaseDisplayData(),
174
- [
175
- 'classes' => [
176
- 'page_container' => 'page-insights page-'.$sNavSection
177
- ],
178
- 'flags' => [
179
- 'show_promo' => !$bIsPro && ( $sNavSection != 'settings' ),
180
- 'show_guided_tour' => $oModPlugin->getIfShowIntroVideo(),
181
- 'tours' => [
182
- 'insights_overview' => $oTourManager->canShow( 'insights_overview' )
183
- ]
184
- ],
185
- 'hrefs' => [
186
- 'go_pro' => 'https://shsec.io/shieldgoprofeature',
187
- 'nav_home' => $this->getUrl_AdminPage(),
188
- 'top_nav' => $aTopNav,
189
- 'img_banner' => $con->getPluginUrl_Image( 'pluginlogo_banner-170x40.png' )
190
- ],
191
- 'strings' => $this->getStrings()->getDisplayStrings(),
192
- 'vars' => [
193
- 'changelog_id' => $con->getPluginSpec()[ 'meta' ][ 'announcekit_changelog_id' ],
194
- 'search_select' => $aSearchSelect
195
- ],
196
- ],
197
- $aData
198
- );
199
- return $this->renderTemplate(
200
- sprintf( '/wpadmin_pages/insights/%s/index.twig', $sNavSection ),
201
- $aData,
202
- true
203
- );
204
  }
205
 
206
  public function insertCustomJsVars_Admin() {
@@ -210,11 +44,11 @@ class ICWP_WPSF_FeatureHandler_Insights extends ICWP_WPSF_FeatureHandler_BaseWps
210
 
211
  $con = $this->getCon();
212
  $aStdDepsJs = [ $con->prefix( 'plugin' ) ];
213
- $sNav = Services::Request()->query( 'inav', 'overview' );
214
 
215
  $oModPlugin = $con->getModule_Plugin();
216
  $oTourManager = $oModPlugin->getTourManager();
217
- switch ( $sNav ) {
218
 
219
  case 'importexport':
220
 
@@ -279,11 +113,12 @@ class ICWP_WPSF_FeatureHandler_Insights extends ICWP_WPSF_FeatureHandler_BaseWps
279
  $this->includeScriptIpDetect();
280
  break;
281
 
 
282
  case 'scans':
283
  case 'audit':
 
284
  case 'ips':
285
  case 'debug':
286
- case 'traffic':
287
  case 'users':
288
 
289
  $sAsset = 'shield-tables';
@@ -298,7 +133,7 @@ class ICWP_WPSF_FeatureHandler_Insights extends ICWP_WPSF_FeatureHandler_BaseWps
298
  wp_enqueue_script( $sUnique );
299
 
300
  $aStdDepsJs[] = $sUnique;
301
- if ( $sNav == 'scans' ) {
302
  $sAsset = 'shield-scans';
303
  $sUnique = $con->prefix( $sAsset );
304
  wp_register_script(
@@ -311,7 +146,7 @@ class ICWP_WPSF_FeatureHandler_Insights extends ICWP_WPSF_FeatureHandler_BaseWps
311
  wp_enqueue_script( $sUnique );
312
  }
313
 
314
- if ( $sNav == 'ips' ) {
315
  $sAsset = 'shield/ipanalyse';
316
  $sUnique = $con->prefix( $sAsset );
317
  wp_register_script(
@@ -324,7 +159,7 @@ class ICWP_WPSF_FeatureHandler_Insights extends ICWP_WPSF_FeatureHandler_BaseWps
324
  wp_enqueue_script( $sUnique );
325
  }
326
 
327
- if ( $sNav == 'audit' ) {
328
  $sUnique = $con->prefix( 'datepicker' );
329
  wp_register_script(
330
  $sUnique, //TODO: use an includes services for CNDJS
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Services\Services;
5
 
6
+ /**
7
+ * @deprecated 10.1
8
+ */
9
  class ICWP_WPSF_FeatureHandler_Insights extends ICWP_WPSF_FeatureHandler_BaseWpsf {
10
 
11
  protected function onModulesLoaded() {
27
  public function getUrl_SubInsightsPage( string $subPage ) :string {
28
  return add_query_arg(
29
  [ 'inav' => sanitize_key( $subPage ) ],
30
+ $this->getUrl_AdminPage()
31
  );
32
  }
33
 
34
  protected function renderModulePage( array $aData = [] ) :string {
35
+ /** @var Shield\Modules\Insights\UI $UI */
36
+ $UI = $this->getUIHandler();
37
+ return $UI->renderPages();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  }
39
 
40
  public function insertCustomJsVars_Admin() {
44
 
45
  $con = $this->getCon();
46
  $aStdDepsJs = [ $con->prefix( 'plugin' ) ];
47
+ $iNav = Services::Request()->query( 'inav', 'overview' );
48
 
49
  $oModPlugin = $con->getModule_Plugin();
50
  $oTourManager = $oModPlugin->getTourManager();
51
+ switch ( $iNav ) {
52
 
53
  case 'importexport':
54
 
113
  $this->includeScriptIpDetect();
114
  break;
115
 
116
+ case 'notes':
117
  case 'scans':
118
  case 'audit':
119
+ case 'traffic':
120
  case 'ips':
121
  case 'debug':
 
122
  case 'users':
123
 
124
  $sAsset = 'shield-tables';
133
  wp_enqueue_script( $sUnique );
134
 
135
  $aStdDepsJs[] = $sUnique;
136
+ if ( $iNav == 'scans' ) {
137
  $sAsset = 'shield-scans';
138
  $sUnique = $con->prefix( $sAsset );
139
  wp_register_script(
146
  wp_enqueue_script( $sUnique );
147
  }
148
 
149
+ if ( $iNav == 'ips' ) {
150
  $sAsset = 'shield/ipanalyse';
151
  $sUnique = $con->prefix( $sAsset );
152
  wp_register_script(
159
  wp_enqueue_script( $sUnique );
160
  }
161
 
162
+ if ( in_array( $iNav, [ 'audit', 'traffic' ] ) ) {
163
  $sUnique = $con->prefix( 'datepicker' );
164
  wp_register_script(
165
  $sUnique, //TODO: use an includes services for CNDJS
src/features/ips.php CHANGED
@@ -4,6 +4,9 @@ use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
 
 
 
7
  class ICWP_WPSF_FeatureHandler_Ips extends ICWP_WPSF_FeatureHandler_BaseWpsf {
8
 
9
  const LIST_MANUAL_WHITE = 'MW';
@@ -138,56 +141,15 @@ class ICWP_WPSF_FeatureHandler_Ips extends ICWP_WPSF_FeatureHandler_BaseWpsf {
138
  }
139
 
140
  /**
141
- * Hooked to the plugin's main plugin_shutdown action
142
  */
143
- public function onPluginShutdown() {
144
- if ( !$this->getCon()->plugin_deleting ) {
145
- $this->addFilterIpsToWhiteList();
146
- }
147
- parent::onPluginShutdown();
148
- }
149
-
150
- protected function addFilterIpsToWhiteList() {
151
- $aIps = [];
152
- $oSp = Services::ServiceProviders();
153
-
154
- if ( @class_exists( '\MwpWorkerResponder' ) ) {
155
- foreach ( array_flip( $oSp->getIps_ManageWp( true ) ) as $sIp => $n ) {
156
- $aIps[ $sIp ] = 'ManageWP';
157
- }
158
- }
159
-
160
- if ( class_exists( 'ICWP_Plugin' ) ) {
161
- foreach ( array_flip( $oSp->getIps_iControlWP( true ) ) as $sIp => $n ) {
162
- $aIps[ $sIp ] = 'iControlWP';
163
- }
164
- }
165
-
166
- $aIps = apply_filters( 'icwp_simple_firewall_whitelist_ips', $aIps );
167
-
168
- if ( !empty( $aIps ) && is_array( $aIps ) ) {
169
- $aWhiteIps = ( new IPs\Lib\Ops\RetrieveIpsForLists() )
170
- ->setDbHandler( $this->getDbHandler_IPs() )
171
- ->white();
172
- foreach ( $aIps as $sIP => $sLabel ) {
173
- if ( !in_array( $sIP, $aWhiteIps ) ) {
174
- try {
175
- ( new IPs\Lib\Ops\AddIp() )
176
- ->setMod( $this )
177
- ->setIP( $sIP )
178
- ->toManualWhitelist( $sLabel );
179
- }
180
- catch ( Exception $oE ) {
181
- }
182
- }
183
- }
184
- }
185
  }
186
 
187
  /**
188
- * @return string
189
  */
190
- protected function getNamespaceBase() :string {
191
- return 'IPs';
192
  }
193
  }
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
7
+ /**
8
+ * @deprecated 10.1
9
+ */
10
  class ICWP_WPSF_FeatureHandler_Ips extends ICWP_WPSF_FeatureHandler_BaseWpsf {
11
 
12
  const LIST_MANUAL_WHITE = 'MW';
141
  }
142
 
143
  /**
144
+ * @return string
145
  */
146
+ protected function getNamespaceBase() :string {
147
+ return 'IPs';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  }
149
 
150
  /**
151
+ * @deprecated 10.1
152
  */
153
+ protected function addFilterIpsToWhiteList() {
 
154
  }
155
  }
src/features/license.php CHANGED
@@ -4,6 +4,9 @@ use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\License;
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
 
 
 
7
  class ICWP_WPSF_FeatureHandler_License extends ICWP_WPSF_FeatureHandler_BaseWpsf {
8
 
9
  /**
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\License;
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
7
+ /**
8
+ * @deprecated 10.1
9
+ */
10
  class ICWP_WPSF_FeatureHandler_License extends ICWP_WPSF_FeatureHandler_BaseWpsf {
11
 
12
  /**
src/features/lockdown.php CHANGED
@@ -2,6 +2,9 @@
2
 
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
 
 
 
 
5
  class ICWP_WPSF_FeatureHandler_Lockdown extends ICWP_WPSF_FeatureHandler_BaseWpsf {
6
 
7
  /**
2
 
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
 
5
+ /**
6
+ * @deprecated 10.1
7
+ */
8
  class ICWP_WPSF_FeatureHandler_Lockdown extends ICWP_WPSF_FeatureHandler_BaseWpsf {
9
 
10
  /**
src/features/login_protect.php CHANGED
@@ -4,6 +4,9 @@ use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
 
 
 
7
  class ICWP_WPSF_FeatureHandler_LoginProtect extends ICWP_WPSF_FeatureHandler_BaseWpsf {
8
 
9
  /**
@@ -286,18 +289,7 @@ class ICWP_WPSF_FeatureHandler_LoginProtect extends ICWP_WPSF_FeatureHandler_Bas
286
  wp_enqueue_style( 'wp-jquery-ui-dialog' );
287
  }
288
 
289
- /**
290
- * @return string
291
- */
292
  protected function getNamespaceBase() :string {
293
  return 'LoginGuard';
294
  }
295
-
296
- /**
297
- * @return string
298
- * @deprecated 10.0
299
- */
300
- public function getCustomLoginPath() {
301
- return $this->getOptions()->getOpt( 'rename_wplogin_path', '' );
302
- }
303
  }
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
7
+ /**
8
+ * @deprecated 10.1
9
+ */
10
  class ICWP_WPSF_FeatureHandler_LoginProtect extends ICWP_WPSF_FeatureHandler_BaseWpsf {
11
 
12
  /**
289
  wp_enqueue_style( 'wp-jquery-ui-dialog' );
290
  }
291
 
 
 
 
292
  protected function getNamespaceBase() :string {
293
  return 'LoginGuard';
294
  }
 
 
 
 
 
 
 
 
295
  }
src/features/plugin.php CHANGED
@@ -5,6 +5,9 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
5
  use FernleafSystems\Wordpress\Services\Services;
6
  use FernleafSystems\Wordpress\Services\Utilities;
7
 
 
 
 
8
  class ICWP_WPSF_FeatureHandler_Plugin extends ICWP_WPSF_FeatureHandler_BaseWpsf {
9
 
10
  /**
@@ -205,6 +208,10 @@ class ICWP_WPSF_FeatureHandler_Plugin extends ICWP_WPSF_FeatureHandler_BaseWpsf
205
  return (int)$this->getOptions()->getOpt( 'installation_time', 0 );
206
  }
207
 
 
 
 
 
208
  /**
209
  * @return string
210
  */
5
  use FernleafSystems\Wordpress\Services\Services;
6
  use FernleafSystems\Wordpress\Services\Utilities;
7
 
8
+ /**
9
+ * @deprecated 10.1
10
+ */
11
  class ICWP_WPSF_FeatureHandler_Plugin extends ICWP_WPSF_FeatureHandler_BaseWpsf {
12
 
13
  /**
208
  return (int)$this->getOptions()->getOpt( 'installation_time', 0 );
209
  }
210
 
211
+ public function isShowAdvanced() :bool {
212
+ return $this->getOptions()->isOpt( 'show_advanced', 'Y' );
213
+ }
214
+
215
  /**
216
  * @return string
217
  */
src/features/reporting.php CHANGED
@@ -3,6 +3,9 @@
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting;
5
 
 
 
 
6
  class ICWP_WPSF_FeatureHandler_Reporting extends ICWP_WPSF_FeatureHandler_BaseWpsf {
7
 
8
  /**
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting;
5
 
6
+ /**
7
+ * @deprecated 10.1
8
+ */
9
  class ICWP_WPSF_FeatureHandler_Reporting extends ICWP_WPSF_FeatureHandler_BaseWpsf {
10
 
11
  /**
src/features/sessions.php CHANGED
@@ -3,6 +3,9 @@
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Services\Services;
5
 
 
 
 
6
  class ICWP_WPSF_FeatureHandler_Sessions extends ICWP_WPSF_FeatureHandler_BaseWpsf {
7
 
8
  /**
3
  use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Services\Services;
5
 
6
+ /**
7
+ * @deprecated 10.1
8
+ */
9
  class ICWP_WPSF_FeatureHandler_Sessions extends ICWP_WPSF_FeatureHandler_BaseWpsf {
10
 
11
  /**
src/features/traffic.php CHANGED
@@ -4,6 +4,9 @@ use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic;
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
 
 
 
7
  class ICWP_WPSF_FeatureHandler_Traffic extends ICWP_WPSF_FeatureHandler_BaseWpsf {
8
 
9
  /**
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic;
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
7
+ /**
8
+ * @deprecated 10.1
9
+ */
10
  class ICWP_WPSF_FeatureHandler_Traffic extends ICWP_WPSF_FeatureHandler_BaseWpsf {
11
 
12
  /**
src/features/user_management.php CHANGED
@@ -4,6 +4,9 @@ use FernleafSystems\Wordpress\Plugin\Shield;
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
 
 
 
7
  class ICWP_WPSF_FeatureHandler_UserManagement extends \ICWP_WPSF_FeatureHandler_BaseWpsf {
8
 
9
  /**
4
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
7
+ /**
8
+ * @deprecated 10.1
9
+ */
10
  class ICWP_WPSF_FeatureHandler_UserManagement extends \ICWP_WPSF_FeatureHandler_BaseWpsf {
11
 
12
  /**
src/lib/src/Controller/Controller.php CHANGED
@@ -9,19 +9,20 @@ use FernleafSystems\Wordpress\Services\Services;
9
  /**
10
  * Class Controller
11
  * @package FernleafSystems\Wordpress\Plugin\Shield\Controller
12
- * @property bool $is_activating
13
- * @property bool $is_debug
14
- * @property bool $modules_loaded
15
- * @property bool $rebuild_options
16
- * @property bool $plugin_deactivating
17
- * @property bool $plugin_deleting
18
- * @property bool $plugin_reset
19
- * @property false|string $file_forceoff
20
- * @property string $base_file
21
- * @property string $root_file
22
- * @property bool $user_can_base_permissions
23
- * @property Shield\Modules\Events\Lib\EventsService $service_events
24
- * @property mixed[]|\ICWP_WPSF_FeatureHandler_Base[] $modules
 
25
  */
26
  class Controller {
27
 
@@ -41,6 +42,7 @@ class Controller {
41
 
42
  /**
43
  * @var string
 
44
  */
45
  private $sRootFile;
46
 
@@ -51,6 +53,7 @@ class Controller {
51
 
52
  /**
53
  * @var string
 
54
  */
55
  private $sPluginBaseFile;
56
 
@@ -69,11 +72,6 @@ class Controller {
69
  */
70
  protected static $sRequestId;
71
 
72
- /**
73
- * @var string
74
- */
75
- private $sConfigOptionsHashWhenLoaded;
76
-
77
  /**
78
  * @var bool
79
  */
@@ -100,12 +98,12 @@ class Controller {
100
  private $oEventsService;
101
 
102
  /**
103
- * @param string $sEventTag
104
- * @param array $aMetaData
105
  * @return $this
106
  */
107
- public function fireEvent( $sEventTag, $aMetaData = [] ) {
108
- $this->loadEventsService()->fireEvent( $sEventTag, $aMetaData );
109
  return $this;
110
  }
111
 
@@ -129,25 +127,28 @@ class Controller {
129
  }
130
 
131
  /**
132
- * @param string $sRootFile
133
  * @return Controller
134
  * @throws \Exception
135
  */
136
- public static function GetInstance( $sRootFile = null ) {
137
  if ( !isset( static::$oInstance ) ) {
138
- static::$oInstance = new static( $sRootFile );
 
 
 
139
  }
140
  return static::$oInstance;
141
  }
142
 
143
  /**
144
- * @param string $sRootFile
145
  * @throws \Exception
146
  */
147
- protected function __construct( $sRootFile ) {
148
- $this->sRootFile = $sRootFile;
149
- $this->root_file = $sRootFile;
150
- $this->base_file = $this->getRootFile();
151
  $this->modules = [];
152
 
153
  $this->loadServices();
@@ -195,16 +196,16 @@ class Controller {
195
  * @return array
196
  * @throws \Exception
197
  */
198
- private function readPluginSpecification() {
199
- $aSpec = [];
200
- $sContents = Services::Data()->readFileContentsUsingInclude( $this->getPathPluginSpec() );
201
- if ( !empty( $sContents ) ) {
202
- $aSpec = json_decode( $sContents, true );
203
- if ( empty( $aSpec ) ) {
204
- throw new \Exception( 'Could not load to process the plugin spec configuration.' );
205
  }
206
  }
207
- return $aSpec;
208
  }
209
 
210
  /**
@@ -538,10 +539,10 @@ class Controller {
538
  }
539
 
540
  public function onWpDashboardSetup() {
541
- $bShow = apply_filters( $this->prefix( 'show_dashboard_widget' ),
542
  $this->isValidAdminArea() && (bool)$this->getPluginSpec_Property( 'show_dashboard_widget' )
543
  );
544
- if ( $bShow ) {
545
  wp_add_dashboard_widget(
546
  $this->prefix( 'dashboard_widget' ),
547
  apply_filters( $this->prefix( 'dashboard_widget_title' ), $this->getHumanName() ),
@@ -580,29 +581,29 @@ class Controller {
580
  }
581
 
582
  public function ajaxAction() {
583
- $sNonceAction = Services::Request()->request( 'exec' );
584
- check_ajax_referer( $sNonceAction, 'exec_nonce' );
585
 
586
  ob_start();
587
- $aResponseData = apply_filters(
588
  $this->prefix( Services::WpUsers()->isUserLoggedIn() ? 'ajaxAuthAction' : 'ajaxNonAuthAction' ),
589
- [], $sNonceAction
590
  );
591
- $sNoise = ob_get_clean();
592
 
593
- if ( is_array( $aResponseData ) && isset( $aResponseData[ 'success' ] ) ) {
594
- $bSuccess = $aResponseData[ 'success' ];
595
  }
596
  else {
597
  $bSuccess = false;
598
- $aResponseData = [];
599
  }
600
 
601
  wp_send_json(
602
  [
603
  'success' => $bSuccess,
604
- 'data' => $aResponseData,
605
- 'noise' => $sNoise
606
  ]
607
  );
608
  }
@@ -887,28 +888,28 @@ class Controller {
887
  /**
888
  * This will hook into the saving of plugin update information and if there is an update for this plugin, it'll add
889
  * a data stamp to state when the update was first detected.
890
- * @param \stdClass $oPluginUpdateData
891
  * @return \stdClass
892
  */
893
- public function setUpdateFirstDetectedAt( $oPluginUpdateData ) {
894
 
895
- if ( !empty( $oPluginUpdateData ) && !empty( $oPluginUpdateData->response )
896
- && isset( $oPluginUpdateData->response[ $this->getPluginBaseFile() ] ) ) {
897
  // i.e. there's an update available
898
 
899
- $sNewVersion = Services::WpPlugins()->getUpdateNewVersion( $this->getPluginBaseFile() );
900
- if ( !empty( $sNewVersion ) ) {
901
- $oConOptions = $this->getPluginControllerOptions();
902
- if ( !isset( $oConOptions->update_first_detected ) || ( count( $oConOptions->update_first_detected ) > 3 ) ) {
903
- $oConOptions->update_first_detected = [];
904
  }
905
- if ( !isset( $oConOptions->update_first_detected[ $sNewVersion ] ) ) {
906
- $oConOptions->update_first_detected[ $sNewVersion ] = Services::Request()->ts();
907
  }
908
  }
909
  }
910
 
911
- return $oPluginUpdateData;
912
  }
913
 
914
  /**
@@ -985,27 +986,21 @@ class Controller {
985
  return $aPlugins;
986
  }
987
 
988
- /**
989
- * @return array
990
- */
991
- public function getLabels() {
992
 
993
- $aLabels = array_map( 'stripslashes', apply_filters( $this->prefix( 'plugin_labels' ), $this->getPluginSpec_Labels() ) );
994
 
995
  $oDP = Services::Data();
996
- foreach ( [ '16x16', '32x32', '128x128' ] as $sSize ) {
997
- $sKey = 'icon_url_'.$sSize;
998
- if ( !empty( $aLabels[ $sKey ] ) && !$oDP->isValidWebUrl( $aLabels[ $sKey ] ) ) {
999
- $aLabels[ $sKey ] = $this->getPluginUrl_Image( $aLabels[ $sKey ] );
1000
  }
1001
  }
1002
 
1003
- return $aLabels;
1004
  }
1005
 
1006
- /**
1007
- * Hooked to 'shutdown'
1008
- */
1009
  public function onWpShutdown() {
1010
  $this->getSiteInstallationId();
1011
  do_action( $this->prefix( 'pre_plugin_shutdown' ) );
@@ -1021,12 +1016,12 @@ class Controller {
1021
  }
1022
 
1023
  protected function deleteFlags() {
1024
- $oFS = Services::WpFs();
1025
- if ( $oFS->exists( $this->getPath_Flags( 'rebuild' ) ) ) {
1026
- $oFS->deleteFile( $this->getPath_Flags( 'rebuild' ) );
1027
  }
1028
  if ( $this->getIsResetPlugin() ) {
1029
- $oFS->deleteFile( $this->getPath_Flags( 'reset' ) );
1030
  }
1031
  }
1032
 
@@ -1151,8 +1146,8 @@ class Controller {
1151
  * @return mixed|null
1152
  */
1153
  protected function getPluginSpec_Property( string $key ) {
1154
- $aData = $this->getPluginSpec()[ 'properties' ];
1155
- return $aData[ $key ] ?? null;
1156
  }
1157
 
1158
  /**
@@ -1231,8 +1226,8 @@ class Controller {
1231
  * @return string
1232
  */
1233
  public function getHumanName() {
1234
- $aLabels = $this->getLabels();
1235
- return empty( $aLabels[ 'Name' ] ) ? $this->getPluginSpec_Property( 'human_name' ) : $aLabels[ 'Name' ];
1236
  }
1237
 
1238
  /**
@@ -1249,49 +1244,30 @@ class Controller {
1249
  return ( strpos( Services::WpGeneral()->getCurrentWpAdminPage(), $this->getPluginPrefix() ) === 0 );
1250
  }
1251
 
1252
- /**
1253
- * @return bool
1254
- */
1255
- public function getIsPage_PluginMainDashboard() {
1256
- return ( Services::WpGeneral()->getCurrentWpAdminPage() == $this->getPluginPrefix() );
1257
  }
1258
 
1259
- /**
1260
- * @return bool
1261
- */
1262
- public function getIsRebuildOptionsFromFile() {
1263
- if ( isset( $this->bRebuildOptions ) ) {
1264
- return $this->bRebuildOptions;
1265
  }
1266
 
1267
  // The first choice is to look for the file hash. If it's "always" empty, it means we could never
1268
  // hash the file in the first place so it's not ever effectively used and it falls back to the rebuild file
1269
- $oConOptions = $this->getPluginControllerOptions();
1270
- $sSpecPath = $this->getPathPluginSpec();
1271
- $sCurrentHash = @md5_file( $sSpecPath );
1272
- $sModifiedTime = Services::WpFs()->getModifiedTime( $sSpecPath );
1273
-
1274
- $this->bRebuildOptions = true;
1275
 
1276
- if ( isset( $oConOptions->hash ) && is_string( $oConOptions->hash )
1277
- && hash_equals( $oConOptions->hash, $sCurrentHash ) ) {
1278
- $this->bRebuildOptions = false;
1279
- }
1280
- elseif ( isset( $oConOptions->mod_time ) && ( $sModifiedTime < $oConOptions->mod_time ) ) {
1281
- $this->bRebuildOptions = false;
1282
- }
1283
 
1284
- $oConOptions->hash = $sCurrentHash;
1285
- $oConOptions->mod_time = $sModifiedTime;
1286
- $this->rebuild_options = $this->bRebuildOptions;
1287
- return $this->bRebuildOptions;
1288
- }
1289
-
1290
- /**
1291
- * @return bool
1292
- */
1293
- public function isUpgrading() {
1294
- return $this->getIsRebuildOptionsFromFile();
1295
  }
1296
 
1297
  public function getIsResetPlugin() :bool {
@@ -1308,37 +1284,23 @@ class Controller {
1308
  return $this->getPluginSpec_Property( 'wpms_network_admin_only' );
1309
  }
1310
 
1311
- /**
1312
- * @return string
1313
- */
1314
- public function getParentSlug() {
1315
  return $this->getPluginSpec_Property( 'slug_parent' );
1316
  }
1317
 
1318
- /**
1319
- * This is the path to the main plugin file relative to the WordPress plugins directory.
1320
- * @return string
1321
- */
1322
- public function getPluginBaseFile() {
1323
- if ( !isset( $this->sPluginBaseFile ) ) {
1324
- $this->sPluginBaseFile = plugin_basename( $this->getRootFile() );
1325
  }
1326
- return $this->sPluginBaseFile;
1327
  }
1328
 
1329
- /**
1330
- * @return string
1331
- */
1332
- public function getPluginSlug() {
1333
  return $this->getPluginSpec_Property( 'slug_plugin' );
1334
  }
1335
 
1336
- /**
1337
- * @param string $sPath
1338
- * @return string
1339
- */
1340
- public function getPluginUrl( $sPath = '' ) {
1341
- return add_query_arg( [ 'ver' => $this->getVersion() ], plugins_url( $sPath, $this->getRootFile() ) );
1342
  }
1343
 
1344
  public function getPluginUrl_Asset( string $asset ) :string {
@@ -1440,29 +1402,22 @@ class Controller {
1440
  return path_join( $this->getRootDir(), 'plugin-spec.php' );
1441
  }
1442
 
1443
- /**
1444
- * Get the root directory for the plugin with the trailing slash
1445
- * @return string
1446
- */
1447
- public function getRootDir() {
1448
  return dirname( $this->getRootFile() ).DIRECTORY_SEPARATOR;
1449
  }
1450
 
1451
- /**
1452
- * @return string
1453
- */
1454
- public function getRootFile() {
1455
- if ( empty( $this->sRootFile ) ) {
1456
- $oVO = ( new \FernleafSystems\Wordpress\Services\Utilities\WpOrg\Plugin\Files() )
1457
  ->findPluginFromFile( __FILE__ );
1458
- if ( $oVO instanceof \FernleafSystems\Wordpress\Services\Core\VOs\WpPluginVo ) {
1459
- $this->sRootFile = path_join( WP_PLUGIN_DIR, $oVO->file );
1460
  }
1461
  else {
1462
- $this->sRootFile = __FILE__;
1463
  }
1464
  }
1465
- return $this->sRootFile;
1466
  }
1467
 
1468
  /**
@@ -1498,20 +1453,14 @@ class Controller {
1498
  return $opts->previous_version;
1499
  }
1500
 
1501
- /**
1502
- * @return int
1503
- */
1504
- public function getVersionNumeric() {
1505
- $aParts = explode( '.', $this->getVersion() );
1506
- return ( $aParts[ 0 ]*100 + $aParts[ 1 ]*10 + $aParts[ 2 ] );
1507
  }
1508
 
1509
- /**
1510
- * @return string
1511
- */
1512
- public function getShieldAction() {
1513
- $sAction = sanitize_key( Services::Request()->query( 'shield_action', '' ) );
1514
- return empty( $sAction ) ? '' : $sAction;
1515
  }
1516
 
1517
  /**
@@ -1525,11 +1474,6 @@ class Controller {
1525
  self::$oControllerOptions = new \stdClass();
1526
  }
1527
 
1528
- // Used at the time of saving during WP Shutdown to determine whether saving is necessary. TODO: Extend to plugin options
1529
- if ( empty( $this->sConfigOptionsHashWhenLoaded ) ) {
1530
- $this->sConfigOptionsHashWhenLoaded = md5( serialize( self::$oControllerOptions ) );
1531
- }
1532
-
1533
  if ( $this->getIsRebuildOptionsFromFile() ) {
1534
  self::$oControllerOptions->plugin_spec = $this->readPluginSpecification();
1535
  }
@@ -1569,16 +1513,16 @@ class Controller {
1569
  }
1570
 
1571
  protected function saveCurrentPluginControllerOptions() {
1572
- $oWP = Services::WpGeneral();
1573
  add_filter( $this->prefix( 'bypass_is_plugin_admin' ), '__return_true' );
1574
  if ( $this->plugin_deleting ) {
1575
- $oWP->deleteOption( $this->getPluginControllerOptionsKey() );
1576
  }
1577
  else {
1578
- $oOptions = $this->getPluginControllerOptions();
1579
- if ( $this->sConfigOptionsHashWhenLoaded != md5( serialize( $oOptions ) ) ) {
1580
- $oWP->updateOption( $this->getPluginControllerOptionsKey(), $oOptions );
1581
- }
1582
  }
1583
  remove_filter( $this->prefix( 'bypass_is_plugin_admin' ), '__return_true' );
1584
  }
@@ -1688,7 +1632,7 @@ class Controller {
1688
 
1689
  /**
1690
  * We let the \Exception from the core plugin feature to bubble up because it's critical.
1691
- * @return \ICWP_WPSF_FeatureHandler_Plugin
1692
  * @throws \Exception from loadFeatureHandler()
1693
  */
1694
  public function loadCorePluginFeatureHandler() {
@@ -1710,15 +1654,13 @@ class Controller {
1710
  * @throws \Exception
1711
  */
1712
  public function loadAllFeatures() :bool {
1713
- $bSuccess = true;
1714
- foreach ( array_keys( $this->loadCorePluginFeatureHandler()->getActivePluginFeatures() ) as $sSlug ) {
1715
  try {
1716
- $this->getModule( $sSlug );
1717
- $bSuccess = true;
1718
  }
1719
- catch ( \Exception $oE ) {
1720
  if ( $this->isValidAdminArea() && $this->isPluginAdmin() ) {
1721
- $this->sAdminNoticeError = $oE->getMessage();
1722
  add_action( 'admin_notices', [ $this, 'adminNoticePluginFailedToLoad' ] );
1723
  add_action( 'network_admin_notices', [ $this, 'adminNoticePluginFailedToLoad' ] );
1724
  }
@@ -1756,102 +1698,118 @@ class Controller {
1756
  return $mod;
1757
  }
1758
 
1759
- public function getModule_AuditTrail() :\ICWP_WPSF_FeatureHandler_AuditTrail {
1760
  return $this->getModule( 'audit_trail' );
1761
  }
1762
 
1763
- public function getModule_Comments() :\ICWP_WPSF_FeatureHandler_CommentsFilter {
1764
  return $this->getModule( 'comments_filter' );
1765
  }
1766
 
1767
- public function getModule_Comms() :\ICWP_WPSF_FeatureHandler_Comms {
1768
  return $this->getModule( 'comms' );
1769
  }
1770
 
1771
- public function getModule_Events() :\ICWP_WPSF_FeatureHandler_Events {
 
 
 
 
1772
  return $this->getModule( 'events' );
1773
  }
1774
 
1775
- public function getModule_HackGuard() :\ICWP_WPSF_FeatureHandler_HackProtect {
1776
  return $this->getModule( 'hack_protect' );
1777
  }
1778
 
1779
- public function getModule_Insights() :\ICWP_WPSF_FeatureHandler_Insights {
1780
  return $this->getModule( 'insights' );
1781
  }
1782
 
1783
- public function getModule_IPs() :\ICWP_WPSF_FeatureHandler_Ips {
 
 
 
 
1784
  return $this->getModule( 'ips' );
1785
  }
1786
 
1787
- public function getModule_License() :\ICWP_WPSF_FeatureHandler_License {
1788
  return $this->getModule( 'license' );
1789
  }
1790
 
1791
- public function getModule_LoginGuard() :\ICWP_WPSF_FeatureHandler_LoginProtect {
1792
  return $this->getModule( 'login_protect' );
1793
  }
1794
 
1795
- public function getModule_Plugin() :\ICWP_WPSF_FeatureHandler_Plugin {
1796
  return $this->getModule( 'plugin' );
1797
  }
1798
 
1799
- public function getModule_Reporting() :\ICWP_WPSF_FeatureHandler_Reporting {
1800
  return $this->getModule( 'reporting' );
1801
  }
1802
 
1803
- public function getModule_SecAdmin() :\ICWP_WPSF_FeatureHandler_AdminAccessRestriction {
1804
  return $this->getModule( 'admin_access_restriction' );
1805
  }
1806
 
1807
- public function getModule_Sessions() :\ICWP_WPSF_FeatureHandler_Sessions {
1808
  return $this->getModule( 'sessions' );
1809
  }
1810
 
1811
- public function getModule_Traffic() :\ICWP_WPSF_FeatureHandler_Traffic {
1812
  return $this->getModule( 'traffic' );
1813
  }
1814
 
1815
- public function getModule_UserManagement() :\ICWP_WPSF_FeatureHandler_UserManagement {
1816
  return $this->getModule( 'user_management' );
1817
  }
1818
 
 
 
 
 
1819
  /**
1820
- * @param array $modProperties
1821
  * @return \ICWP_WPSF_FeatureHandler_Base|mixed
1822
  * @throws \Exception
1823
  */
1824
- public function loadFeatureHandler( array $modProperties ) {
1825
- $modSlug = $modProperties[ 'slug' ];
1826
  $mod = isset( $this->modules[ $modSlug ] ) ? $this->modules[ $modSlug ] : null;
1827
- if ( $mod instanceof \ICWP_WPSF_FeatureHandler_Base ) {
1828
  return $mod;
1829
  }
1830
 
1831
- if ( empty( $modProperties[ 'storage_key' ] ) ) {
1832
- $modProperties[ 'storage_key' ] = $modSlug;
 
 
 
1833
  }
1834
 
1835
- if ( !empty( $modProperties[ 'min_php' ] )
1836
- && !Services::Data()->getPhpVersionIsAtLeast( $modProperties[ 'min_php' ] ) ) {
1837
  return null;
1838
  }
1839
 
1840
- $sFeatureName = str_replace( ' ', '', ucwords( str_replace( '_', ' ', $modSlug ) ) );
1841
- $sOptionsVarName = sprintf( 'oFeatureHandler%s', $sFeatureName ); // e.g. oFeatureHandlerPlugin
1842
 
1843
- // e.g. \ICWP_WPSF_FeatureHandler_Plugin
1844
- $sClassName = sprintf( '%s_FeatureHandler_%s', strtoupper( $this->getPluginPrefix( '_' ) ), $sFeatureName );
 
 
1845
 
1846
  // All this to prevent fatal errors if the plugin doesn't install/upgrade correctly
1847
- if ( class_exists( $sClassName ) ) {
1848
- $this->{$sOptionsVarName} = new $sClassName( $this, $modProperties );
1849
- }
1850
- else {
1851
- $sMessage = sprintf( 'Class "%s" is missing', $sClassName );
1852
  throw new \Exception( $sMessage );
1853
  }
1854
 
 
 
1855
  $aMs = $this->modules;
1856
  $aMs[ $modSlug ] = $this->{$sOptionsVarName};
1857
  $this->modules = $aMs;
@@ -1904,18 +1862,6 @@ class Controller {
1904
  return $oRndr;
1905
  }
1906
 
1907
- /**
1908
- * Path of format -
1909
- * wp-content/languages/plugins/wp-simple-firewall-de_DE.mo
1910
- * @param string $sMoFilePath
1911
- * @param string $sDomain
1912
- * @return string
1913
- * @deprecated 10.0
1914
- */
1915
- public function overrideTranslations( $sMoFilePath, $sDomain ) {
1916
- return $sMoFilePath;
1917
- }
1918
-
1919
  /**
1920
  * @param array[] $aRegistered
1921
  * @return array[]
9
  /**
10
  * Class Controller
11
  * @package FernleafSystems\Wordpress\Plugin\Shield\Controller
12
+ * @property bool $is_activating
13
+ * @property bool $is_debug
14
+ * @property bool $modules_loaded
15
+ * @property bool $rebuild_options
16
+ * @property Shield\Modules\Integrations\Lib\MainWP\Common\MainWPVO $mwpVO
17
+ * @property bool $plugin_deactivating
18
+ * @property bool $plugin_deleting
19
+ * @property bool $plugin_reset
20
+ * @property false|string $file_forceoff
21
+ * @property string $base_file
22
+ * @property string $root_file
23
+ * @property bool $user_can_base_permissions
24
+ * @property Shield\Modules\Events\Lib\EventsService $service_events
25
+ * @property mixed[]|Shield\Modules\Base\ModCon[] $modules
26
  */
27
  class Controller {
28
 
42
 
43
  /**
44
  * @var string
45
+ * @deprecated 10.1
46
  */
47
  private $sRootFile;
48
 
53
 
54
  /**
55
  * @var string
56
+ * @deprecated 10.1
57
  */
58
  private $sPluginBaseFile;
59
 
72
  */
73
  protected static $sRequestId;
74
 
 
 
 
 
 
75
  /**
76
  * @var bool
77
  */
98
  private $oEventsService;
99
 
100
  /**
101
+ * @param string $event
102
+ * @param array $meta
103
  * @return $this
104
  */
105
+ public function fireEvent( string $event, $meta = [] ) :self {
106
+ $this->loadEventsService()->fireEvent( $event, $meta );
107
  return $this;
108
  }
109
 
127
  }
128
 
129
  /**
130
+ * @param string $rootFile
131
  * @return Controller
132
  * @throws \Exception
133
  */
134
+ public static function GetInstance( $rootFile = null ) {
135
  if ( !isset( static::$oInstance ) ) {
136
+ if ( empty( $rootFile ) ) {
137
+ throw new \Exception( 'Empty root file provided for instantiation' );
138
+ }
139
+ static::$oInstance = new static( $rootFile );
140
  }
141
  return static::$oInstance;
142
  }
143
 
144
  /**
145
+ * @param string $rootFile
146
  * @throws \Exception
147
  */
148
+ protected function __construct( string $rootFile ) {
149
+ $this->sRootFile = $rootFile;
150
+ $this->root_file = $rootFile;
151
+ $this->base_file = $this->getPluginBaseFile();
152
  $this->modules = [];
153
 
154
  $this->loadServices();
196
  * @return array
197
  * @throws \Exception
198
  */
199
+ private function readPluginSpecification() :array {
200
+ $spec = [];
201
+ $content = Services::Data()->readFileContentsUsingInclude( $this->getPathPluginSpec() );
202
+ if ( !empty( $content ) ) {
203
+ $spec = json_decode( $content, true );
204
+ if ( empty( $spec ) || !is_array( $spec ) ) {
205
+ throw new \Exception( 'Could not load plugin spec configuration.' );
206
  }
207
  }
208
+ return $spec;
209
  }
210
 
211
  /**
539
  }
540
 
541
  public function onWpDashboardSetup() {
542
+ $show = apply_filters( $this->prefix( 'show_dashboard_widget' ),
543
  $this->isValidAdminArea() && (bool)$this->getPluginSpec_Property( 'show_dashboard_widget' )
544
  );
545
+ if ( $show ) {
546
  wp_add_dashboard_widget(
547
  $this->prefix( 'dashboard_widget' ),
548
  apply_filters( $this->prefix( 'dashboard_widget_title' ), $this->getHumanName() ),
581
  }
582
 
583
  public function ajaxAction() {
584
+ $nonceAction = Services::Request()->request( 'exec' );
585
+ check_ajax_referer( $nonceAction, 'exec_nonce' );
586
 
587
  ob_start();
588
+ $response = apply_filters(
589
  $this->prefix( Services::WpUsers()->isUserLoggedIn() ? 'ajaxAuthAction' : 'ajaxNonAuthAction' ),
590
+ [], $nonceAction
591
  );
592
+ $noise = ob_get_clean();
593
 
594
+ if ( is_array( $response ) && isset( $response[ 'success' ] ) ) {
595
+ $bSuccess = $response[ 'success' ];
596
  }
597
  else {
598
  $bSuccess = false;
599
+ $response = [];
600
  }
601
 
602
  wp_send_json(
603
  [
604
  'success' => $bSuccess,
605
+ 'data' => $response,
606
+ 'noise' => $noise
607
  ]
608
  );
609
  }
888
  /**
889
  * This will hook into the saving of plugin update information and if there is an update for this plugin, it'll add
890
  * a data stamp to state when the update was first detected.
891
+ * @param \stdClass $updateData
892
  * @return \stdClass
893
  */
894
+ public function setUpdateFirstDetectedAt( $updateData ) {
895
 
896
+ if ( !empty( $updateData ) && !empty( $updateData->response )
897
+ && isset( $updateData->response[ $this->getPluginBaseFile() ] ) ) {
898
  // i.e. there's an update available
899
 
900
+ $new = Services::WpPlugins()->getUpdateNewVersion( $this->getPluginBaseFile() );
901
+ if ( !empty( $new ) ) {
902
+ $opts = $this->getPluginControllerOptions();
903
+ if ( !isset( $opts->update_first_detected ) || ( count( $opts->update_first_detected ) > 3 ) ) {
904
+ $opts->update_first_detected = [];
905
  }
906
+ if ( !isset( $opts->update_first_detected[ $new ] ) ) {
907
+ $opts->update_first_detected[ $new ] = Services::Request()->ts();
908
  }
909
  }
910
  }
911
 
912
+ return $updateData;
913
  }
914
 
915
  /**
986
  return $aPlugins;
987
  }
988
 
989
+ public function getLabels() :array {
 
 
 
990
 
991
+ $labels = array_map( 'stripslashes', apply_filters( $this->prefix( 'plugin_labels' ), $this->getPluginSpec_Labels() ) );
992
 
993
  $oDP = Services::Data();
994
+ foreach ( [ '16x16', '32x32', '128x128' ] as $dimension ) {
995
+ $key = 'icon_url_'.$dimension;
996
+ if ( !empty( $labels[ $key ] ) && !$oDP->isValidWebUrl( $labels[ $key ] ) ) {
997
+ $labels[ $key ] = $this->getPluginUrl_Image( $labels[ $key ] );
998
  }
999
  }
1000
 
1001
+ return $labels;
1002
  }
1003
 
 
 
 
1004
  public function onWpShutdown() {
1005
  $this->getSiteInstallationId();
1006
  do_action( $this->prefix( 'pre_plugin_shutdown' ) );
1016
  }
1017
 
1018
  protected function deleteFlags() {
1019
+ $FS = Services::WpFs();
1020
+ if ( $FS->exists( $this->getPath_Flags( 'rebuild' ) ) ) {
1021
+ $FS->deleteFile( $this->getPath_Flags( 'rebuild' ) );
1022
  }
1023
  if ( $this->getIsResetPlugin() ) {
1024
+ $FS->deleteFile( $this->getPath_Flags( 'reset' ) );
1025
  }
1026
  }
1027
 
1146
  * @return mixed|null
1147
  */
1148
  protected function getPluginSpec_Property( string $key ) {
1149
+ $data = $this->getPluginSpec()[ 'properties' ];
1150
+ return $data[ $key ] ?? null;
1151
  }
1152
 
1153
  /**
1226
  * @return string
1227
  */
1228
  public function getHumanName() {
1229
+ $labels = $this->getLabels();
1230
+ return empty( $labels[ 'Name' ] ) ? $this->getPluginSpec_Property( 'human_name' ) : $labels[ 'Name' ];
1231
  }
1232
 
1233
  /**
1244
  return ( strpos( Services::WpGeneral()->getCurrentWpAdminPage(), $this->getPluginPrefix() ) === 0 );
1245
  }
1246
 
1247
+ public function getIsPage_PluginMainDashboard() :bool {
1248
+ return Services::WpGeneral()->getCurrentWpAdminPage() === $this->getPluginPrefix();
 
 
 
1249
  }
1250
 
1251
+ public function getIsRebuildOptionsFromFile() :bool {
1252
+ if ( isset( $this->rebuild_options ) ) {
1253
+ return $this->rebuild_options;
 
 
 
1254
  }
1255
 
1256
  // The first choice is to look for the file hash. If it's "always" empty, it means we could never
1257
  // hash the file in the first place so it's not ever effectively used and it falls back to the rebuild file
1258
+ $opts = $this->getPluginControllerOptions();
1259
+ $specPath = $this->getPathPluginSpec();
1260
+ $specHash = @sha1_file( $specPath );
1261
+ $specModTS = Services::WpFs()->getModifiedTime( $specPath );
 
 
1262
 
1263
+ $this->rebuild_options =
1264
+ ( $this->getVersion() !== Services::WpPlugins()->getPluginAsVo( $this->getPluginBaseFile() )->Version )
1265
+ || ( empty( $opts->hash ) || !hash_equals( $opts->hash, $specHash ) )
1266
+ || ( empty( $opts->mod_time || $opts->mod_time < $specModTS ) );
 
 
 
1267
 
1268
+ $opts->hash = $specHash;
1269
+ $opts->mod_time = $specModTS;
1270
+ return $this->rebuild_options;
 
 
 
 
 
 
 
 
1271
  }
1272
 
1273
  public function getIsResetPlugin() :bool {
1284
  return $this->getPluginSpec_Property( 'wpms_network_admin_only' );
1285
  }
1286
 
1287
+ public function getParentSlug() :string {
 
 
 
1288
  return $this->getPluginSpec_Property( 'slug_parent' );
1289
  }
1290
 
1291
+ public function getPluginBaseFile() :string {
1292
+ if ( !isset( $this->base_file ) ) {
1293
+ $this->base_file = plugin_basename( $this->getRootFile() );
 
 
 
 
1294
  }
1295
+ return $this->base_file;
1296
  }
1297
 
1298
+ public function getPluginSlug() :string {
 
 
 
1299
  return $this->getPluginSpec_Property( 'slug_plugin' );
1300
  }
1301
 
1302
+ public function getPluginUrl( string $path = '' ) :string {
1303
+ return add_query_arg( [ 'ver' => $this->getVersion() ], plugins_url( $path, $this->getRootFile() ) );
 
 
 
 
1304
  }
1305
 
1306
  public function getPluginUrl_Asset( string $asset ) :string {
1402
  return path_join( $this->getRootDir(), 'plugin-spec.php' );
1403
  }
1404
 
1405
+ public function getRootDir() :string {
 
 
 
 
1406
  return dirname( $this->getRootFile() ).DIRECTORY_SEPARATOR;
1407
  }
1408
 
1409
+ public function getRootFile() :string {
1410
+ if ( empty( $this->root_file ) ) {
1411
+ $VO = ( new \FernleafSystems\Wordpress\Services\Utilities\WpOrg\Plugin\Files() )
 
 
 
1412
  ->findPluginFromFile( __FILE__ );
1413
+ if ( $VO instanceof \FernleafSystems\Wordpress\Services\Core\VOs\WpPluginVo ) {
1414
+ $this->root_file = path_join( WP_PLUGIN_DIR, $VO->file );
1415
  }
1416
  else {
1417
+ $this->root_file = __FILE__;
1418
  }
1419
  }
1420
+ return $this->root_file;
1421
  }
1422
 
1423
  /**
1453
  return $opts->previous_version;
1454
  }
1455
 
1456
+ public function getVersionNumeric() :int {
1457
+ $parts = explode( '.', $this->getVersion() );
1458
+ return (int)( $parts[ 0 ]*100 + $parts[ 1 ]*10 + $parts[ 2 ] );
 
 
 
1459
  }
1460
 
1461
+ public function getShieldAction() :string {
1462
+ $action = sanitize_key( Services::Request()->query( 'shield_action', '' ) );
1463
+ return empty( $action ) ? '' : $action;
 
 
 
1464
  }
1465
 
1466
  /**
1474
  self::$oControllerOptions = new \stdClass();
1475
  }
1476
 
 
 
 
 
 
1477
  if ( $this->getIsRebuildOptionsFromFile() ) {
1478
  self::$oControllerOptions->plugin_spec = $this->readPluginSpecification();
1479
  }
1513
  }
1514
 
1515
  protected function saveCurrentPluginControllerOptions() {
1516
+ $WP = Services::WpGeneral();
1517
  add_filter( $this->prefix( 'bypass_is_plugin_admin' ), '__return_true' );
1518
  if ( $this->plugin_deleting ) {
1519
+ $WP->deleteOption( $this->getPluginControllerOptionsKey() );
1520
  }
1521
  else {
1522
+ $WP->updateOption(
1523
+ $this->getPluginControllerOptionsKey(),
1524
+ $this->getPluginControllerOptions()
1525
+ );
1526
  }
1527
  remove_filter( $this->prefix( 'bypass_is_plugin_admin' ), '__return_true' );
1528
  }
1632
 
1633
  /**
1634
  * We let the \Exception from the core plugin feature to bubble up because it's critical.
1635
+ * @return Shield\Modules\Plugin\ModCon
1636
  * @throws \Exception from loadFeatureHandler()
1637
  */
1638
  public function loadCorePluginFeatureHandler() {
1654
  * @throws \Exception
1655
  */
1656
  public function loadAllFeatures() :bool {
1657
+ foreach ( array_keys( $this->loadCorePluginFeatureHandler()->getActivePluginFeatures() ) as $slug ) {
 
1658
  try {
1659
+ $this->getModule( $slug );
 
1660
  }
1661
+ catch ( \Exception $e ) {
1662
  if ( $this->isValidAdminArea() && $this->isPluginAdmin() ) {
1663
+ $this->sAdminNoticeError = $e->getMessage();
1664
  add_action( 'admin_notices', [ $this, 'adminNoticePluginFailedToLoad' ] );
1665
  add_action( 'network_admin_notices', [ $this, 'adminNoticePluginFailedToLoad' ] );
1666
  }
1698
  return $mod;
1699
  }
1700
 
1701
+ public function getModule_AuditTrail() :Shield\Modules\AuditTrail\ModCon {
1702
  return $this->getModule( 'audit_trail' );
1703
  }
1704
 
1705
+ public function getModule_Comments() :Shield\Modules\CommentsFilter\ModCon {
1706
  return $this->getModule( 'comments_filter' );
1707
  }
1708
 
1709
+ public function getModule_Comms() :Shield\Modules\Comms\ModCon {
1710
  return $this->getModule( 'comms' );
1711
  }
1712
 
1713
+ public function getModule_Email() :Shield\Modules\Email\ModCon {
1714
+ return $this->getModule( 'email' );
1715
+ }
1716
+
1717
+ public function getModule_Events() :Shield\Modules\Events\ModCon {
1718
  return $this->getModule( 'events' );
1719
  }
1720
 
1721
+ public function getModule_HackGuard() :Shield\Modules\HackGuard\ModCon {
1722
  return $this->getModule( 'hack_protect' );
1723
  }
1724
 
1725
+ public function getModule_Insights() :Shield\Modules\Insights\ModCon {
1726
  return $this->getModule( 'insights' );
1727
  }
1728
 
1729
+ public function getModule_Integrations() :Shield\Modules\Integrations\ModCon {
1730
+ return $this->getModule( 'integrations' );
1731
+ }
1732
+
1733
+ public function getModule_IPs() :Shield\Modules\IPs\ModCon {
1734
  return $this->getModule( 'ips' );
1735
  }
1736
 
1737
+ public function getModule_License() :Shield\Modules\License\ModCon {
1738
  return $this->getModule( 'license' );
1739
  }
1740
 
1741
+ public function getModule_LoginGuard() :Shield\Modules\LoginGuard\ModCon {
1742
  return $this->getModule( 'login_protect' );
1743
  }
1744
 
1745
+ public function getModule_Plugin() :Shield\Modules\Plugin\ModCon {
1746
  return $this->getModule( 'plugin' );
1747
  }
1748
 
1749
+ public function getModule_Reporting() :Shield\Modules\Reporting\ModCon {
1750
  return $this->getModule( 'reporting' );
1751
  }
1752
 
1753
+ public function getModule_SecAdmin() :Shield\Modules\SecurityAdmin\ModCon {
1754
  return $this->getModule( 'admin_access_restriction' );
1755
  }
1756
 
1757
+ public function getModule_Sessions() :Shield\Modules\Sessions\ModCon {
1758
  return $this->getModule( 'sessions' );
1759
  }
1760
 
1761
+ public function getModule_Traffic() :Shield\Modules\Traffic\ModCon {
1762
  return $this->getModule( 'traffic' );
1763
  }
1764
 
1765
+ public function getModule_UserManagement() :Shield\Modules\UserManagement\ModCon {
1766
  return $this->getModule( 'user_management' );
1767
  }
1768
 
1769
+ public function getModulesNamespace() :string {
1770
+ return '\FernleafSystems\Wordpress\Plugin\Shield\Modules';
1771
+ }
1772
+
1773
  /**
1774
+ * @param array $modProps
1775
  * @return \ICWP_WPSF_FeatureHandler_Base|mixed
1776
  * @throws \Exception
1777
  */
1778
+ public function loadFeatureHandler( array $modProps ) {
1779
+ $modSlug = $modProps[ 'slug' ];
1780
  $mod = isset( $this->modules[ $modSlug ] ) ? $this->modules[ $modSlug ] : null;
1781
+ if ( $mod instanceof \ICWP_WPSF_FeatureHandler_Base || $mod instanceof Shield\Modules\Base\ModCon ) {
1782
  return $mod;
1783
  }
1784
 
1785
+ if ( empty( $modProps[ 'storage_key' ] ) ) {
1786
+ $modProps[ 'storage_key' ] = $modSlug;
1787
+ }
1788
+ if ( empty( $modProps[ 'namespace' ] ) ) {
1789
+ $modProps[ 'namespace' ] = str_replace( ' ', '', ucwords( str_replace( '_', ' ', $modSlug ) ) );
1790
  }
1791
 
1792
+ if ( !empty( $modProps[ 'min_php' ] )
1793
+ && !Services::Data()->getPhpVersionIsAtLeast( $modProps[ 'min_php' ] ) ) {
1794
  return null;
1795
  }
1796
 
1797
+ $modName = $modProps[ 'namespace' ];
1798
+ $sOptionsVarName = sprintf( 'oFeatureHandler%s', $modName ); // e.g. oFeatureHandlerPlugin
1799
 
1800
+ $className = $this->getModulesNamespace().sprintf( '\\%s\\ModCon', $modName );
1801
+ if ( !@class_exists( $className ) ) {
1802
+ $className = sprintf( '%s_FeatureHandler_%s', strtoupper( $this->getPluginPrefix( '_' ) ), $modName );
1803
+ }
1804
 
1805
  // All this to prevent fatal errors if the plugin doesn't install/upgrade correctly
1806
+ if ( !class_exists( $className ) ) {
1807
+ $sMessage = sprintf( 'Class "%s" is missing', $className );
 
 
 
1808
  throw new \Exception( $sMessage );
1809
  }
1810
 
1811
+ $this->{$sOptionsVarName} = new $className( $this, $modProps );
1812
+
1813
  $aMs = $this->modules;
1814
  $aMs[ $modSlug ] = $this->{$sOptionsVarName};
1815
  $this->modules = $aMs;
1862
  return $oRndr;
1863
  }
1864
 
 
 
 
 
 
 
 
 
 
 
 
 
1865
  /**
1866
  * @param array[] $aRegistered
1867
  * @return array[]
src/lib/src/Controller/Utilities/Upgrade.php CHANGED
@@ -16,7 +16,7 @@ class Upgrade {
16
  foreach ( $con->modules as $mod ) {
17
  $H = $mod->getUpgradeHandler();
18
  if ( $H instanceof Shield\Modules\Base\Upgrade ) {
19
- $H->setMod( $mod )->execute();
20
  }
21
  }
22
  }
16
  foreach ( $con->modules as $mod ) {
17
  $H = $mod->getUpgradeHandler();
18
  if ( $H instanceof Shield\Modules\Base\Upgrade ) {
19
+ $H->execute();
20
  }
21
  }
22
  }
src/lib/src/Crons/PluginCronsConsumer.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Crons;
4
+
5
+ trait PluginCronsConsumer {
6
+
7
+ public function runDailyCron() {
8
+ }
9
+
10
+ public function runHourlyCron() {
11
+ }
12
+
13
+ protected function setupCronHooks() {
14
+ add_action( $this->getCon()->prefix( 'daily_cron' ), [ $this, 'runDailyCron' ] );
15
+ add_action( $this->getCon()->prefix( 'hourly_cron' ), [ $this, 'runHourlyCron' ] );
16
+ }
17
+ }
src/lib/src/Databases/AdminNotes/Handler.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\AdminNotes;
4
 
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\AdminNotes;
4
 
src/lib/src/Databases/Base/BaseQuery.php CHANGED
@@ -210,13 +210,13 @@ abstract class BaseQuery {
210
  }
211
 
212
  /**
213
- * @param int $nTs
214
- * @param string $sComparison
215
  * @return $this
216
  */
217
- public function filterByCreatedAt( $nTs, $sComparison ) {
218
- if ( !preg_match( '#[^=<>]#', $sComparison ) && is_numeric( $nTs ) ) {
219
- $this->addWhere( 'created_at', (int)$nTs, $sComparison );
220
  }
221
  return $this;
222
  }
210
  }
211
 
212
  /**
213
+ * @param int $ts
214
+ * @param string $comparisonOp
215
  * @return $this
216
  */
217
+ public function filterByCreatedAt( $ts, $comparisonOp ) {
218
+ if ( !preg_match( '#[^=<>]#', $comparisonOp ) && is_numeric( $ts ) ) {
219
+ $this->addWhere( 'created_at', (int)$ts, $comparisonOp );
220
  }
221
  return $this;
222
  }
src/lib/src/Databases/Base/Handler.php CHANGED
@@ -248,62 +248,4 @@ abstract class Handler {
248
  unset( $this->bIsReady );
249
  return $this;
250
  }
251
-
252
- /**
253
- * @return string[]
254
- * @deprecated 10.0
255
- */
256
- public function enumerateColumns() :array {
257
- return $this->getTableSchema()->enumerateColumns();
258
- }
259
-
260
- /**
261
- * @return bool
262
- * @throws \Exception
263
- * @deprecated 10.0
264
- */
265
- protected function verifyTableStructure() {
266
- return true;
267
- }
268
-
269
- /**
270
- * @return string[]
271
- * @deprecated 10.0
272
- */
273
- public function getColumns() :array {
274
- return $this->getTableSchema()->getColumnNames();
275
- }
276
-
277
- /**
278
- * @return string[]
279
- * @deprecated 10.0
280
- */
281
- public function getColumnsDefinition() :array {
282
- return $this->getTableSchema()->enumerateColumns();
283
- }
284
-
285
- /**
286
- * @return string[]
287
- * @deprecated 10.0
288
- */
289
- public function getColumnsActual() {
290
- return Services::WpDb()->getColumnsForTable( $this->getTable(), 'strtolower' );
291
- }
292
-
293
- /**
294
- * @return string
295
- * @throws \Exception
296
- * @deprecated 10.0
297
- */
298
- protected function getDefaultCreateTableSql() :string {
299
- throw new \Exception( 'No SQL' );
300
- }
301
-
302
- /**
303
- * @return string
304
- * @deprecated 10.0
305
- */
306
- public function getSqlCreate() {
307
- return $this->getDefaultCreateTableSql();
308
- }
309
  }
248
  unset( $this->bIsReady );
249
  return $this;
250
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  }
src/lib/src/Databases/Base/Insert.php CHANGED
@@ -13,10 +13,7 @@ class Insert extends BaseQuery {
13
 
14
  public function getInsertData() :array {
15
  $dbh = $this->getDbH();
16
- $cols = method_exists( $dbh, 'getTableSchema' ) ?
17
- $dbh->getTableSchema()->getColumnNames()
18
- : $dbh->getColumnsActual();
19
-
20
  return array_intersect_key(
21
  is_array( $this->aInsertData ) ? $this->aInsertData : [],
22
  array_flip( $cols )
@@ -41,12 +38,10 @@ class Insert extends BaseQuery {
41
  $data = [];
42
  }
43
 
44
- $dbh = $this->getDbH();
45
- $cols = method_exists( $dbh, 'getTableSchema' ) ?
46
- $dbh->getTableSchema()->getColumnNames()
47
- : $dbh->getColumnsActual();
48
-
49
- $this->aInsertData = array_intersect_key( $data, array_flip( $cols ) );
50
  return $this;
51
  }
52
 
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 )
38
  $data = [];
39
  }
40
 
41
+ $this->aInsertData = array_intersect_key(
42
+ $data,
43
+ array_flip( $this->getDbH()->getTableSchema()->getColumnNames() )
44
+ );
 
 
45
  return $this;
46
  }
47
 
src/lib/src/Databases/IPs/CommonFilters.php CHANGED
@@ -2,14 +2,16 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\IPs;
4
 
 
 
5
  trait CommonFilters {
6
 
7
  /**
8
- * @param string $sIp
9
  * @return $this
10
  */
11
- public function filterByIp( $sIp ) {
12
- return $this->addWhereEquals( 'ip', $sIp );
13
  }
14
 
15
  /**
@@ -25,8 +27,8 @@ trait CommonFilters {
25
  */
26
  public function filterByBlacklist() {
27
  return $this->filterByLists( [
28
- \ICWP_WPSF_FeatureHandler_Ips::LIST_AUTO_BLACK,
29
- \ICWP_WPSF_FeatureHandler_Ips::LIST_MANUAL_BLACK
30
  ] );
31
  }
32
 
@@ -34,7 +36,7 @@ trait CommonFilters {
34
  * @return $this
35
  */
36
  public function filterByWhitelist() {
37
- return $this->filterByList( \ICWP_WPSF_FeatureHandler_Ips::LIST_MANUAL_WHITE );
38
  }
39
 
40
  /**
@@ -45,6 +47,10 @@ trait CommonFilters {
45
  return $this->addWhereEquals( 'is_range', $bIsRange ? 1 : 0 );
46
  }
47
 
 
 
 
 
48
  /**
49
  * @param string $nLastAccessAfter
50
  * @return $this
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\IPs;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\ModCon;
6
+
7
  trait CommonFilters {
8
 
9
  /**
10
+ * @param string $ip
11
  * @return $this
12
  */
13
+ public function filterByIp( $ip ) {
14
+ return $this->addWhereEquals( 'ip', $ip );
15
  }
16
 
17
  /**
27
  */
28
  public function filterByBlacklist() {
29
  return $this->filterByLists( [
30
+ ModCon::LIST_AUTO_BLACK,
31
+ ModCon::LIST_MANUAL_BLACK
32
  ] );
33
  }
34
 
36
  * @return $this
37
  */
38
  public function filterByWhitelist() {
39
+ return $this->filterByList( ModCon::LIST_MANUAL_WHITE );
40
  }
41
 
42
  /**
47
  return $this->addWhereEquals( 'is_range', $bIsRange ? 1 : 0 );
48
  }
49
 
50
+ public function filterByLabel( string $label ) :self {
51
+ return $this->addWhereEquals( 'label', $label );
52
+ }
53
+
54
  /**
55
  * @param string $nLastAccessAfter
56
  * @return $this
src/lib/src/Databases/IPs/Handler.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\IPs;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Ips\Options;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
@@ -25,7 +26,7 @@ class Handler extends Base\Handler {
25
  public function deleteRowsOlderThan( $nTimeStamp ) {
26
  return $this->getQueryDeleter()
27
  ->addWhereOlderThan( $nTimeStamp, 'last_access_at' )
28
- ->addWhere( 'list', \ICWP_WPSF_FeatureHandler_Ips::LIST_MANUAL_WHITE, '!=' )
29
  ->query();
30
  }
31
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\IPs;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\ModCon;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Ips\Options;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
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
 
src/lib/src/Databases/Reports/Common.php CHANGED
@@ -1,24 +1,16 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Reports;
4
 
5
  trait Common {
6
 
7
- /**
8
- * @param string $sFrequency
9
- * @return $this
10
- */
11
- public function filterByFrequency( $sFrequency ) {
12
- return $this->addWhere( 'frequency', $sFrequency );
13
  }
14
 
15
- /**
16
- * @param string $sType
17
- * @return $this
18
- */
19
- public function filterByType( $sType ) {
20
- if ( in_array( $sType, [ Handler::TYPE_INFO, Handler::TYPE_ALERT ] ) ) {
21
- $this->addWhere( 'type', $sType );
22
  }
23
  return $this;
24
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Reports;
4
 
5
  trait Common {
6
 
7
+ public function filterByFrequency( string $freq ) :self {
8
+ return $this->addWhere( 'frequency', $freq );
 
 
 
 
9
  }
10
 
11
+ public function filterByType( string $type ) :self {
12
+ if ( in_array( $type, [ Handler::TYPE_INFO, Handler::TYPE_ALERT ] ) ) {
13
+ $this->addWhere( 'type', $type );
 
 
 
 
14
  }
15
  return $this;
16
  }
src/lib/src/Databases/Reports/Handler.php CHANGED
@@ -3,7 +3,6 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Reports;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Options;
7
 
8
  class Handler extends Base\Handler {
9
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Reports;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
 
6
 
7
  class Handler extends Base\Handler {
8
 
src/lib/src/Modules/AuditTrail/AjaxHandler.php CHANGED
@@ -5,7 +5,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail;
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
- class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
9
 
10
  protected function processAjaxAction( string $action ) :array {
11
 
@@ -25,12 +25,9 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
25
  return $aResponse;
26
  }
27
 
28
- /**
29
- * @return array
30
- */
31
- protected function ajaxExec_AddParamToFirewallWhitelist() {
32
- /** @var \ICWP_WPSF_FeatureHandler_AuditTrail $oMod */
33
- $oMod = $this->getMod();
34
  $bSuccess = false;
35
 
36
  $nId = Services::Request()->post( 'rid' );
@@ -39,9 +36,9 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
39
  }
40
  else {
41
  /** @var Shield\Databases\AuditTrail\EntryVO $oEntry */
42
- $oEntry = $oMod->getDbHandler_AuditTrail()
43
- ->getQuerySelector()
44
- ->byId( $nId );
45
 
46
  if ( empty( $oEntry ) ) {
47
  $sMessage = __( 'Audit entry could not be loaded.', 'wp-simple-firewall' );
@@ -54,7 +51,7 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
54
  $sMessage = __( 'Parameter associated with this audit entry could not be found.', 'wp-simple-firewall' );
55
  }
56
  else {
57
- /** @var \ICWP_WPSF_FeatureHandler_Firewall $oModFire */
58
  $oModFire = $this->getCon()->getModule( 'firewall' );
59
  $oModFire->addParamToWhitelist( $sParam, $sUri );
60
  $sMessage = sprintf( __( 'Parameter "%s" whitelisted successfully', 'wp-simple-firewall' ), $sParam );
@@ -69,15 +66,12 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
69
  ];
70
  }
71
 
72
- /**
73
- * @return array
74
- */
75
- private function ajaxExec_BuildTableAuditTrail() {
76
- /** @var \ICWP_WPSF_FeatureHandler_AuditTrail $oMod */
77
- $oMod = $this->getMod();
78
  $oTableBuilder = ( new Shield\Tables\Build\AuditTrail() )
79
- ->setMod( $oMod )
80
- ->setDbHandler( $oMod->getDbHandler_AuditTrail() );
81
 
82
  return [
83
  'success' => true,
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
+ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
9
 
10
  protected function processAjaxAction( string $action ) :array {
11
 
25
  return $aResponse;
26
  }
27
 
28
+ protected function ajaxExec_AddParamToFirewallWhitelist() :array {
29
+ /** @var ModCon $mod */
30
+ $mod = $this->getMod();
 
 
 
31
  $bSuccess = false;
32
 
33
  $nId = Services::Request()->post( 'rid' );
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' );
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 );
66
  ];
67
  }
68
 
69
+ private function ajaxExec_BuildTableAuditTrail() :array {
70
+ /** @var ModCon $mod */
71
+ $mod = $this->getMod();
 
 
 
72
  $oTableBuilder = ( new Shield\Tables\Build\AuditTrail() )
73
+ ->setMod( $mod )
74
+ ->setDbHandler( $mod->getDbHandler_AuditTrail() );
75
 
76
  return [
77
  'success' => true,
src/lib/src/Modules/AuditTrail/Auditors/Upgrades.php ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\Auditors;
4
+
5
+ use FernleafSystems\Wordpress\Services\Services;
6
+
7
+ class Upgrades extends Base {
8
+
9
+ /**
10
+ * @var array
11
+ */
12
+ private $plugins;
13
+
14
+ /**
15
+ * @var array
16
+ */
17
+ private $themes;
18
+
19
+ public function run() {
20
+ $this->init();
21
+ add_action( 'upgrader_process_complete', [ $this, 'auditUpgrades' ], 10, 2 );
22
+ /* add_action( 'upgrader_post_install', [ $this, 'auditUpgrade' ], 10, 3 ); */
23
+ }
24
+
25
+ private function init() {
26
+ $this->plugins = array_map( function ( $plugin ) {
27
+ return $plugin[ 'Version' ];
28
+ }, Services::WpPlugins()->getPlugins() );
29
+ $this->themes = array_map( function ( $theme ) {
30
+ return $theme->get( 'Version' );
31
+ }, Services::WpThemes()->getThemes() );
32
+ }
33
+
34
+ /**
35
+ * @param \WP_Upgrader $handler
36
+ * @param array $data
37
+ */
38
+ public function auditUpgrades( $handler, $data ) {
39
+ $action = $data[ 'action' ] ?? null;
40
+ $type = $data[ 'type' ] ?? null;
41
+ if ( $action === 'update' && in_array( $type, [ 'plugin', 'theme' ] ) ) {
42
+ if ( !empty( $data[ 'plugins' ] ) && is_array( $data[ 'plugins' ] ) ) {
43
+ foreach ( $data[ 'plugins' ] as $item ) {
44
+ if ( isset( $this->plugins[ $item ] ) ) {
45
+ $this->handlePlugin( $item );
46
+ }
47
+ }
48
+ }
49
+ elseif ( !empty( $data[ 'themes' ] ) && is_array( $data[ 'themes' ] ) ) {
50
+ foreach ( $data[ 'themes' ] as $item ) {
51
+ if ( isset( $this->themes[ $item ] ) ) {
52
+ $this->handleTheme( $item );
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ private function handlePlugin( string $item ) {
60
+ $WPP = Services::WpPlugins();
61
+ $VO = $WPP->getPluginAsVo( $item, true );
62
+ $this->getCon()->fireEvent(
63
+ 'plugin_upgraded',
64
+ [
65
+ 'audit' => [
66
+ 'file' => $VO->Name,
67
+ 'from' => $this->plugins[ $item ],
68
+ 'to' => $VO->Version,
69
+ ]
70
+ ]
71
+ );
72
+ }
73
+
74
+ private function handleTheme( string $item ) {
75
+ $WPT = Services::WpThemes();
76
+ $VO = $WPT->getThemeAsVo( $item, true );
77
+ $this->getCon()->fireEvent(
78
+ 'theme_upgraded',
79
+ [
80
+ 'audit' => [
81
+ 'file' => $VO->Name,
82
+ 'from' => $this->themes[ $item ],
83
+ 'to' => $VO->Version,
84
+ ]
85
+ ]
86
+ );
87
+ }
88
+ }
src/lib/src/Modules/AuditTrail/Auditors/Users.php CHANGED
@@ -2,44 +2,43 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\Auditors;
4
 
 
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
7
  class Users extends Base {
8
 
 
 
9
  public function run() {
10
- add_action( 'wp_login', [ $this, 'auditUserLoginSuccess' ] );
11
  add_action( 'user_register', [ $this, 'auditNewUserRegistered' ] );
12
  add_action( 'delete_user', [ $this, 'auditDeleteUser' ], 30, 2 );
13
  }
14
 
15
- /**
16
- * @param string $sUsername
17
- */
18
- public function auditUserLoginSuccess( $sUsername ) {
19
- if ( !empty( $sUsername ) ) {
20
- $this->getCon()->fireEvent(
21
- 'user_login',
22
- [
23
- 'audit' => [
24
- 'user' => sanitize_user( $sUsername ),
25
- ]
26
  ]
27
- );
28
- }
29
  }
30
 
31
- /**
32
- * @param int $nUserId
33
- */
34
- public function auditNewUserRegistered( $nUserId ) {
35
- $oUser = empty( $nUserId ) ? null : Services::WpUsers()->getUserById( $nUserId );
36
- if ( $oUser instanceof \WP_User ) {
37
  $this->getCon()->fireEvent(
38
  'user_registered',
39
  [
40
  'audit' => [
41
- 'user' => sanitize_user( $oUser->user_login ),
42
- 'email' => $oUser->user_email,
43
  ]
44
  ]
45
  );
@@ -47,20 +46,20 @@ class Users extends Base {
47
  }
48
 
49
  /**
50
- * @param int $nUserId
51
  * @param int $nReassigned
52
  */
53
- public function auditDeleteUser( $nUserId, $nReassigned ) {
54
  $oWpUsers = Services::WpUsers();
55
 
56
- $oUser = empty( $nUserId ) ? null : $oWpUsers->getUserById( $nUserId );
57
- if ( $oUser instanceof \WP_User ) {
58
  $this->getCon()->fireEvent(
59
  'user_deleted',
60
  [
61
  'audit' => [
62
- 'user' => sanitize_user( $oUser->user_login ),
63
- 'email' => $oUser->user_email,
64
  ]
65
  ]
66
  );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\Auditors;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Consumer\WpLoginCapture;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class Users extends Base {
9
 
10
+ use WpLoginCapture;
11
+
12
  public function run() {
13
+ $this->setupLoginCaptureHooks();
14
  add_action( 'user_register', [ $this, 'auditNewUserRegistered' ] );
15
  add_action( 'delete_user', [ $this, 'auditDeleteUser' ], 30, 2 );
16
  }
17
 
18
+ protected function captureLogin( \WP_User $user ) {
19
+ $this->auditUserLoginSuccess( $user );
20
+ }
21
+
22
+ public function auditUserLoginSuccess( \WP_User $user ) {
23
+ $this->getCon()->fireEvent(
24
+ 'user_login',
25
+ [
26
+ 'audit' => [
27
+ 'user' => $user->user_login,
 
28
  ]
29
+ ]
30
+ );
31
  }
32
 
33
+ public function auditNewUserRegistered( int $userID ) {
34
+ $user = empty( $userID ) ? null : Services::WpUsers()->getUserById( $userID );
35
+ if ( $user instanceof \WP_User ) {
 
 
 
36
  $this->getCon()->fireEvent(
37
  'user_registered',
38
  [
39
  'audit' => [
40
+ 'user' => sanitize_user( $user->user_login ),
41
+ 'email' => $user->user_email,
42
  ]
43
  ]
44
  );
46
  }
47
 
48
  /**
49
+ * @param int $userID
50
  * @param int $nReassigned
51
  */
52
+ public function auditDeleteUser( $userID, $nReassigned ) {
53
  $oWpUsers = Services::WpUsers();
54
 
55
+ $user = empty( $userID ) ? null : $oWpUsers->getUserById( $userID );
56
+ if ( $user instanceof \WP_User ) {
57
  $this->getCon()->fireEvent(
58
  'user_deleted',
59
  [
60
  'audit' => [
61
+ 'user' => sanitize_user( $user->user_login ),
62
+ 'email' => $user->user_email,
63
  ]
64
  ]
65
  );
src/lib/src/Modules/AuditTrail/Insights/OverviewCards.php CHANGED
@@ -3,13 +3,14 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\Insights;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
 
6
 
7
  class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
8
 
9
  public function build() :array {
10
- /** @var \ICWP_WPSF_FeatureHandler_AuditTrail $mod */
11
  $mod = $this->getMod();
12
- /** @var Shield\Modules\AuditTrail\Options $opts */
13
  $opts = $this->getOptions();
14
 
15
  $cardSection = [
@@ -24,40 +25,12 @@ class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
24
  $data[ 'key_opts' ][ 'mod' ] = $this->getModDisabledCard();
25
  }
26
  else {
27
- $aAudit = [];
28
- $aNonAudit = [];
29
- $opts->isAuditShield() ? $aAudit[] = 'Shield' : $aNonAudit[] = 'Shield';
30
- $opts->isAuditUsers() ? $aAudit[] = __( 'users', 'wp-simple-firewall' ) : $aNonAudit[] = __( 'users', 'wp-simple-firewall' );
31
- $opts->isAuditPlugins() ? $aAudit[] = __( 'plugins', 'wp-simple-firewall' ) : $aNonAudit[] = __( 'plugins', 'wp-simple-firewall' );
32
- $opts->isAuditThemes() ? $aAudit[] = __( 'themes', 'wp-simple-firewall' ) : $aNonAudit[] = __( 'themes', 'wp-simple-firewall' );
33
- $opts->isAuditPosts() ? $aAudit[] = __( 'posts', 'wp-simple-firewall' ) : $aNonAudit[] = __( 'posts', 'wp-simple-firewall' );
34
- $opts->isAuditEmails() ? $aAudit[] = __( 'emails', 'wp-simple-firewall' ) : $aNonAudit[] = __( 'emails', 'wp-simple-firewall' );
35
- $opts->isAuditWp() ? $aAudit[] = 'WP' : $aNonAudit[] = 'WP';
36
-
37
- if ( empty( $aNonAudit ) ) {
38
- $cards[ 'audit' ] = [
39
- 'name' => __( 'Audit Areas', 'wp-simple-firewall' ),
40
- 'state' => 1,
41
- 'summary' => __( 'All important events on your site are being logged', 'wp-simple-firewall' ),
42
- 'href' => $mod->getUrl_DirectLinkToOption( 'section_enable_audit_contexts' ),
43
- ];
44
- }
45
- elseif ( empty( $aAudit ) ) {
46
- $cards[ 'audit' ] = [
47
- 'name' => __( 'Audit Areas', 'wp-simple-firewall' ),
48
- 'state' => -1,
49
- 'summary' => sprintf( __( 'No areas are set to be audited: %s', 'wp-simple-firewall' ), implode( ', ', $aAudit ) ),
50
- 'href' => $mod->getUrl_DirectLinkToSection( 'section_enable_audit_contexts' ),
51
- ];
52
- }
53
- else {
54
- $cards[ 'nonaudit' ] = [
55
- 'name' => __( 'Audit Events', 'wp-simple-firewall' ),
56
- 'state' => 0,
57
- 'summary' => sprintf( __( "Important events aren't being audited: %s", 'wp-simple-firewall' ), implode( ', ', $aNonAudit ) ),
58
- 'href' => $mod->getUrl_DirectLinkToSection( 'section_enable_audit_contexts' ),
59
- ];
60
- }
61
 
62
  $cards[ 'audit_length' ] = [
63
  'name' => __( 'Audit Trail', 'wp-simple-firewall' ),
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\Insights;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail;
7
 
8
  class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
9
 
10
  public function build() :array {
11
+ /** @var AuditTrail\ModCon $mod */
12
  $mod = $this->getMod();
13
+ /** @var AuditTrail\Options $opts */
14
  $opts = $this->getOptions();
15
 
16
  $cardSection = [
25
  $data[ 'key_opts' ][ 'mod' ] = $this->getModDisabledCard();
26
  }
27
  else {
28
+ $cards[ 'audit' ] = [
29
+ 'name' => __( 'Audit Areas', 'wp-simple-firewall' ),
30
+ 'state' => 1,
31
+ 'summary' => __( 'All important events on your site are being logged', 'wp-simple-firewall' ),
32
+ 'href' => $mod->getUrl_DirectLinkToOption( 'section_enable_audit_contexts' ),
33
+ ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
  $cards[ 'audit_length' ] = [
36
  'name' => __( 'Audit Trail', 'wp-simple-firewall' ),
src/lib/src/Modules/AuditTrail/Lib/AuditWriter.php CHANGED
@@ -17,16 +17,16 @@ class AuditWriter extends EventsListener {
17
  private $aAuditLogs;
18
 
19
  /**
20
- * @param string $sEvent
21
  * @param array $aMeta
22
  */
23
- protected function captureEvent( $sEvent, $aMeta = [] ) {
24
  $oCon = $this->getCon();
25
- $aDef = $oCon->loadEventsService()->getEventDef( $sEvent );
26
  if ( $aDef[ 'audit' ] && empty( $aMeta[ 'suppress_audit' ] ) ) { // only audit if it's an auditable event
27
  $oEntry = new AuditTrail\EntryVO();
28
  $oEntry->rid = $this->getCon()->getShortRequestId();
29
- $oEntry->event = $sEvent;
30
  $oEntry->category = $aDef[ 'cat' ];
31
  $oEntry->context = $aDef[ 'context' ];
32
  $oEntry->meta = isset( $aMeta[ 'audit' ] ) ? $aMeta[ 'audit' ] : [];
@@ -38,7 +38,7 @@ class AuditWriter extends EventsListener {
38
  $aLogs[] = $oEntry;
39
  }
40
  else {
41
- $aLogs[ $sEvent ] = $oEntry;
42
  }
43
 
44
  $this->setLogs( $aLogs );
17
  private $aAuditLogs;
18
 
19
  /**
20
+ * @param string $evt
21
  * @param array $aMeta
22
  */
23
+ protected function captureEvent( $evt, $aMeta = [] ) {
24
  $oCon = $this->getCon();
25
+ $aDef = $oCon->loadEventsService()->getEventDef( $evt );
26
  if ( $aDef[ 'audit' ] && empty( $aMeta[ 'suppress_audit' ] ) ) { // only audit if it's an auditable event
27
  $oEntry = new AuditTrail\EntryVO();
28
  $oEntry->rid = $this->getCon()->getShortRequestId();
29
+ $oEntry->event = $evt;
30
  $oEntry->category = $aDef[ 'cat' ];
31
  $oEntry->context = $aDef[ 'context' ];
32
  $oEntry->meta = isset( $aMeta[ 'audit' ] ) ? $aMeta[ 'audit' ] : [];
38
  $aLogs[] = $oEntry;
39
  }
40
  else {
41
+ $aLogs[ $evt ] = $oEntry;
42
  }
43
 
44
  $this->setLogs( $aLogs );
src/lib/src/Modules/AuditTrail/ModCon.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ 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
+ /**
16
+ * @return bool
17
+ * @throws \Exception
18
+ */
19
+ protected function isReadyToExecute() :bool {
20
+ return $this->getDbHandler_AuditTrail()->isReady() && parent::isReadyToExecute();
21
+ }
22
+
23
+ /**
24
+ * @return array
25
+ */
26
+ public function getAllContexts() {
27
+ return [
28
+ 'all' => 'All', //special
29
+ 'wpsf' => $this->getCon()->getHumanName(),
30
+ 'wordpress' => 'WordPress',
31
+ 'users' => 'Users',
32
+ 'posts' => 'Posts',
33
+ 'plugins' => 'Plugins',
34
+ 'themes' => 'Themes',
35
+ 'emails' => 'Emails',
36
+ ];
37
+ }
38
+
39
+ /**
40
+ * See plugin controller for the nature of $aData wpPrivacyExport()
41
+ *
42
+ * @param array $aExportItems
43
+ * @param string $sEmail
44
+ * @param int $nPage
45
+ * @return array
46
+ */
47
+ public function onWpPrivacyExport( $aExportItems, $sEmail, $nPage = 1 ) {
48
+
49
+ $oUser = Services::WpUsers()->getUserByEmail( $sEmail );
50
+
51
+ $aExportItem = [
52
+ 'group_id' => $this->prefix(),
53
+ 'group_label' => sprintf( __( '[%s] Audit Trail Entries', 'wp-simple-firewall' ), $this->getCon()
54
+ ->getHumanName() ),
55
+ 'item_id' => $this->prefix( 'audit-trail' ),
56
+ 'data' => [],
57
+ ];
58
+
59
+ try {
60
+ /** @var Shield\Databases\AuditTrail\Select $oFinder */
61
+ $oFinder = $this->getDbHandler_AuditTrail()->getQuerySelector();
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
+
74
+ if ( !empty( $aExportItem[ 'data' ] ) ) {
75
+ $aExportItems[] = $aExportItem;
76
+ }
77
+ }
78
+ catch ( \Exception $e ) {
79
+ }
80
+
81
+ return $aExportItems;
82
+ }
83
+
84
+ /**
85
+ * See plugin controller for the nature of $aData wpPrivacyErase()
86
+ *
87
+ * @param array $aData
88
+ * @param string $sEmail
89
+ * @param int $nPage
90
+ * @return array
91
+ */
92
+ public function onWpPrivacyErase( $aData, $sEmail, $nPage = 1 ) {
93
+ try {
94
+ $oThisUsername = Services::WpUsers()->getUserByEmail( $sEmail )->user_login;
95
+ $this->getDbHandler_AuditTrail()
96
+ ->getQueryDeleter()
97
+ ->addWhereSearch( 'wp_username', $oThisUsername )
98
+ ->all();
99
+ $aData[ 'messages' ][] = sprintf( '%s Audit Entries deleted', $this->getCon()->getHumanName() );
100
+ }
101
+ catch ( \Exception $e ) {
102
+ }
103
+ return $aData;
104
+ }
105
+ }
src/lib/src/Modules/AuditTrail/Options.php CHANGED
@@ -2,22 +2,11 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
- use FernleafSystems\Wordpress\Services\Services;
7
 
8
- class Options extends Base\ShieldOptions {
9
 
10
- /**
11
- * @return string
12
- */
13
- public function getDbTable_ChangeTracking() {
14
- return $this->getCon()->prefixOption( $this->getDef( 'table_name_changetracking' ) );
15
- }
16
-
17
- /**
18
- * @return int
19
- */
20
- public function getAutoCleanDays() {
21
  return (int)$this->getOpt( 'audit_trail_auto_clean' );
22
  }
23
 
@@ -29,105 +18,65 @@ class Options extends Base\ShieldOptions {
29
 
30
  /**
31
  * @return bool
 
32
  */
33
- public function isEnabledChangeTracking() {
34
- return !$this->isOpt( 'enable_change_tracking', 'disabled' );
35
- }
36
-
37
- /**
38
- * @return int
39
- */
40
- public function getCTSnapshotsPerWeek() {
41
- return (int)$this->getOpt( 'ct_snapshots_per_week', 7 );
42
- }
43
-
44
- /**
45
- * @return int
46
- */
47
- public function getCTMaxSnapshots() {
48
- return (int)$this->getOpt( 'ct_max_snapshots', 28 );
49
- }
50
-
51
- /**
52
- * @return int
53
- */
54
- public function getCTSnapshotInterval() {
55
- return WEEK_IN_SECONDS/$this->getCTSnapshotsPerWeek();
56
- }
57
-
58
- /**
59
- * @return int
60
- */
61
- public function getCTLastSnapshotAt() {
62
- return $this->getOpt( 'ct_last_snapshot_at' );
63
- }
64
-
65
- /**
66
- * @return bool
67
- */
68
- public function isCTSnapshotDue() {
69
- return ( Services::Request()->ts() - $this->getCTLastSnapshotAt() > $this->getCTSnapshotInterval() );
70
  }
71
 
72
  /**
73
  * @return bool
 
74
  */
75
- public function isEnabledAuditing() {
76
- return $this->isAuditEmails()
77
- || $this->isAuditPlugins()
78
- || $this->isAuditThemes()
79
- || $this->isAuditPosts()
80
- || $this->isAuditShield()
81
- || $this->isAuditUsers()
82
- || $this->isAuditWp();
83
- }
84
-
85
- /**
86
- * @return bool
87
- */
88
- public function isAuditEmails() {
89
  return $this->isOpt( 'enable_audit_context_emails', 'Y' );
90
  }
91
 
92
  /**
93
  * @return bool
 
94
  */
95
- public function isAuditPlugins() {
96
  return $this->isOpt( 'enable_audit_context_plugins', 'Y' );
97
  }
98
 
99
  /**
100
  * @return bool
 
101
  */
102
- public function isAuditPosts() {
103
  return $this->isOpt( 'enable_audit_context_posts', 'Y' );
104
  }
105
 
106
  /**
107
  * @return bool
 
108
  */
109
- public function isAuditShield() {
110
  return $this->isOpt( 'enable_audit_context_wpsf', 'Y' );
111
  }
112
 
113
  /**
114
  * @return bool
 
115
  */
116
- public function isAuditThemes() {
117
  return $this->isOpt( 'enable_audit_context_themes', 'Y' );
118
  }
119
 
120
  /**
121
  * @return bool
 
122
  */
123
- public function isAuditUsers() {
124
  return $this->isOpt( 'enable_audit_context_users', 'Y' );
125
  }
126
 
127
  /**
128
  * @return bool
 
129
  */
130
- public function isAuditWp() {
131
  return $this->isOpt( 'enable_audit_context_wordpress', 'Y' );
132
  }
133
 
@@ -137,12 +86,4 @@ class Options extends Base\ShieldOptions {
137
  public function updateCTLastSnapshotAt() {
138
  return $this->setOptAt( 'ct_last_snapshot_at' );
139
  }
140
-
141
- /**
142
- * @return string
143
- * @deprecated 10.0
144
- */
145
- public function getDbTable_AuditTrail() {
146
- return $this->getCon()->prefixOption( $this->getDef( 'audit_trail_table_name' ) );
147
- }
148
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
6
 
7
+ class Options extends BaseShield\Options {
8
 
9
+ public function getAutoCleanDays() :int {
 
 
 
 
 
 
 
 
 
 
10
  return (int)$this->getOpt( 'audit_trail_auto_clean' );
11
  }
12
 
18
 
19
  /**
20
  * @return bool
21
+ * @deprecated 10.1
22
  */
23
+ public function isEnabledAuditing() :bool {
24
+ return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  }
26
 
27
  /**
28
  * @return bool
29
+ * @deprecated 10.1
30
  */
31
+ public function isAuditEmails() :bool {
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  return $this->isOpt( 'enable_audit_context_emails', 'Y' );
33
  }
34
 
35
  /**
36
  * @return bool
37
+ * @deprecated 10.1
38
  */
39
+ public function isAuditPlugins() :bool {
40
  return $this->isOpt( 'enable_audit_context_plugins', 'Y' );
41
  }
42
 
43
  /**
44
  * @return bool
45
+ * @deprecated 10.1
46
  */
47
+ public function isAuditPosts() :bool {
48
  return $this->isOpt( 'enable_audit_context_posts', 'Y' );
49
  }
50
 
51
  /**
52
  * @return bool
53
+ * @deprecated 10.1
54
  */
55
+ public function isAuditShield() :bool {
56
  return $this->isOpt( 'enable_audit_context_wpsf', 'Y' );
57
  }
58
 
59
  /**
60
  * @return bool
61
+ * @deprecated 10.1
62
  */
63
+ public function isAuditThemes() :bool {
64
  return $this->isOpt( 'enable_audit_context_themes', 'Y' );
65
  }
66
 
67
  /**
68
  * @return bool
69
+ * @deprecated 10.1
70
  */
71
+ public function isAuditUsers() :bool {
72
  return $this->isOpt( 'enable_audit_context_users', 'Y' );
73
  }
74
 
75
  /**
76
  * @return bool
77
+ * @deprecated 10.1
78
  */
79
+ public function isAuditWp() :bool {
80
  return $this->isOpt( 'enable_audit_context_wordpress', 'Y' );
81
  }
82
 
86
  public function updateCTLastSnapshotAt() {
87
  return $this->setOptAt( 'ct_last_snapshot_at' );
88
  }
 
 
 
 
 
 
 
 
89
  }
src/lib/src/Modules/AuditTrail/Processor.php ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
+
8
+ class Processor extends BaseShield\Processor {
9
+
10
+ /**
11
+ * @var Lib\AuditWriter
12
+ */
13
+ private $auditWriter;
14
+
15
+ protected function run() {
16
+ $this->initAuditors();
17
+ $this->getSubProAuditor()->execute();
18
+ }
19
+
20
+ /**
21
+ * @return Lib\AuditWriter
22
+ */
23
+ private function loadAuditorWriter() :Lib\AuditWriter {
24
+ if ( !isset( $this->auditWriter ) ) {
25
+ /** @var ModCon $mod */
26
+ $mod = $this->getMod();
27
+ $this->auditWriter = ( new Lib\AuditWriter( $this->getCon() ) )
28
+ ->setDbHandler( $mod->getDbHandler_AuditTrail() );
29
+ }
30
+ return $this->auditWriter;
31
+ }
32
+
33
+ private function initAuditors() {
34
+ $this->loadAuditorWriter()->setIfCommit( true );
35
+
36
+ ( new Auditors\Users() )
37
+ ->setMod( $this->getMod() )
38
+ ->run();
39
+ ( new Auditors\Plugins() )
40
+ ->setMod( $this->getMod() )
41
+ ->run();
42
+ ( new Auditors\Themes() )
43
+ ->setMod( $this->getMod() )
44
+ ->run();
45
+ ( new Auditors\Wordpress() )
46
+ ->setMod( $this->getMod() )
47
+ ->run();
48
+ ( new Auditors\Posts() )
49
+ ->setMod( $this->getMod() )
50
+ ->run();
51
+ ( new Auditors\Emails() )
52
+ ->setMod( $this->getMod() )
53
+ ->run();
54
+ ( new Auditors\Upgrades() )
55
+ ->setMod( $this->getMod() )
56
+ ->run();
57
+ }
58
+
59
+ /**
60
+ * @return \ICWP_WPSF_Processor_AuditTrail_Auditor|mixed
61
+ */
62
+ public function getSubProAuditor() {
63
+ return new \ICWP_WPSF_Processor_AuditTrail_Auditor( $this->getMod() );
64
+ }
65
+ }
src/lib/src/Modules/AuditTrail/Strings.php CHANGED
@@ -20,12 +20,18 @@ class Strings extends Base\Strings {
20
  'plugin_file_edited' => [
21
  __( 'An attempt was made to edit the plugin file "%s" directly through the WordPress editor.', 'wp-simple-firewall' )
22
  ],
 
 
 
23
  'theme_activated' => [
24
  __( 'Theme "%s" was activated.', 'wp-simple-firewall' )
25
  ],
26
  'theme_file_edited' => [
27
  __( 'An attempt was made to edit the theme file "%s" directly through the WordPress editor.', 'wp-simple-firewall' )
28
  ],
 
 
 
29
  'core_updated' => [
30
  __( 'WordPress Core was updated from "%s" to "%s".', 'wp-simple-firewall' )
31
  ],
20
  'plugin_file_edited' => [
21
  __( 'An attempt was made to edit the plugin file "%s" directly through the WordPress editor.', 'wp-simple-firewall' )
22
  ],
23
+ 'plugin_upgraded' => [
24
+ __( 'Plugin "%s" was upgraded from version %s to version %s.', 'wp-simple-firewall' )
25
+ ],
26
  'theme_activated' => [
27
  __( 'Theme "%s" was activated.', 'wp-simple-firewall' )
28
  ],
29
  'theme_file_edited' => [
30
  __( 'An attempt was made to edit the theme file "%s" directly through the WordPress editor.', 'wp-simple-firewall' )
31
  ],
32
+ 'theme_upgraded' => [
33
+ __( 'Theme "%s" was upgraded from version %s to version %s.', 'wp-simple-firewall' )
34
+ ],
35
  'core_updated' => [
36
  __( 'WordPress Core was updated from "%s" to "%s".', 'wp-simple-firewall' )
37
  ],
src/lib/src/Modules/AuditTrail/UI.php CHANGED
@@ -4,13 +4,13 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
8
 
9
- class UI extends Base\ShieldUI {
10
 
11
- public function buildInsightsVars() :array {
12
  $con = $this->getCon();
13
- /** @var \ICWP_WPSF_FeatureHandler_AuditTrail $mod */
14
  $mod = $this->getMod();
15
  /** @var Databases\AuditTrail\Select $dbSel */
16
  $dbSel = $mod->getDbHandler_AuditTrail()->getQuerySelector();
@@ -20,33 +20,44 @@ class UI extends Base\ShieldUI {
20
  $aEventsSelect = array_intersect_key( $oEventStrings->getEventNames(), array_flip( $dbSel->getDistinctEvents() ) );
21
  asort( $aEventsSelect );
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  return [
24
- 'ajax' => [
25
- 'render_table_audittrail' => $mod->getAjaxActionData( 'render_table_audittrail', true ),
26
- 'item_addparamwhite' => $mod->getAjaxActionData( 'item_addparamwhite', true )
27
- ],
28
- 'flags' => [],
29
- 'strings' => [
30
- 'table_title' => sprintf( '%s: %s', __( 'Logs', 'wp-simple-firewall' ), __( 'Audit Trail', 'wp-simple-firewall' ) ),
31
- 'sub_title' => __( 'Use the Audit Trail Glossary for help interpreting log entries.', 'wp-simple-firewall' ),
32
- 'audit_trail_glossary' => __( 'Audit Trail Glossary', 'wp-simple-firewall' ),
33
- 'title_filter_form' => __( 'Audit Trail Filters', 'wp-simple-firewall' ),
34
- 'username_ignores' => __( "Providing a username will cause the 'logged-in' filter to be ignored.", 'wp-simple-firewall' ),
35
- 'exclude_your_ip' => __( 'Exclude Your Current IP', 'wp-simple-firewall' ),
36
- 'exclude_your_ip_tooltip' => __( 'Exclude Your IP From Results', 'wp-simple-firewall' ),
37
- 'context' => __( 'Context', 'wp-simple-firewall' ),
38
- 'event' => __( 'Event', 'wp-simple-firewall' ),
39
- 'show_after' => __( 'show results that occurred after', 'wp-simple-firewall' ),
40
- 'show_before' => __( 'show results that occurred before', 'wp-simple-firewall' ),
41
- ],
42
- 'vars' => [
43
- 'events_for_select' => $aEventsSelect,
44
- 'unique_ips' => $dbSel->getDistinctIps(),
45
- 'unique_users' => $dbSel->getDistinctUsernames(),
46
- ],
47
- 'hrefs' => [
48
- 'audit_trail_glossary' => 'https://shsec.io/audittrailglossary',
49
- ],
50
  ];
51
  }
52
  }
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
8
 
9
+ class UI extends BaseShield\UI {
10
 
11
+ public function renderAuditTrailTable() :string {
12
  $con = $this->getCon();
13
+ /** @var ModCon $mod */
14
  $mod = $this->getMod();
15
  /** @var Databases\AuditTrail\Select $dbSel */
16
  $dbSel = $mod->getDbHandler_AuditTrail()->getQuerySelector();
20
  $aEventsSelect = array_intersect_key( $oEventStrings->getEventNames(), array_flip( $dbSel->getDistinctEvents() ) );
21
  asort( $aEventsSelect );
22
 
23
+ return $this->getMod()
24
+ ->renderTemplate(
25
+ '/wpadmin_pages/insights/audit/audit_table.twig',
26
+ [
27
+ 'ajax' => [
28
+ 'render_table_audittrail' => $mod->getAjaxActionData( 'render_table_audittrail', true ),
29
+ 'item_addparamwhite' => $mod->getAjaxActionData( 'item_addparamwhite', true )
30
+ ],
31
+ 'flags' => [],
32
+ 'strings' => [
33
+ 'table_title' => sprintf( '%s: %s', __( 'Logs', 'wp-simple-firewall' ), __( 'Audit Trail', 'wp-simple-firewall' ) ),
34
+ 'sub_title' => __( 'Use the Audit Trail Glossary for help interpreting log entries.', 'wp-simple-firewall' ),
35
+ 'title_filter_form' => __( 'Audit Trail Filters', 'wp-simple-firewall' ),
36
+ 'username_ignores' => __( "Providing a username will cause the 'logged-in' filter to be ignored.", 'wp-simple-firewall' ),
37
+ 'exclude_your_ip' => __( 'Exclude Your Current IP', 'wp-simple-firewall' ),
38
+ 'exclude_your_ip_tooltip' => __( 'Exclude Your IP From Results', 'wp-simple-firewall' ),
39
+ 'context' => __( 'Context', 'wp-simple-firewall' ),
40
+ 'event' => __( 'Event', 'wp-simple-firewall' ),
41
+ 'show_after' => __( 'show results that occurred after', 'wp-simple-firewall' ),
42
+ 'show_before' => __( 'show results that occurred before', 'wp-simple-firewall' ),
43
+ ],
44
+ 'vars' => [
45
+ 'events_for_select' => $aEventsSelect,
46
+ 'unique_ips' => $dbSel->getDistinctIps(),
47
+ 'unique_users' => $dbSel->getDistinctUsernames(),
48
+ ],
49
+ ],
50
+ true
51
+ );
52
+ }
53
+
54
+ protected function getSettingsRelatedLinks() :array {
55
+ $modInsights = $this->getCon()->getModule_Insights();
56
  return [
57
+ [
58
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'audit' ),
59
+ 'title' => __( 'View Audit Trail', 'wp-simple-firewall' ),
60
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  ];
62
  }
63
  }
src/lib/src/Modules/AuditTrail/WpCli.php CHANGED
@@ -10,7 +10,7 @@ class WpCli extends Base\WpCli {
10
  /**
11
  * @inheritDoc
12
  */
13
- protected function getCmdHandlers() {
14
  return [
15
  new AuditTrail\WpCli\Display()
16
  ];
10
  /**
11
  * @inheritDoc
12
  */
13
+ protected function getCmdHandlers() :array {
14
  return [
15
  new AuditTrail\WpCli\Display()
16
  ];
src/lib/src/Modules/AuditTrail/WpCli/Display.php CHANGED
@@ -2,6 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\WpCli;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Tables;
7
  use WP_CLI;
@@ -63,11 +64,11 @@ class Display extends Base\WpCli\BaseWpCliCmd {
63
  * @throws WP_CLI\ExitException
64
  */
65
  public function cmdDisplay( array $null, array $aA ) {
66
- /** @var \ICWP_WPSF_FeatureHandler_AuditTrail $oMod */
67
- $oMod = $this->getMod();
68
  $oTableBuilder = ( new Tables\Build\AuditTrail() )
69
- ->setMod( $oMod )
70
- ->setDbHandler( $oMod->getDbHandler_AuditTrail() );
71
  ( new Tables\Render\WpCliTable\AuditTrail() )
72
  ->setDataBuilder( $oTableBuilder )
73
  ->render();
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\WpCli;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\ModCon;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Tables;
8
  use WP_CLI;
64
  * @throws WP_CLI\ExitException
65
  */
66
  public function cmdDisplay( array $null, array $aA ) {
67
+ /** @var ModCon $mod */
68
+ $mod = $this->getMod();
69
  $oTableBuilder = ( new Tables\Build\AuditTrail() )
70
+ ->setMod( $mod )
71
+ ->setDbHandler( $mod->getDbHandler_AuditTrail() );
72
  ( new Tables\Render\WpCliTable\AuditTrail() )
73
  ->setDataBuilder( $oTableBuilder )
74
  ->render();
src/lib/src/Modules/Autoupdates/AjaxHandler.php CHANGED
@@ -1,9 +1,9 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Autoupdates;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
 
7
- class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
8
 
9
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Autoupdates;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
 
7
+ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
8
 
9
  }
src/lib/src/Modules/Autoupdates/ModCon.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Autoupdates;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+
7
+ class ModCon extends BaseShield\ModCon {
8
+
9
+ }
src/lib/src/Modules/Autoupdates/Options.php CHANGED
@@ -2,10 +2,10 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Autoupdates;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
- class Options extends Base\ShieldOptions {
9
 
10
  /**
11
  * @return array
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Autoupdates;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
+ class Options extends BaseShield\Options {
9
 
10
  /**
11
  * @return array
src/lib/src/Modules/Autoupdates/Processor.php ADDED
@@ -0,0 +1,453 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Autoupdates;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class Processor extends BaseShield\Processor {
9
+
10
+ /**
11
+ * @var array
12
+ */
13
+ private $assetsVersions = [];
14
+
15
+ /**
16
+ * The allow_* core filters are run first in a "should_update" query. Then comes the "auto_update_core"
17
+ * filter. What this filter decides will ultimately determine the fate of any core upgrade.
18
+ */
19
+ protected function run() {
20
+ /** @var Options $oOpts */
21
+ $oOpts = $this->getOptions();
22
+
23
+ $nPriority = $this->getHookPriority();
24
+ if ( Services::WpGeneral()->isClassicPress() ) {
25
+ add_filter( 'allow_patch_auto_core_updates', [ $this, 'autoupdate_core_minor' ], $nPriority );
26
+ add_filter( 'allow_minor_auto_core_updates', [ $this, 'autoupdate_core_major' ], $nPriority );
27
+ }
28
+ else {
29
+ add_filter( 'allow_minor_auto_core_updates', [ $this, 'autoupdate_core_minor' ], $nPriority );
30
+ add_filter( 'allow_major_auto_core_updates', [ $this, 'autoupdate_core_major' ], $nPriority );
31
+ }
32
+
33
+ add_filter( 'auto_update_plugin', [ $this, 'autoupdate_plugins' ], $nPriority, 2 );
34
+ add_filter( 'auto_update_theme', [ $this, 'autoupdate_themes' ], $nPriority, 2 );
35
+ add_filter( 'auto_update_core', [ $this, 'autoupdate_core' ], $nPriority, 2 );
36
+
37
+ if ( !$oOpts->isDisableAllAutoUpdates() ) {
38
+ //more parameter options here for later
39
+ add_filter( 'auto_core_update_send_email', [ $this, 'autoupdate_send_email' ], $nPriority, 1 );
40
+ add_filter( 'auto_core_update_email', [ $this, 'autoupdate_email_override' ], $nPriority, 1 );
41
+ add_filter( 'auto_plugin_theme_update_email', [ $this, 'autoupdate_email_override' ], $nPriority, 1 );
42
+
43
+ add_action( 'set_site_transient_update_core', [ $this, 'trackUpdateTimesCore' ] );
44
+ add_action( 'set_site_transient_update_plugins', [ $this, 'trackUpdateTimesPlugins' ] );
45
+ add_action( 'set_site_transient_update_themes', [ $this, 'trackUpdateTimesThemes' ] );
46
+
47
+ if ( $oOpts->isSendAutoupdatesNotificationEmail()
48
+ && !Services::WpGeneral()->getWordpressIsAtLeastVersion( '5.5' ) ) {
49
+ $this->trackAssetsVersions();
50
+ add_action( 'automatic_updates_complete', [ $this, 'sendNotificationEmail' ] );
51
+ }
52
+ }
53
+ }
54
+
55
+ public function onWpLoaded() {
56
+ /** @var Options $opts */
57
+ $opts = $this->getOptions();
58
+ if ( $opts->isDisableAllAutoUpdates() ) {
59
+ $this->disableAllAutoUpdates();
60
+ }
61
+ }
62
+
63
+ private function disableAllAutoUpdates() {
64
+ remove_all_filters( 'automatic_updater_disabled' );
65
+ add_filter( 'automatic_updater_disabled', '__return_true', PHP_INT_MAX );
66
+ if ( !defined( 'WP_AUTO_UPDATE_CORE' ) ) {
67
+ define( 'WP_AUTO_UPDATE_CORE', false );
68
+ }
69
+ }
70
+
71
+ private function trackAssetsVersions() {
72
+ $aAssVers = $this->getTrackedAssetsVersions();
73
+
74
+ $oWpPlugins = Services::WpPlugins();
75
+ foreach ( array_keys( $oWpPlugins->getUpdates() ) as $sFile ) {
76
+ $aAssVers[ 'plugins' ][ $sFile ] = $oWpPlugins->getPluginAsVo( $sFile )->Version;
77
+ }
78
+ $oWpThemes = Services::WpThemes();
79
+ foreach ( array_keys( $oWpThemes->getUpdates() ) as $sFile ) {
80
+ $aAssVers[ 'themes' ][ $sFile ] = $oWpThemes->getTheme( $sFile )->get( 'Version' );
81
+ }
82
+ $this->assetsVersions = $aAssVers;
83
+ }
84
+
85
+ protected function getTrackedAssetsVersions() :array {
86
+ if ( empty( $this->assetsVersions ) || !is_array( $this->assetsVersions ) ) {
87
+ $this->assetsVersions = [
88
+ 'plugins' => [],
89
+ 'themes' => [],
90
+ ];
91
+ }
92
+ return $this->assetsVersions;
93
+ }
94
+
95
+ /**
96
+ * @param \stdClass $oUpdates
97
+ */
98
+ public function trackUpdateTimesCore( $oUpdates ) {
99
+
100
+ if ( !empty( $oUpdates ) && isset( $oUpdates->updates ) && is_array( $oUpdates->updates ) ) {
101
+ /** @var Options $oOpts */
102
+ $oOpts = $this->getOptions();
103
+
104
+ $aTk = $oOpts->getDelayTracking();
105
+ $aItemTk = isset( $aTk[ 'core' ][ 'wp' ] ) ? $aTk[ 'core' ][ 'wp' ] : [];
106
+ foreach ( $oUpdates->updates as $oUpdate ) {
107
+ if ( 'autoupdate' == $oUpdate->response ) {
108
+ $sVersion = $oUpdate->current;
109
+ if ( !isset( $aItemTk[ $sVersion ] ) ) {
110
+ $aItemTk[ $sVersion ] = Services::Request()->ts();
111
+ }
112
+ }
113
+ }
114
+ $aTk[ 'core' ][ 'wp' ] = array_slice( $aItemTk, -5 );
115
+ $oOpts->setDelayTracking( $aTk );
116
+ }
117
+ }
118
+
119
+ /**
120
+ * @param \stdClass $oUpdates
121
+ */
122
+ public function trackUpdateTimesPlugins( $oUpdates ) {
123
+ $this->trackUpdateTimeCommon( $oUpdates, 'plugins' );
124
+ }
125
+
126
+ /**
127
+ * @param \stdClass $oUpdates
128
+ */
129
+ public function trackUpdateTimesThemes( $oUpdates ) {
130
+ $this->trackUpdateTimeCommon( $oUpdates, 'themes' );
131
+ }
132
+
133
+ /**
134
+ * @param \stdClass $oUpdates
135
+ * @param string $sContext - plugins/themes
136
+ */
137
+ protected function trackUpdateTimeCommon( $oUpdates, $sContext ) {
138
+ /** @var Options $oOpts */
139
+ $oOpts = $this->getOptions();
140
+
141
+ if ( !empty( $oUpdates ) && isset( $oUpdates->response ) && is_array( $oUpdates->response ) ) {
142
+
143
+ $aTk = $oOpts->getDelayTracking();
144
+ foreach ( $oUpdates->response as $sSlug => $oUpdate ) {
145
+ $aItemTk = isset( $aTk[ $sContext ][ $sSlug ] ) ? $aTk[ $sContext ][ $sSlug ] : [];
146
+ if ( is_array( $oUpdate ) ) {
147
+ $oUpdate = (object)$oUpdate;
148
+ }
149
+
150
+ $sNewVersion = isset( $oUpdate->new_version ) ? $oUpdate->new_version : '';
151
+ if ( !empty( $sNewVersion ) ) {
152
+ if ( !isset( $aItemTk[ $sNewVersion ] ) ) {
153
+ $aItemTk[ $sNewVersion ] = Services::Request()->ts();
154
+ }
155
+ $aTk[ $sContext ][ $sSlug ] = array_slice( $aItemTk, -3 );
156
+ }
157
+ }
158
+ $oOpts->setDelayTracking( $aTk );
159
+ }
160
+ }
161
+
162
+ /**
163
+ * This is a filter method designed to say whether a major core WordPress upgrade should be permitted,
164
+ * based on the plugin settings.
165
+ * @param bool $bUpdate
166
+ * @return bool
167
+ */
168
+ public function autoupdate_core_major( $bUpdate ) {
169
+ /** @var Options $oOpts */
170
+ $oOpts = $this->getOptions();
171
+
172
+ if ( $oOpts->isDisableAllAutoUpdates() || $oOpts->isAutoUpdateCoreNever() ) {
173
+ $bUpdate = false;
174
+ }
175
+ elseif ( !$oOpts->isDelayUpdates() ) { // delay handled elsewhere
176
+ $bUpdate = $oOpts->isAutoUpdateCoreMajor();
177
+ }
178
+
179
+ return $bUpdate;
180
+ }
181
+
182
+ /**
183
+ * This is a filter method designed to say whether a minor core WordPress upgrade should be permitted,
184
+ * based on the plugin settings.
185
+ * @param bool $bUpdate
186
+ * @return bool
187
+ */
188
+ public function autoupdate_core_minor( $bUpdate ) {
189
+ /** @var Options $oOpts */
190
+ $oOpts = $this->getOptions();
191
+
192
+ if ( $oOpts->isDisableAllAutoUpdates() || $oOpts->isAutoUpdateCoreNever() ) {
193
+ $bUpdate = false;
194
+ }
195
+ elseif ( !$oOpts->isDelayUpdates() ) {
196
+ $bUpdate = !$oOpts->isAutoUpdateCoreNever();
197
+ }
198
+ return $bUpdate;
199
+ }
200
+
201
+ /**
202
+ * @param bool $bDoAutoUpdate
203
+ * @param \stdClass $oCoreUpdate
204
+ * @return bool
205
+ */
206
+ public function autoupdate_core( $bDoAutoUpdate, $oCoreUpdate ) {
207
+ /** @var Options $oOpts */
208
+ $oOpts = $this->getOptions();
209
+
210
+ if ( $oOpts->isDisableAllAutoUpdates() ) {
211
+ $bDoAutoUpdate = false;
212
+ }
213
+ elseif ( $this->isDelayed( $oCoreUpdate, 'core' ) ) {
214
+ $bDoAutoUpdate = false;
215
+ }
216
+
217
+ return $bDoAutoUpdate;
218
+ }
219
+
220
+ /**
221
+ * @param bool $bDoAutoUpdate
222
+ * @param \stdClass|string $mItem
223
+ * @return bool
224
+ */
225
+ public function autoupdate_plugins( $bDoAutoUpdate, $mItem ) {
226
+ /** @var Options $oOpts */
227
+ $oOpts = $this->getOptions();
228
+
229
+ if ( $oOpts->isDisableAllAutoUpdates() ) {
230
+ $bDoAutoUpdate = false;
231
+ }
232
+ else {
233
+ $file = Services::WpGeneral()->getFileFromAutomaticUpdateItem( $mItem );
234
+
235
+ if ( $this->isDelayed( $file, 'plugins' ) ) {
236
+ $bDoAutoUpdate = false;
237
+ }
238
+ elseif ( $oOpts->isAutoupdateAllPlugins() ) {
239
+ $bDoAutoUpdate = true;
240
+ }
241
+ elseif ( $file === $this->getCon()->getPluginBaseFile() ) {
242
+ $sAuto = $oOpts->getSelfAutoUpdateOpt();
243
+ if ( $sAuto === 'immediate' ) {
244
+ $bDoAutoUpdate = true;
245
+ }
246
+ elseif ( $sAuto === 'disabled' ) {
247
+ $bDoAutoUpdate = false;
248
+ }
249
+ }
250
+ }
251
+
252
+ return $bDoAutoUpdate;
253
+ }
254
+
255
+ /**
256
+ * @param bool $bDoAutoUpdate
257
+ * @param \stdClass|string $mItem
258
+ * @return bool
259
+ */
260
+ public function autoupdate_themes( $bDoAutoUpdate, $mItem ) {
261
+ /** @var Options $opts */
262
+ $opts = $this->getOptions();
263
+
264
+ if ( $opts->isDisableAllAutoUpdates() ) {
265
+ $bDoAutoUpdate = false;
266
+ }
267
+ else {
268
+ $file = Services::WpGeneral()->getFileFromAutomaticUpdateItem( $mItem, 'theme' );
269
+
270
+ if ( $this->isDelayed( $file, 'themes' ) ) {
271
+ $bDoAutoUpdate = false;
272
+ }
273
+ elseif ( $opts->isOpt( 'enable_autoupdate_themes', 'Y' ) ) {
274
+ $bDoAutoUpdate = true;
275
+ }
276
+ }
277
+
278
+ return $bDoAutoUpdate;
279
+ }
280
+
281
+ /**
282
+ * @param string|\stdClass $sSlug
283
+ * @param string $sContext
284
+ * @return bool
285
+ */
286
+ private function isDelayed( $sSlug, $sContext = 'plugins' ) {
287
+ /** @var Options $oOpts */
288
+ $oOpts = $this->getOptions();
289
+
290
+ $bDelayed = false;
291
+
292
+ if ( $oOpts->isDelayUpdates() ) {
293
+
294
+ $aTk = $oOpts->getDelayTracking();
295
+
296
+ $sVersion = '';
297
+ if ( $sContext == 'core' ) {
298
+ $sVersion = $sSlug->current; // \stdClass from transient update_core
299
+ $sSlug = 'wp';
300
+ }
301
+
302
+ $aItemTk = isset( $aTk[ $sContext ][ $sSlug ] ) ? $aTk[ $sContext ][ $sSlug ] : [];
303
+
304
+ if ( $sContext == 'plugins' ) {
305
+ $oPlugin = Services::WpPlugins()->getUpdateInfo( $sSlug );
306
+ $sVersion = isset( $oPlugin->new_version ) ? $oPlugin->new_version : '';
307
+ }
308
+ elseif ( $sContext == 'themes' ) {
309
+ $aThemeInfo = Services::WpThemes()->getUpdateInfo( $sSlug );
310
+ $sVersion = isset( $aThemeInfo[ 'new_version' ] ) ? $aThemeInfo[ 'new_version' ] : '';
311
+ }
312
+
313
+ if ( !empty( $sVersion ) && isset( $aItemTk[ $sVersion ] ) ) {
314
+ $bDelayed = ( Services::Request()->ts() - $aItemTk[ $sVersion ] < $oOpts->getDelayUpdatesPeriod() );
315
+ }
316
+ }
317
+
318
+ return $bDelayed;
319
+ }
320
+
321
+ /**
322
+ * A filter on whether or not a notification email is sent after core upgrades are attempted.
323
+ * @param bool $bSendEmail
324
+ * @return bool
325
+ */
326
+ public function autoupdate_send_email( $bSendEmail ) {
327
+ /** @var Options $opts */
328
+ $opts = $this->getOptions();
329
+ return $opts->isSendAutoupdatesNotificationEmail();
330
+ }
331
+
332
+ /**
333
+ * A filter on the target email address to which to send upgrade notification emails.
334
+ * @param array $aEmailParams
335
+ * @return array
336
+ */
337
+ public function autoupdate_email_override( $aEmailParams ) {
338
+ $sOverride = $this->getOptions()->getOpt( 'override_email_address', '' );
339
+ if ( Services::Data()->validEmail( $sOverride ) ) {
340
+ $aEmailParams[ 'to' ] = $sOverride;
341
+ }
342
+ return $aEmailParams;
343
+ }
344
+
345
+ /**
346
+ * @param array $aUpdateResults
347
+ */
348
+ public function sendNotificationEmail( $aUpdateResults ) {
349
+ if ( empty( $aUpdateResults ) || !is_array( $aUpdateResults ) ) {
350
+ return;
351
+ }
352
+
353
+ // Are there really updates?
354
+ $bReallyUpdates = false;
355
+
356
+ $body = [
357
+ sprintf(
358
+ __( 'This is a quick notification from the %s that WordPress Automatic Updates just completed on your site with the following results.', 'wp-simple-firewall' ),
359
+ $this->getCon()->getHumanName()
360
+ ),
361
+ ''
362
+ ];
363
+
364
+ $aTrkd = $this->getTrackedAssetsVersions();
365
+
366
+ $oWpPlugins = Services::WpPlugins();
367
+ if ( !empty( $aUpdateResults[ 'plugin' ] ) && is_array( $aUpdateResults[ 'plugin' ] ) ) {
368
+ $bHasPluginUpdates = false;
369
+ $aTrkdPlugs = $aTrkd[ 'plugins' ];
370
+
371
+ $aTempContent[] = __( 'Plugins Updated:', 'wp-simple-firewall' );
372
+ foreach ( $aUpdateResults[ 'plugin' ] as $oUpdate ) {
373
+ $oP = $oWpPlugins->getPluginAsVo( $oUpdate->item->plugin, true );
374
+ $bValidUpdate = !empty( $oUpdate->result ) && !empty( $oUpdate->name )
375
+ && isset( $aTrkdPlugs[ $oP->file ] )
376
+ && version_compare( $aTrkdPlugs[ $oP->file ], $oP->Version, '<' );
377
+ if ( $bValidUpdate ) {
378
+ $aTempContent[] = ' - '.sprintf(
379
+ __( 'Plugin "%s" auto-updated from "%s" to version "%s"', 'wp-simple-firewall' ),
380
+ $oUpdate->name, $aTrkdPlugs[ $oP->file ], $oP->Version );
381
+ $bHasPluginUpdates = true;
382
+ }
383
+ }
384
+ $aTempContent[] = '';
385
+
386
+ if ( $bHasPluginUpdates ) {
387
+ $bReallyUpdates = true;
388
+ $body = array_merge( $body, $aTempContent );
389
+ }
390
+ }
391
+
392
+ if ( !empty( $aUpdateResults[ 'theme' ] ) && is_array( $aUpdateResults[ 'theme' ] ) ) {
393
+ $bHasThemesUpdates = false;
394
+ $aTrkdThemes = $aTrkd[ 'themes' ];
395
+
396
+ $aTempContent = [ __( 'Themes Updated:', 'wp-simple-firewall' ) ];
397
+ foreach ( $aUpdateResults[ 'theme' ] as $oUpdate ) {
398
+ $oItem = $oUpdate->item;
399
+ $bValidUpdate = isset( $oUpdate->result ) && $oUpdate->result && !empty( $oUpdate->name )
400
+ && isset( $aTrkdThemes[ $oItem->theme ] )
401
+ && version_compare( $aTrkdThemes[ $oItem->theme ], $oItem->new_version, '<' );
402
+ if ( $bValidUpdate ) {
403
+ $aTempContent[] = ' - '.sprintf(
404
+ __( 'Theme "%s" auto-updated from "%s" to version "%s"', 'wp-simple-firewall' ),
405
+ $oUpdate->name, $aTrkdThemes[ $oItem->theme ], $oItem->new_version );
406
+ $bHasThemesUpdates = true;
407
+ }
408
+ }
409
+ $aTempContent[] = '';
410
+
411
+ if ( $bHasThemesUpdates ) {
412
+ $bReallyUpdates = true;
413
+ $body = array_merge( $body, $aTempContent );
414
+ }
415
+ }
416
+
417
+ if ( !empty( $aUpdateResults[ 'core' ] ) && is_array( $aUpdateResults[ 'core' ] ) ) {
418
+ $bHasCoreUpdates = false;
419
+ $aTempContent = [ __( 'WordPress Core Updated:', 'wp-simple-firewall' ) ];
420
+ foreach ( $aUpdateResults[ 'core' ] as $oUpdate ) {
421
+ if ( isset( $oUpdate->result ) && !is_wp_error( $oUpdate->result ) ) {
422
+ $aTempContent[] = ' - '.sprintf( 'WordPress was automatically updated to "%s"', $oUpdate->name );
423
+ $bHasCoreUpdates = true;
424
+ }
425
+ }
426
+ $aTempContent[] = '';
427
+
428
+ if ( $bHasCoreUpdates ) {
429
+ $bReallyUpdates = true;
430
+ $body = array_merge( $body, $aTempContent );
431
+ }
432
+ }
433
+
434
+ if ( !$bReallyUpdates ) {
435
+ return;
436
+ }
437
+
438
+ $body[] = __( 'Thank you.', 'wp-simple-firewall' );
439
+
440
+ $this->getMod()
441
+ ->getEmailProcessor()
442
+ ->sendEmailWithWrap(
443
+ $this->getOptions()->getOpt( 'override_email_address' ),
444
+ sprintf( __( "Notice: %s", 'wp-simple-firewall' ), __( "Automatic Updates Completed", 'wp-simple-firewall' ) ),
445
+ $body
446
+ );
447
+ die();
448
+ }
449
+
450
+ private function getHookPriority() :int {
451
+ return (int)$this->getOptions()->getDef( 'action_hook_priority' );
452
+ }
453
+ }
src/lib/src/Modules/Autoupdates/UI.php CHANGED
@@ -2,9 +2,8 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Autoupdates;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
- use FernleafSystems\Wordpress\Services\Services;
7
 
8
- class UI extends Base\ShieldUI {
9
 
10
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Autoupdates;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
6
 
7
+ class UI extends BaseShield\UI {
8
 
9
  }
src/lib/src/Modules/Base/AdminNotices.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
 
6
  use FernleafSystems\Wordpress\Services\Services;
7
  use FernleafSystems\Wordpress\Services\Utilities\PluginUserMeta;
8
 
@@ -17,15 +18,11 @@ class AdminNotices {
17
  add_filter( $this->getCon()->prefix( 'ajaxAuthAction' ), [ $this, 'handleAuthAjax' ] );
18
  }
19
 
20
- /**
21
- * @param array $aAjaxResponse
22
- * @return array
23
- */
24
- public function handleAuthAjax( $aAjaxResponse ) {
25
- if ( empty( $aAjaxResponse ) && Services::Request()->request( 'exec' ) === 'dismiss_admin_notice' ) {
26
- $aAjaxResponse = $this->ajaxExec_DismissAdminNotice();
27
  }
28
- return $aAjaxResponse;
29
  }
30
 
31
  protected function ajaxExec_DismissAdminNotice() :array {
@@ -33,13 +30,13 @@ class AdminNotices {
33
 
34
  $sNoticeId = sanitize_key( Services::Request()->query( 'notice_id', '' ) );
35
 
36
- foreach ( $this->getAdminNotices() as $oNotice ) {
37
- if ( $sNoticeId == $oNotice->id ) {
38
- $this->setNoticeDismissed( $oNotice );
39
  $aAjaxResponse = [
40
  'success' => true,
41
  'message' => 'Admin notice dismissed', //not currently seen
42
- 'notice_id' => $oNotice->id,
43
  ];
44
  break;
45
  }
@@ -81,37 +78,34 @@ class AdminNotices {
81
  }
82
 
83
  /**
84
- * @return Shield\Utilities\AdminNotices\NoticeVO[]
85
  */
86
- protected function getAdminNotices() {
87
  return array_map(
88
- function ( $aNotDef ) {
89
- $aNotDef = Services::DataManipulation()
90
- ->mergeArraysRecursive(
91
- [
92
- 'schedule' => 'conditions',
93
- 'type' => 'promo',
94
- 'plugin_page_only' => true,
95
- 'valid_admin' => true,
96
- 'plugin_admin' => 'yes',
97
- 'can_dismiss' => true,
98
- 'per_user' => false,
99
- 'display' => false,
100
- 'min_install_days' => 0,
101
- 'twig' => true,
102
- ],
103
- $aNotDef
104
- );
105
- return ( new Shield\Utilities\AdminNotices\NoticeVO() )->applyFromArray( $aNotDef );
106
  },
107
  $this->getOptions()->getAdminNotices()
108
  );
109
  }
110
 
111
- /**
112
- * @param Shield\Utilities\AdminNotices\NoticeVO $notice
113
- */
114
- protected function preProcessNotice( $notice ) {
115
  $con = $this->getCon();
116
  $opts = $this->getOptions();
117
 
@@ -152,7 +146,7 @@ class AdminNotices {
152
  }
153
 
154
  /**
155
- * @param Shield\Utilities\AdminNotices\NoticeVO $notice
156
  * @return bool
157
  */
158
  protected function isNoticeDismissed( $notice ) {
@@ -169,15 +163,15 @@ class AdminNotices {
169
  }
170
 
171
  /**
172
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
173
  * @return bool
174
  */
175
- protected function isDisplayNeeded( $oNotice ) {
176
  return true;
177
  }
178
 
179
  /**
180
- * @param Shield\Utilities\AdminNotices\NoticeVO $notice
181
  * @return bool
182
  */
183
  protected function isNoticeDismissedForCurrentUser( $notice ) {
@@ -201,15 +195,15 @@ class AdminNotices {
201
  }
202
 
203
  /**
204
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
205
  * @throws \Exception
206
  */
207
- protected function processNotice( $oNotice ) {
208
- throw new \Exception( 'Unsupported Notice ID: '.$oNotice->id );
209
  }
210
 
211
  /**
212
- * @param Shield\Utilities\AdminNotices\NoticeVO $notice
213
  * @return $this
214
  */
215
  protected function setNoticeDismissed( $notice ) {
@@ -238,7 +232,7 @@ class AdminNotices {
238
  }
239
 
240
  /**
241
- * @param Shield\Utilities\AdminNotices\NoticeVO $notice
242
  * @return string
243
  */
244
  private function getNoticeMetaKey( $notice ) {
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\AdminNotices\NoticeVO;
7
  use FernleafSystems\Wordpress\Services\Services;
8
  use FernleafSystems\Wordpress\Services\Utilities\PluginUserMeta;
9
 
18
  add_filter( $this->getCon()->prefix( 'ajaxAuthAction' ), [ $this, 'handleAuthAjax' ] );
19
  }
20
 
21
+ public function handleAuthAjax( array $ajaxResponse ) :array {
22
+ if ( empty( $ajaxResponse ) && Services::Request()->request( 'exec' ) === 'dismiss_admin_notice' ) {
23
+ $ajaxResponse = $this->ajaxExec_DismissAdminNotice();
 
 
 
 
24
  }
25
+ return $ajaxResponse;
26
  }
27
 
28
  protected function ajaxExec_DismissAdminNotice() :array {
30
 
31
  $sNoticeId = sanitize_key( Services::Request()->query( 'notice_id', '' ) );
32
 
33
+ foreach ( $this->getAdminNotices() as $notice ) {
34
+ if ( $sNoticeId == $notice->id ) {
35
+ $this->setNoticeDismissed( $notice );
36
  $aAjaxResponse = [
37
  'success' => true,
38
  'message' => 'Admin notice dismissed', //not currently seen
39
+ 'notice_id' => $notice->id,
40
  ];
41
  break;
42
  }
78
  }
79
 
80
  /**
81
+ * @return NoticeVO[]
82
  */
83
+ protected function getAdminNotices() :array {
84
  return array_map(
85
+ function ( $noticeDef ) {
86
+ $noticeDef = Services::DataManipulation()
87
+ ->mergeArraysRecursive(
88
+ [
89
+ 'schedule' => 'conditions',
90
+ 'type' => 'promo',
91
+ 'plugin_page_only' => true,
92
+ 'valid_admin' => true,
93
+ 'plugin_admin' => 'yes',
94
+ 'can_dismiss' => true,
95
+ 'per_user' => false,
96
+ 'display' => false,
97
+ 'min_install_days' => 0,
98
+ 'twig' => true,
99
+ ],
100
+ $noticeDef
101
+ );
102
+ return ( new NoticeVO() )->applyFromArray( $noticeDef );
103
  },
104
  $this->getOptions()->getAdminNotices()
105
  );
106
  }
107
 
108
+ protected function preProcessNotice( NoticeVO $notice ) {
 
 
 
109
  $con = $this->getCon();
110
  $opts = $this->getOptions();
111
 
146
  }
147
 
148
  /**
149
+ * @param NoticeVO $notice
150
  * @return bool
151
  */
152
  protected function isNoticeDismissed( $notice ) {
163
  }
164
 
165
  /**
166
+ * @param NoticeVO $notice
167
  * @return bool
168
  */
169
+ protected function isDisplayNeeded( NoticeVO $notice ) :bool {
170
  return true;
171
  }
172
 
173
  /**
174
+ * @param NoticeVO $notice
175
  * @return bool
176
  */
177
  protected function isNoticeDismissedForCurrentUser( $notice ) {
195
  }
196
 
197
  /**
198
+ * @param NoticeVO $notice
199
  * @throws \Exception
200
  */
201
+ protected function processNotice( NoticeVO $notice ) {
202
+ throw new \Exception( 'Unsupported Notice ID: '.$notice->id );
203
  }
204
 
205
  /**
206
+ * @param NoticeVO $notice
207
  * @return $this
208
  */
209
  protected function setNoticeDismissed( $notice ) {
232
  }
233
 
234
  /**
235
+ * @param NoticeVO $notice
236
  * @return string
237
  */
238
  private function getNoticeMetaKey( $notice ) {
src/lib/src/Modules/Base/AjaxHandler.php ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ abstract class AjaxHandler {
9
+
10
+ use ModConsumer;
11
+
12
+ public function __construct() {
13
+ add_action( 'wp_loaded', [ $this, 'init' ] );
14
+ }
15
+
16
+ public function init() {
17
+ add_filter( $this->getCon()->prefix( 'ajaxAuthAction' ), [ $this, 'handleAjaxAuth' ], 10, 2 );
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
+ }
25
+ return $ajaxResponse;
26
+ }
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
+ }
34
+
35
+ /**
36
+ * @param string $encoding
37
+ * @return array
38
+ */
39
+ protected function getAjaxFormParams( $encoding = 'none' ) {
40
+ $req = Services::Request();
41
+ $aFormParams = [];
42
+ $sRaw = $req->post( 'form_params', '' );
43
+
44
+ if ( !empty( $sRaw ) ) {
45
+
46
+ $sMaybeEncoding = $req->post( 'enc_params' );
47
+ if ( in_array( $sMaybeEncoding, [ 'none', 'lz-string', 'b64' ] ) ) {
48
+ $encoding = $sMaybeEncoding;
49
+ }
50
+
51
+ switch ( $encoding ) {
52
+ case 'lz-string':
53
+ $sRaw = \LZCompressor\LZString::decompress( base64_decode( $sRaw ) );
54
+ break;
55
+
56
+ case 'b64':
57
+ $sRaw = base64_decode( $sRaw );
58
+ break;
59
+
60
+ case 'none':
61
+ default:
62
+ break;
63
+ }
64
+
65
+ parse_str( $sRaw, $aFormParams );
66
+ }
67
+ return $aFormParams;
68
+ }
69
+
70
+ protected function processAjaxAction( string $action ) :array {
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
77
+ * processing will finish
78
+ * @param array $ajaxResponse
79
+ * @return array
80
+ */
81
+ protected function normaliseAjaxResponse( array $ajaxResponse ) {
82
+ if ( !empty( $ajaxResponse ) ) {
83
+ $ajaxResponse = array_merge(
84
+ [
85
+ 'success' => false,
86
+ 'page_reload' => false,
87
+ 'message' => 'Unknown',
88
+ 'html' => '',
89
+ ],
90
+ $ajaxResponse
91
+ );
92
+ }
93
+ return $ajaxResponse;
94
+ }
95
+ }
src/lib/src/Modules/Base/AjaxHandlerBase.php CHANGED
@@ -5,7 +5,12 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
- class AjaxHandlerBase {
 
 
 
 
 
9
 
10
  use ModConsumer;
11
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
+ /**
9
+ * Class AjaxHandlerBase
10
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Base
11
+ * @deprecated 10.1
12
+ */
13
+ abstract class AjaxHandlerBase {
14
 
15
  use ModConsumer;
16
 
src/lib/src/Modules/Base/AjaxHandlerShield.php CHANGED
@@ -2,6 +2,11 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
 
 
 
 
 
 
5
  class AjaxHandlerShield extends AjaxHandlerBase {
6
 
7
  protected function processAjaxAction( string $action ) :array {
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
 
5
+ /**
6
+ * Class AjaxHandlerShield
7
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Base
8
+ * @deprecated 10.1
9
+ */
10
  class AjaxHandlerShield extends AjaxHandlerBase {
11
 
12
  protected function processAjaxAction( string $action ) :array {
src/lib/src/Modules/Base/BaseProcessor.php CHANGED
@@ -5,6 +5,11 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
 
 
 
 
 
8
  class BaseProcessor {
9
 
10
  use Modules\ModConsumer;
@@ -25,10 +30,10 @@ class BaseProcessor {
25
  private $bHasExecuted;
26
 
27
  /**
28
- * @param BaseModCon $oMod
29
  */
30
- public function __construct( $oMod ) {
31
- $this->setMod( $oMod );
32
 
33
  add_action( 'init', [ $this, 'onWpInit' ], 9 );
34
  add_action( 'wp_loaded', [ $this, 'onWpLoaded' ] );
@@ -38,10 +43,10 @@ class BaseProcessor {
38
  add_action( 'set_logged_in_cookie', [ $this, 'onWpSetLoggedInCookie' ], 5, 4 );
39
  }
40
  }
41
- add_action( $oMod->prefix( 'plugin_shutdown' ), [ $this, 'onModuleShutdown' ] );
42
- add_action( $oMod->prefix( 'daily_cron' ), [ $this, 'runDailyCron' ] );
43
- add_action( $oMod->prefix( 'hourly_cron' ), [ $this, 'runHourlyCron' ] );
44
- add_action( $oMod->prefix( 'deactivate_plugin' ), [ $this, 'deactivatePlugin' ] );
45
 
46
  /**
47
  * 2019-04-19:
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.1
12
+ */
13
  class BaseProcessor {
14
 
15
  use Modules\ModConsumer;
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' ] );
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:
src/lib/src/Modules/Base/BaseReporting.php CHANGED
@@ -5,6 +5,11 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Lib\Reports;
7
 
 
 
 
 
 
8
  abstract class BaseReporting {
9
 
10
  use ModConsumer;
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Lib\Reports;
7
 
8
+ /**
9
+ * Class BaseReporting
10
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Base
11
+ * @deprecated 10.1
12
+ */
13
  abstract class BaseReporting {
14
 
15
  use ModConsumer;
src/lib/src/Modules/Base/{BaseModCon.php → ModCon.php} RENAMED
@@ -6,9 +6,14 @@ use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
- class BaseModCon {
 
 
 
 
10
 
11
  use Modules\PluginControllerConsumer;
 
12
 
13
  /**
14
  * @var string
@@ -26,12 +31,7 @@ class BaseModCon {
26
  protected $bImportExportWhitelistNotify = false;
27
 
28
  /**
29
- * @var \ICWP_WPSF_FeatureHandler_Email
30
- */
31
- private static $oEmailHandler;
32
-
33
- /**
34
- * @var BaseProcessor
35
  */
36
  private $oProcessor;
37
 
@@ -41,35 +41,40 @@ class BaseModCon {
41
  private $oWizard;
42
 
43
  /**
44
- * @var Shield\Databases\Base\Handler
45
  */
46
- private $oDbh;
47
 
48
  /**
49
- * @var Shield\Modules\Base\Strings
50
  */
51
- private $oStrings;
52
 
53
  /**
54
  * @var Shield\Modules\Base\Options
55
  */
56
  private $oOpts;
57
 
 
 
 
 
 
58
  /**
59
  * @var Shield\Databases\Base\Handler[]
60
  */
61
  private $aDbHandlers;
62
 
63
  /**
64
- * @param Shield\Controller\Controller $oPlugCon
65
  * @param array $aMod
66
  * @throws \Exception
67
  */
68
- public function __construct( $oPlugCon, $aMod = [] ) {
69
- if ( !$oPlugCon instanceof Shield\Controller\Controller ) {
70
  throw new \Exception( 'Plugin controller not supplied to Module' );
71
  }
72
- $this->setCon( $oPlugCon );
73
 
74
  if ( empty( $aMod[ 'storage_key' ] ) && empty( $aMod[ 'slug' ] ) ) {
75
  throw new \Exception( 'Module storage key AND slug are undefined' );
@@ -81,59 +86,30 @@ class BaseModCon {
81
  }
82
 
83
  if ( $this->verifyModuleMeetRequirements() ) {
 
84
  $this->setupHooks( $aMod );
85
  $this->doPostConstruction();
86
  }
87
  }
88
 
89
- /**
90
- * @param array $aModProps
91
- */
92
- protected function setupHooks( $aModProps ) {
93
- $oReq = Services::Request();
94
-
95
  $nRunPriority = isset( $aModProps[ 'load_priority' ] ) ? $aModProps[ 'load_priority' ] : 100;
96
- add_action( $this->prefix( 'run_processors' ), [ $this, 'onRunProcessors' ], $nRunPriority );
97
- add_action( 'init', [ $this, 'onWpInit' ], 1 );
98
 
99
- if ( $this->isModuleRequest() ) {
100
-
101
- if ( Services::WpGeneral()->isAjax() ) {
102
- $this->loadAjaxHandler();
103
- }
104
-
105
- if ( $oReq->request( 'action' ) == $this->prefix()
106
- && check_admin_referer( $oReq->request( 'exec' ), 'exec_nonce' )
107
- ) {
108
- add_action( $this->prefix( 'mod_request' ), [ $this, 'handleModRequest' ] );
109
- }
110
- }
111
 
112
  $nMenuPri = isset( $aModProps[ 'menu_priority' ] ) ? $aModProps[ 'menu_priority' ] : 100;
113
- add_filter( $this->prefix( 'submenu_items' ), [ $this, 'supplySubMenuItem' ], $nMenuPri );
114
- add_action( $this->prefix( 'plugin_shutdown' ), [ $this, 'onPluginShutdown' ] );
115
- add_action( $this->prefix( 'deactivate_plugin' ), [ $this, 'onPluginDeactivate' ] );
116
- add_action( $this->prefix( 'delete_plugin' ), [ $this, 'onPluginDelete' ] );
117
- add_filter( $this->prefix( 'aggregate_all_plugin_options' ), [ $this, 'aggregateOptionsValues' ] );
118
-
119
- add_filter( $this->prefix( 'register_admin_notices' ), [ $this, 'fRegisterAdminNotices' ] );
120
-
121
- add_action( $this->prefix( 'daily_cron' ), [ $this, 'runDailyCron' ] );
122
- add_action( $this->prefix( 'hourly_cron' ), [ $this, 'runHourlyCron' ] );
123
-
124
- // supply our supported plugin events for this module
125
- add_filter( $this->prefix( 'get_all_events' ), function ( $aEvents ) {
126
- return array_merge(
127
- is_array( $aEvents ) ? $aEvents : [],
128
- array_map(
129
- function ( $aEvt ) {
130
- $aEvt[ 'context' ] = $this->getSlug();
131
- return $aEvt;
132
- },
133
- is_array( $this->getDef( 'events' ) ) ? $this->getDef( 'events' ) : []
134
- )
135
- );
136
- } );
137
 
138
  add_action( 'admin_enqueue_scripts', [ $this, 'onWpEnqueueAdminJs' ], 100 );
139
 
@@ -144,7 +120,7 @@ class BaseModCon {
144
  // if ( $this->isAdminOptionsPage() ) {
145
  // add_action( 'current_screen', array( $this, 'onSetCurrentScreen' ) );
146
  // }
147
-
148
  $this->setupCustomHooks();
149
  }
150
 
@@ -158,13 +134,10 @@ class BaseModCon {
158
  $this->cleanupDatabases();
159
  }
160
 
161
- public function runHourlyCron() {
162
- }
163
-
164
  protected function cleanupDatabases() {
165
- foreach ( $this->getDbHandlers( true ) as $oDbh ) {
166
- if ( $oDbh instanceof Shield\Databases\Base\Handler && $oDbh->isReady() ) {
167
- $oDbh->autoCleanDb();
168
  }
169
  }
170
  }
@@ -183,49 +156,49 @@ class BaseModCon {
183
  }
184
 
185
  /**
186
- * @param string $sDbhKey
187
  * @return Shield\Databases\Base\Handler|mixed|false
188
  */
189
- protected function getDbH( $sDbhKey ) {
190
- $oDbH = false;
191
 
192
  if ( !is_array( $this->aDbHandlers ) ) {
193
  $this->aDbHandlers = [];
194
  }
195
 
196
- if ( !empty( $this->aDbHandlers[ $sDbhKey ] ) ) {
197
- $oDbH = $this->aDbHandlers[ $sDbhKey ];
198
  }
199
  else {
200
  $aDbClasses = $this->getAllDbClasses();
201
- if ( isset( $aDbClasses[ $sDbhKey ] ) ) {
202
- /** @var Shield\Databases\Base\Handler $oDbH */
203
- $oDbH = new $aDbClasses[ $sDbhKey ]();
204
  try {
205
- $oDbH->setMod( $this )->tableInit();
206
  }
207
- catch ( \Exception $oE ) {
208
  }
209
  }
210
- $this->aDbHandlers[ $sDbhKey ] = $oDbH;
211
  }
212
 
213
- return $oDbH;
214
  }
215
 
216
  /**
217
  * @return string[]
218
  */
219
  private function getAllDbClasses() {
220
- $aCls = $this->getOptions()->getDef( 'db_classes' );
221
- return is_array( $aCls ) ? $aCls : [];
222
  }
223
 
224
  /**
225
- * Should be over-ridden by each new class to handle upgrades.
226
- * Called upon construction and after plugin options are initialized.
227
  */
228
- protected function updateHandler() {
 
229
  }
230
 
231
  /**
@@ -274,10 +247,7 @@ class BaseModCon {
274
  return array_merge( $aAdminNotices, $this->getOptions()->getAdminNotices() );
275
  }
276
 
277
- /**
278
- * @return bool
279
- */
280
- private function verifyModuleMeetRequirements() {
281
  $bMeetsReqs = true;
282
 
283
  $aPhpReqs = $this->getOptions()->getFeatureRequirement( 'php' );
@@ -301,34 +271,67 @@ class BaseModCon {
301
  return $bMeetsReqs;
302
  }
303
 
 
 
 
304
  public function onRunProcessors() {
305
- if ( $this->isUpgrading() ) {
306
- $this->updateHandler();
307
- }
308
- if ( $this->getOptions()->getFeatureProperty( 'auto_load_processor' ) ) {
309
  $this->loadProcessor();
310
  }
311
- if ( !$this->isUpgrading() && $this->isModuleEnabled() && $this->isReadyToExecute() ) {
312
- $this->doExecuteProcessor();
 
 
 
 
 
313
  }
314
  }
315
 
316
  /**
317
  * @return bool
 
318
  */
319
- protected function isReadyToExecute() {
320
- return ( $this->getProcessor() instanceof Shield\Modules\Base\BaseProcessor );
321
  }
322
 
323
  protected function doExecuteProcessor() {
324
  $this->getProcessor()->execute();
325
  }
326
 
327
- /**
328
- * A action added to WordPress 'init' hook
329
- */
330
  public function onWpInit() {
331
- do_action( $this->prefix( 'mod_request' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
 
333
  $this->runWizards();
334
 
@@ -337,6 +340,8 @@ class BaseModCon {
337
  add_filter( $this->prefix( 'wpPrivacyExport' ), [ $this, 'onWpPrivacyExport' ], 10, 3 );
338
  add_filter( $this->prefix( 'wpPrivacyErase' ), [ $this, 'onWpPrivacyErase' ], 10, 3 );
339
  }
 
 
340
  }
341
 
342
  /**
@@ -356,31 +361,37 @@ class BaseModCon {
356
 
357
  /**
358
  * Override this and adapt per feature
359
- * @return BaseProcessor|mixed
360
  */
361
  protected function loadProcessor() {
362
  if ( !isset( $this->oProcessor ) ) {
363
- $sClassName = $this->getProcessorClassName();
364
- if ( !class_exists( $sClassName ) ) {
 
 
 
 
 
 
365
  return null;
366
  }
367
- $this->oProcessor = new $sClassName( $this );
368
  }
369
  return $this->oProcessor;
370
  }
371
 
372
  /**
373
- * Override this and adapt per feature
374
- * @return string
375
  */
376
- protected function getProcessorClassName() {
377
- return implode( '_',
378
- [
379
- strtoupper( $this->getCon()->getPluginPrefix( '_' ) ),
380
- 'Processor',
381
- str_replace( ' ', '', ucwords( str_replace( '_', ' ', $this->getSlug() ) ) )
382
- ]
383
- );
384
  }
385
 
386
  /**
@@ -397,10 +408,7 @@ class BaseModCon {
397
  );
398
  }
399
 
400
- /**
401
- * @return bool
402
- */
403
- public function isUpgrading() {
404
  return $this->getCon()->getIsRebuildOptionsFromFile() || $this->getOptions()->getRebuildFromFile();
405
  }
406
 
@@ -417,24 +425,18 @@ class BaseModCon {
417
  }
418
  }
419
 
420
- /**
421
- * @return string
422
- */
423
- protected function getOptionsStorageKey() {
424
  return $this->getCon()->prefixOption( $this->sOptionsStoreKey ).'_options';
425
  }
426
 
427
  /**
428
- * @return BaseProcessor|mixed
429
  */
430
  public function getProcessor() {
431
  return $this->loadProcessor();
432
  }
433
 
434
- /**
435
- * @return string
436
- */
437
- public function getUrl_AdminPage() {
438
  return Services::WpGeneral()
439
  ->getUrl_AdminPage(
440
  $this->getModSlug(),
@@ -443,113 +445,140 @@ class BaseModCon {
443
  }
444
 
445
  /**
446
- * @param string $sOptKey
447
  * @return string
448
  */
449
- public function getUrl_DirectLinkToOption( $sOptKey ) {
450
- $sUrl = $this->getUrl_AdminPage();
451
- $aDef = $this->getOptions()->getOptDefinition( $sOptKey );
452
- if ( !empty( $aDef[ 'section' ] ) ) {
453
- $sUrl = $this->getUrl_DirectLinkToSection( $aDef[ 'section' ] );
454
- }
455
- return $sUrl;
 
 
 
 
 
 
 
 
 
 
456
  }
457
 
458
  /**
459
- * @param string $sSection
460
- * @return string
461
  */
462
- public function getUrl_DirectLinkToSection( $sSection ) {
463
- if ( $sSection == 'primary' ) {
464
- $aSec = $this->getOptions()->getPrimarySection();
465
- $sSection = $aSec[ 'slug' ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
466
  }
467
- return $this->getUrl_AdminPage().'#tab-'.$sSection;
 
 
 
 
 
 
 
468
  }
469
 
470
  /**
471
  * TODO: Get rid of this crap and/or handle the \Exception thrown in loadFeatureHandler()
472
- * @return \ICWP_WPSF_FeatureHandler_Email
473
  * @throws \Exception
 
474
  */
475
  public function getEmailHandler() {
476
- if ( is_null( self::$oEmailHandler ) ) {
477
- self::$oEmailHandler = $this->getCon()->getModule( 'email' );
478
- }
479
- return self::$oEmailHandler;
480
  }
481
 
482
  /**
483
- * @return \ICWP_WPSF_Processor_Email
484
  */
485
  public function getEmailProcessor() {
486
  return $this->getEmailHandler()->getProcessor();
487
  }
488
 
489
  /**
490
- * @param bool $bEnable
491
  * @return $this
492
  */
493
- public function setIsMainFeatureEnabled( $bEnable ) {
494
- return $this->setOpt( 'enable_'.$this->getSlug(), $bEnable ? 'Y' : 'N' );
 
495
  }
496
 
497
- /**
498
- * @return bool
499
- */
500
- public function isModuleEnabled() {
501
- $oOpts = $this->getOptions();
502
  /** @var Shield\Modules\Plugin\Options $oPluginOpts */
503
  $oPluginOpts = $this->getCon()->getModule_Plugin()->getOptions();
504
 
505
  if ( $this->getOptions()->getFeatureProperty( 'auto_enabled' ) === true ) {
506
  // Auto enabled modules always run regardless
507
- $bEnabled = true;
508
  }
509
  elseif ( $oPluginOpts->isPluginGloballyDisabled() ) {
510
- $bEnabled = false;
511
  }
512
  elseif ( $this->getCon()->getIfForceOffActive() ) {
513
- $bEnabled = false;
514
  }
515
- elseif ( $oOpts->getFeatureProperty( 'premium' ) === true && !$this->isPremium() ) {
516
- $bEnabled = false;
 
517
  }
518
  else {
519
- $bEnabled = $this->isModOptEnabled();
520
  }
521
 
522
- return $bEnabled;
523
  }
524
 
525
- /**
526
- * @return bool
527
- */
528
- protected function isModOptEnabled() {
529
- return $this->isOpt( $this->getEnableModOptKey(), 'Y' )
530
- || $this->isOpt( $this->getEnableModOptKey(), true, true );
531
  }
532
 
533
- /**
534
- * @return string
535
- */
536
- protected function getEnableModOptKey() {
537
  return 'enable_'.$this->getSlug();
538
  }
539
 
540
- /**
541
- * @return string
542
- */
543
- public function getMainFeatureName() {
544
  return __( $this->getOptions()->getFeatureProperty( 'name' ), 'wp-simple-firewall' );
545
  }
546
 
547
- /**
548
- * @param bool $bWithPrefix
549
- * @return string
550
- */
551
- public function getModSlug( $bWithPrefix = true ) {
552
- return $bWithPrefix ? $this->prefix( $this->getSlug() ) : $this->getSlug();
553
  }
554
 
555
  /**
@@ -592,16 +621,12 @@ class BaseModCon {
592
  if ( !empty( $aAdditionalItems ) && is_array( $aAdditionalItems ) ) {
593
 
594
  foreach ( $aAdditionalItems as $aMenuItem ) {
595
-
596
- if ( empty( $aMenuItem[ 'callback' ] ) || !method_exists( $this, $aMenuItem[ 'callback' ] ) ) {
597
- continue;
598
- }
599
-
600
  $sMenuPageTitle = $sHumanName.' - '.$aMenuItem[ 'title' ];
601
  $aItems[ $sMenuPageTitle ] = [
602
- $aMenuItem[ 'title' ],
603
  $this->prefix( $aMenuItem[ 'slug' ] ),
604
- [ $this, $aMenuItem[ 'callback' ] ]
 
605
  ];
606
  }
607
  }
@@ -610,74 +635,111 @@ class BaseModCon {
610
  }
611
 
612
  /**
613
- * @return array
 
 
614
  */
615
- protected function getAdditionalMenuItem() {
616
- return [];
 
 
 
 
 
 
 
 
 
 
 
 
617
  }
618
 
619
  /**
620
- * @param array $aSummaryData
621
  * @return array
622
  */
623
- public function addModuleSummaryData( $aSummaryData ) {
624
- if ( $this->getIfShowModuleLink() ) {
625
- $aSummaryData[ $this->getModSlug( false ) ] = $this->buildSummaryData();
626
- }
627
- return $aSummaryData;
628
  }
629
 
630
  /**
631
- * @param array $aAllNotices
632
- * @return array
633
  */
634
- public function addInsightsNoticeData( $aAllNotices ) {
635
- return $aAllNotices;
 
 
 
 
 
636
  }
637
 
638
  /**
639
- * @param array $aAllData
640
  * @return array
641
  */
642
- public function addInsightsConfigData( $aAllData ) {
643
- return $aAllData;
644
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
645
 
646
- /**
647
- * @return bool
648
- */
649
- protected function isEnabledForUiSummary() {
650
- return $this->isModuleEnabled();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
651
  }
652
 
653
- /**
654
- * @return bool
655
- */
656
- public function getIfShowModuleMenuItem() {
657
  return (bool)$this->getOptions()->getFeatureProperty( 'show_module_menu_item' );
658
  }
659
 
660
- /**
661
- * @return bool
662
- */
663
- public function getIfShowModuleLink() {
664
  return (bool)$this->getOptions()->getFeatureProperty( 'show_module_options' );
665
  }
666
 
667
- /**
668
- * @return bool
669
- */
670
- public function getIfUseSessions() {
671
- return $this->getOptions()->getFeatureProperty( 'use_sessions' );
672
- }
673
-
674
  /**
675
  * Get config 'definition'.
676
- * @param string $sKey
677
  * @return mixed|null
678
  */
679
- public function getDef( $sKey ) {
680
- return $this->getOptions()->getDef( $sKey );
681
  }
682
 
683
  /**
@@ -692,59 +754,27 @@ class BaseModCon {
692
  * @param string $sGlue
693
  * @return string|array
694
  */
695
- public function getLastErrors( $bAsString = true, $sGlue = " " ) {
696
- $aErrors = $this->getOpt( 'last_errors' );
697
- if ( !is_array( $aErrors ) ) {
698
- $aErrors = [];
699
  }
700
- return $bAsString ? implode( $sGlue, $aErrors ) : $aErrors;
701
  }
702
 
703
- /**
704
- * @return bool
705
- */
706
- public function hasLastErrors() {
707
  return count( $this->getLastErrors( false ) ) > 0;
708
  }
709
 
710
- /**
711
- * @param string $sOptionKey
712
- * @param mixed $mDefault
713
- * @return mixed
714
- */
715
- public function getOpt( $sOptionKey, $mDefault = false ) {
716
- return $this->getOptions()->getOpt( $sOptionKey, $mDefault );
717
- }
718
-
719
- /**
720
- * @param string $sOptionKey
721
- * @param mixed $mValueToTest
722
- * @param bool $bStrict
723
- * @return bool
724
- */
725
- public function isOpt( $sOptionKey, $mValueToTest, $bStrict = false ) {
726
- $mOptionValue = $this->getOptions()->getOpt( $sOptionKey );
727
- return $bStrict ? $mOptionValue === $mValueToTest : $mOptionValue == $mValueToTest;
728
- }
729
-
730
- /**
731
- * @param string $sOptKey
732
- * @return string
733
- */
734
- public function getTextOpt( $sOptKey ) {
735
- $sValue = $this->getOpt( $sOptKey, 'default' );
736
  if ( $sValue == 'default' ) {
737
- $sValue = $this->getTextOptDefault( $sOptKey );
738
  }
739
- return $sValue;
740
  }
741
 
742
- /**
743
- * Override this on each feature that has Text field options to supply the text field defaults
744
- * @param string $sOptKey
745
- * @return string
746
- */
747
- public function getTextOptDefault( $sOptKey ) {
748
  return 'Undefined Text Opt Default';
749
  }
750
 
@@ -761,47 +791,30 @@ class BaseModCon {
761
  $mErrors = [];
762
  }
763
  }
764
- return $this->setOpt( 'last_errors', $mErrors );
765
- }
766
-
767
- /**
768
- * Sets the value for the given option key
769
- * Note: We also set the ability to bypass admin access since setOpt() is a protected function
770
- * @param string $sOptionKey
771
- * @param mixed $mValue
772
- * @return $this
773
- */
774
- protected function setOpt( $sOptionKey, $mValue ) {
775
- $this->getOptions()->setOpt( $sOptionKey, $mValue );
776
  return $this;
777
  }
778
 
779
- /**
780
- * @param array $aOptions
781
- */
782
- public function setOptions( $aOptions ) {
783
- $oVO = $this->getOptions();
784
- foreach ( $aOptions as $sKey => $mValue ) {
785
- $oVO->setOpt( $sKey, $mValue );
786
  }
787
  }
788
 
789
- /**
790
- * @return bool
791
- */
792
- public function isModuleRequest() {
793
- return ( $this->getModSlug() == Services::Request()->request( 'mod_slug' ) );
794
  }
795
 
796
  /**
797
- * @param string $sAction
798
- * @param bool $bAsJsonEncodedObject
799
- * @return array
800
  */
801
- public function getAjaxActionData( $sAction = '', $bAsJsonEncodedObject = false ) {
802
- $aData = $this->getNonceActionData( $sAction );
803
- $aData[ 'ajaxurl' ] = admin_url( 'admin-ajax.php' );
804
- return $bAsJsonEncodedObject ? json_encode( (object)$aData ) : $aData;
805
  }
806
 
807
  /**
@@ -817,33 +830,25 @@ class BaseModCon {
817
  /**
818
  * @return string[]
819
  */
820
- public function getDismissedNotices() {
821
- $aDN = $this->getOpt( 'dismissed_notices' );
822
- return is_array( $aDN ) ? $aDN : [];
823
  }
824
 
825
  /**
826
  * @return string[]
827
  */
828
- public function getUiTrack() {
829
- $aDN = $this->getOpt( 'ui_track' );
830
- return is_array( $aDN ) ? $aDN : [];
831
  }
832
 
833
- /**
834
- * @param string[] $aDismissed
835
- * @return $this
836
- */
837
- public function setDismissedNotices( $aDismissed ) {
838
- return $this->setOpt( 'dismissed_notices', $aDismissed );
839
  }
840
 
841
- /**
842
- * @param string[] $aDismissed
843
- * @return $this
844
- */
845
- public function setUiTrack( $aDismissed ) {
846
- return $this->setOpt( 'ui_track', $aDismissed );
847
  }
848
 
849
  /**
@@ -862,9 +867,15 @@ class BaseModCon {
862
  }
863
 
864
  /**
 
865
  * @return $this
866
  */
867
- public function saveModOptions() {
 
 
 
 
 
868
  $this->doPrePluginOptionsSave();
869
  if ( apply_filters( $this->prefix( 'force_options_resave' ), false ) ) {
870
  $this->getOptions()
@@ -877,6 +888,9 @@ class BaseModCon {
877
  return $this;
878
  }
879
 
 
 
 
880
  private function store() {
881
  add_filter( $this->prefix( 'bypass_is_plugin_admin' ), '__return_true', 1000 );
882
  $this->getOptions()
@@ -902,6 +916,11 @@ class BaseModCon {
902
  }
903
 
904
  public function onPluginDelete() {
 
 
 
 
 
905
  $this->getOptions()->deleteStorage();
906
  }
907
 
@@ -911,7 +930,7 @@ class BaseModCon {
911
  protected function getAllFormOptionsAndTypes() {
912
  $aOpts = [];
913
 
914
- foreach ( $this->buildOptions() as $aOptionsSection ) {
915
  if ( !empty( $aOptionsSection ) ) {
916
  foreach ( $aOptionsSection[ 'options' ] as $aOption ) {
917
  $aOpts[ $aOption[ 'key' ] ] = $aOption[ 'type' ];
@@ -922,7 +941,7 @@ class BaseModCon {
922
  return $aOpts;
923
  }
924
 
925
- public function handleModRequest() {
926
  }
927
 
928
  /**
@@ -932,11 +951,17 @@ class BaseModCon {
932
  if ( !$this->getCon()->isPluginAdmin() ) {
933
  throw new \Exception( __( "You don't currently have permission to save settings.", 'wp-simple-firewall' ) );
934
  }
 
935
  $this->doSaveStandardOptions();
936
- $this->preProcessOptions();
937
- }
938
 
939
- protected function preProcessOptions() {
 
 
 
 
 
 
 
940
  }
941
 
942
  /**
@@ -956,11 +981,8 @@ class BaseModCon {
956
  return $this;
957
  }
958
 
959
- /**
960
- * @return bool
961
- */
962
- protected function isAdminOptionsPage() {
963
- return ( is_admin() && !Services::WpGeneral()->isAjax() && $this->isThisModulePage() );
964
  }
965
 
966
  /**
@@ -1021,23 +1043,20 @@ class BaseModCon {
1021
  elseif ( $sOptType == 'comma_separated_lists' ) {
1022
  $sOptionValue = Services::Data()->extractCommaSeparatedList( $sOptionValue );
1023
  }
1024
- elseif ( $sOptType == 'multiple_select' ) {
1025
- }
1026
  }
1027
 
1028
  // Prevent overwriting of non-editable fields
1029
  if ( !in_array( $sOptType, [ 'noneditable_text' ] ) ) {
1030
- $this->setOpt( $sKey, $sOptionValue );
1031
  }
1032
  }
1033
 
1034
- $this->saveModOptions();
1035
-
1036
- // only use this flag when the options are being updated with a MANUAL save.
1037
- if ( isset( $this->bImportExportWhitelistNotify ) && $this->bImportExportWhitelistNotify ) {
1038
- if ( !wp_next_scheduled( $this->prefix( 'importexport_notify' ) ) ) {
1039
- wp_schedule_single_event( Services::Request()->ts() + 15, $this->prefix( 'importexport_notify' ) );
1040
- }
1041
  }
1042
  }
1043
 
@@ -1097,24 +1116,11 @@ class BaseModCon {
1097
  return $this->getCon()->prefix( $sSuffix, $sGlue );
1098
  }
1099
 
1100
- /**
1101
- * @param string
1102
- * @return string
1103
- */
1104
- public function getOptionStoragePrefix() {
1105
- return $this->getCon()->getOptionStoragePrefix();
1106
- }
1107
-
1108
  /**
1109
  * @uses echo()
1110
  */
1111
  public function displayModuleAdminPage() {
1112
- if ( $this->canDisplayOptionsForm() ) {
1113
- echo $this->renderModulePage();
1114
- }
1115
- else {
1116
- echo $this->renderRestrictedPage();
1117
- }
1118
  }
1119
 
1120
  /**
@@ -1122,103 +1128,18 @@ class BaseModCon {
1122
  * @param array $aData
1123
  * @return string
1124
  */
1125
- protected function renderModulePage( $aData = [] ) {
1126
- // Get Base Data
1127
- $aData = Services::DataManipulation()
1128
- ->mergeArraysRecursive( $this->getBaseDisplayData(), $aData );
1129
- $aData[ 'content' ][ 'options_form' ] = $this->renderOptionsForm();
1130
-
1131
- return $this->renderTemplate( 'index.php', $aData );
1132
- }
1133
-
1134
- /**
1135
- * @return string
1136
- */
1137
- protected function renderRestrictedPage() {
1138
- $aData = Services::DataManipulation()
1139
- ->mergeArraysRecursive(
1140
- $this->getBaseDisplayData(),
1141
- [
1142
- 'ajax' => [
1143
- 'restricted_access' => $this->getAjaxActionData( 'restricted_access' )
1144
- ]
1145
- ]
1146
- );
1147
- return $this->renderTemplate( '/wpadmin_pages/security_admin/index.twig', $aData, true );
1148
- }
1149
-
1150
- /**
1151
- * @return array
1152
- */
1153
- protected function getBaseDisplayData() {
1154
- $oCon = $this->getCon();
1155
-
1156
- return [
1157
- 'sPluginName' => $oCon->getHumanName(),
1158
- 'sTagline' => $this->getOptions()->getFeatureTagline(),
1159
- 'nonce_field' => wp_nonce_field( $oCon->getPluginPrefix(), '_wpnonce', true, false ), //don't echo!
1160
- 'form_action' => 'admin.php?page='.$this->getModSlug(),
1161
- 'aPluginLabels' => $oCon->getLabels(),
1162
- 'help_video' => [
1163
- 'auto_show' => $this->getIfAutoShowHelpVideo(),
1164
- 'iframe_url' => $this->getHelpVideoUrl( $this->getHelpVideoId() ),
1165
- 'display_id' => 'ShieldHelpVideo'.$this->getSlug(),
1166
- 'options' => $this->getHelpVideoOptions(),
1167
- 'displayable' => $this->isHelpVideoDisplayable(),
1168
- 'show' => $this->isHelpVideoDisplayable() && !$this->getHelpVideoHasBeenClosed(),
1169
- 'width' => 772,
1170
- 'height' => 454,
1171
- ],
1172
-
1173
- // 'sPageTitle' => sprintf( '%s: %s', $oCon->getHumanName(), $this->getMainFeatureName() ),
1174
- 'sPageTitle' => $this->getMainFeatureName(),
1175
- 'data' => [
1176
- 'mod_slug' => $this->getModSlug( true ),
1177
- 'mod_slug_short' => $this->getModSlug( false ),
1178
- 'all_options' => $this->buildOptions(),
1179
- 'hidden_options' => $this->getOptions()->getHiddenOptions()
1180
- ],
1181
- 'ajax' => [
1182
- 'mod_options' => $this->getAjaxActionData( 'mod_options' ),
1183
- ],
1184
- 'strings' => $this->getStrings()->getDisplayStrings(),
1185
- 'flags' => [
1186
- 'access_restricted' => !$this->canDisplayOptionsForm(),
1187
- 'show_ads' => $this->getIsShowMarketing(),
1188
- 'wrap_page_content' => true,
1189
- 'show_standard_options' => true,
1190
- 'show_content_help' => true,
1191
- 'show_alt_content' => false,
1192
- 'has_wizard' => $this->hasWizard(),
1193
- ],
1194
- 'hrefs' => [
1195
- 'go_pro' => 'https://shsec.io/shieldgoprofeature',
1196
- 'goprofooter' => 'https://shsec.io/goprofooter',
1197
- 'wizard_link' => $this->getUrl_WizardLanding(),
1198
- 'wizard_landing' => $this->getUrl_WizardLanding()
1199
- ],
1200
- 'content' => [
1201
- 'options_form' => '',
1202
- 'alt' => '',
1203
- 'actions' => '',
1204
- 'help' => '',
1205
- 'wizard_landing' => ''
1206
- ]
1207
- ];
1208
- }
1209
-
1210
- /**
1211
- * @return string
1212
- */
1213
- protected function getContentHelp() {
1214
- return $this->renderTemplate( 'snippets/module-help-template.php', $this->getBaseDisplayData() );
1215
  }
1216
 
1217
  /**
1218
  * @return string
1219
  */
1220
  protected function getContentWizardLanding() {
1221
- $aData = $this->getBaseDisplayData();
1222
  if ( $this->hasWizard() ) {
1223
  $aData[ 'content' ][ 'wizard_landing' ] = $this->getWizardHandler()->renderWizardLandingSnippet();
1224
  }
@@ -1273,7 +1194,7 @@ class BaseModCon {
1273
  /**
1274
  * @return string
1275
  */
1276
- protected function getUrl_WizardLanding() {
1277
  return $this->getUrl_Wizard( 'landing' );
1278
  }
1279
 
@@ -1298,11 +1219,8 @@ class BaseModCon {
1298
  return is_array( $aW ) ? $aW : [];
1299
  }
1300
 
1301
- /**
1302
- * @return bool
1303
- */
1304
- public function hasWizard() {
1305
- return ( count( $this->getWizardDefinitions() ) > 0 );
1306
  }
1307
 
1308
  /**
@@ -1317,7 +1235,7 @@ class BaseModCon {
1317
  /**
1318
  * @return bool
1319
  */
1320
- protected function getIsShowMarketing() {
1321
  return apply_filters( $this->prefix( 'show_marketing' ), !$this->isPremium() );
1322
  }
1323
 
@@ -1337,7 +1255,7 @@ class BaseModCon {
1337
  return $this->getCon()
1338
  ->getRenderer()
1339
  ->setTemplate( $sTemplate )
1340
- ->setRenderVars( $this->getBaseDisplayData() )
1341
  ->setTemplateEngineTwig()
1342
  ->render();
1343
  }
@@ -1349,7 +1267,7 @@ class BaseModCon {
1349
  /**
1350
  * @return bool
1351
  */
1352
- protected function canDisplayOptionsForm() {
1353
  return $this->getOptions()->isAccessRestricted() ? $this->getCon()->isPluginAdmin() : true;
1354
  }
1355
 
@@ -1419,32 +1337,32 @@ class BaseModCon {
1419
  return $this->renderTemplate( $sTemplate, $aData, $bTwig );
1420
  }
1421
 
1422
- /**
1423
- * @param string $sTemplate
1424
- * @param array $aData
1425
- * @param bool $bUseTwig
1426
- * @return string
1427
- */
1428
- public function renderTemplate( $sTemplate, $aData = [], $bUseTwig = false ) {
1429
- if ( empty( $aData[ 'unique_render_id' ] ) ) {
1430
- $aData[ 'unique_render_id' ] = 'noticeid-'.substr( md5( mt_rand() ), 0, 5 );
1431
  }
1432
  try {
1433
  $oRndr = $this->getCon()->getRenderer();
1434
- if ( $bUseTwig || preg_match( '#^.*\.twig$#i', $sTemplate ) ) {
1435
  $oRndr->setTemplateEngineTwig();
1436
  }
1437
 
1438
- $sOutput = $oRndr->setTemplate( $sTemplate )
1439
- ->setRenderVars( $aData )
1440
- ->render();
 
 
 
 
 
 
1441
  }
1442
  catch ( \Exception $oE ) {
1443
- $sOutput = $oE->getMessage();
1444
  error_log( $oE->getMessage() );
1445
  }
1446
 
1447
- return $sOutput;
1448
  }
1449
 
1450
  /**
@@ -1459,25 +1377,10 @@ class BaseModCon {
1459
  return $aTransferableOptions;
1460
  }
1461
 
1462
- /**
1463
- * @return array
1464
- */
1465
- public function collectOptionsForTracking() {
1466
- $oVO = $this->getOptions();
1467
- $aOptionsData = $this->getOptions()->getOptionsMaskSensitive();
1468
- foreach ( $aOptionsData as $sOption => $mValue ) {
1469
- unset( $aOptionsData[ $sOption ] );
1470
- // some cleaning to ensure we don't have disallowed characters
1471
- $sOption = preg_replace( '#[^_a-z]#', '', strtolower( $sOption ) );
1472
- $sType = $oVO->getOptionType( $sOption );
1473
- if ( $sType == 'checkbox' ) { // only want a boolean 1 or 0
1474
- $aOptionsData[ $sOption ] = (int)( $mValue == 'Y' );
1475
- }
1476
- else {
1477
- $aOptionsData[ $sOption ] = $mValue;
1478
- }
1479
- }
1480
- return $aOptionsData;
1481
  }
1482
 
1483
  /**
@@ -1502,160 +1405,172 @@ class BaseModCon {
1502
  return $aData;
1503
  }
1504
 
1505
- protected function getHelpVideoOptions() {
1506
- $aOptions = $this->getOpt( 'help_video_options', [] );
1507
- if ( is_null( $aOptions ) || !is_array( $aOptions ) ) {
1508
- $aOptions = [
1509
- 'closed' => false,
1510
- 'displayed' => false,
1511
- 'played' => false,
1512
- ];
1513
- $this->setOpt( 'help_video_options', $aOptions );
 
 
1514
  }
1515
- return $aOptions;
1516
  }
1517
 
1518
  /**
1519
- * @return bool
 
1520
  */
1521
- protected function getHelpVideoHasBeenClosed() {
1522
- return (bool)$this->getHelpVideoOption( 'closed' );
 
 
 
 
 
 
1523
  }
1524
 
1525
  /**
1526
- * @return bool
1527
  */
1528
- protected function getHelpVideoHasBeenDisplayed() {
1529
- return (bool)$this->getHelpVideoOption( 'displayed' );
1530
  }
1531
 
1532
  /**
1533
- * @return bool
1534
  */
1535
- protected function getVideoHasBeenPlayed() {
1536
- return (bool)$this->getHelpVideoOption( 'played' );
 
 
 
 
 
 
 
1537
  }
1538
 
1539
  /**
1540
- * @param string $sKey
1541
- * @return mixed|null
1542
  */
1543
- protected function getHelpVideoOption( $sKey ) {
1544
- $aOpts = $this->getHelpVideoOptions();
1545
- return isset( $aOpts[ $sKey ] ) ? $aOpts[ $sKey ] : null;
 
 
1546
  }
1547
 
1548
- /**
1549
- * @return bool
1550
- */
1551
- protected function getIfAutoShowHelpVideo() {
1552
- return !$this->getHelpVideoHasBeenClosed();
1553
  }
1554
 
1555
- /**
1556
- * @return string
1557
- */
1558
- protected function getHelpVideoId() {
1559
- return $this->getDef( 'help_video_id' );
1560
  }
1561
 
1562
  /**
1563
- * @param string $sId
1564
- * @return string
1565
  */
1566
- protected function getHelpVideoUrl( $sId ) {
1567
- return sprintf( 'https://player.vimeo.com/video/%s', $sId );
1568
  }
1569
 
1570
- /**
1571
- * @return bool
1572
- */
1573
- protected function isHelpVideoDisplayable() {
1574
- return false;
 
 
 
1575
  }
1576
 
1577
  /**
1578
- * @return null|Shield\Modules\Base\ShieldOptions|mixed
1579
  */
1580
- public function getOptions() {
1581
- if ( !isset( $this->oOpts ) ) {
1582
- $oCon = $this->getCon();
1583
- $this->oOpts = $this->loadOptions()->setMod( $this );
1584
- $this->oOpts->setPathToConfig( $oCon->getPath_ConfigFile( $this->getSlug() ) )
1585
- ->setRebuildFromFile( $oCon->getIsRebuildOptionsFromFile() )
1586
- ->setOptionsStorageKey( $this->getOptionsStorageKey() )
1587
- ->setIfLoadOptionsFromStorage( !$oCon->getIsResetPlugin() );
1588
- }
1589
- return $this->oOpts;
1590
  }
1591
 
1592
  /**
1593
- * @return string
 
 
1594
  */
1595
- private function getNamespace() {
 
1596
  try {
1597
- $sNS = ( new \ReflectionClass( $this ) )->getNamespaceName();
 
 
 
 
 
1598
  }
1599
- catch ( \Exception $oE ) {
1600
- $sNS = __NAMESPACE__;
1601
  }
1602
- return rtrim( $sNS, '\\' ).'\\';
1603
  }
1604
 
1605
  /**
1606
- * @return null|Shield\Modules\Base\Strings
 
 
 
1607
  */
1608
- public function getStrings() {
1609
- if ( !isset( $this->oStrings ) ) {
1610
- $this->oStrings = $this->loadStrings()->setMod( $this );
 
 
 
 
 
 
 
 
 
 
 
 
1611
  }
1612
- return $this->oStrings;
1613
- }
1614
 
1615
- /**
1616
- * @return $this;
1617
- */
1618
- private function loadAdminNotices() {
1619
- $oNotices = $this->loadClass( 'AdminNotices' );
1620
- if ( $oNotices instanceof Shield\Modules\Base\AdminNotices ) {
1621
- $oNotices->setMod( $this )->run();
1622
  }
1623
- return $this;
1624
  }
1625
 
1626
- /**
1627
- * All modules have an AJAX handler
1628
- * @return $this
1629
- */
1630
- private function loadAjaxHandler() {
1631
- $oAj = $this->loadClass( 'AjaxHandler' );
1632
- if ( !$oAj instanceof Shield\Modules\Base\AjaxHandlerBase ) {
1633
- $oAj = new Shield\Modules\Base\AjaxHandlerShield(); // TODO: Provide a better fallback
1634
- }
1635
- $oAj->setMod( $this );
1636
- return $this;
1637
  }
1638
 
1639
- /**
1640
- * @return Shield\Modules\Base\ShieldOptions|mixed
1641
- */
1642
- protected function loadOptions() {
1643
- return $this->loadClass( 'Options' );
1644
  }
1645
 
1646
- /**
1647
- * @return Shield\Modules\Base\Strings|mixed
1648
- */
1649
- protected function loadStrings() {
1650
- return $this->loadClass( 'Strings' );
1651
  }
1652
 
1653
  /**
1654
- * @param $sClass
1655
- * @return \stdClass|mixed|false
 
1656
  */
1657
- private function loadClass( $sClass ) {
1658
- $sC = $this->getNamespace().$sClass;
1659
- return @class_exists( $sC ) ? new $sC() : false;
1660
  }
1661
  }
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
+ /**
10
+ * Class ModCon
11
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Base
12
+ */
13
+ abstract class ModCon {
14
 
15
  use Modules\PluginControllerConsumer;
16
+ use Shield\Crons\PluginCronsConsumer;
17
 
18
  /**
19
  * @var string
31
  protected $bImportExportWhitelistNotify = false;
32
 
33
  /**
34
+ * @var Shield\Modules\Base\BaseProcessor
 
 
 
 
 
35
  */
36
  private $oProcessor;
37
 
41
  private $oWizard;
42
 
43
  /**
44
+ * @var Shield\Modules\Base\Reporting
45
  */
46
+ private $oReporting;
47
 
48
  /**
49
+ * @var Shield\Modules\Base\UI
50
  */
51
+ private $oUI;
52
 
53
  /**
54
  * @var Shield\Modules\Base\Options
55
  */
56
  private $oOpts;
57
 
58
+ /**
59
+ * @var Shield\Modules\Base\WpCli
60
+ */
61
+ private $oWpCli;
62
+
63
  /**
64
  * @var Shield\Databases\Base\Handler[]
65
  */
66
  private $aDbHandlers;
67
 
68
  /**
69
+ * @param Shield\Controller\Controller $pluginCon
70
  * @param array $aMod
71
  * @throws \Exception
72
  */
73
+ public function __construct( $pluginCon, $aMod = [] ) {
74
+ if ( !$pluginCon instanceof Shield\Controller\Controller ) {
75
  throw new \Exception( 'Plugin controller not supplied to Module' );
76
  }
77
+ $this->setCon( $pluginCon );
78
 
79
  if ( empty( $aMod[ 'storage_key' ] ) && empty( $aMod[ 'slug' ] ) ) {
80
  throw new \Exception( 'Module storage key AND slug are undefined' );
86
  }
87
 
88
  if ( $this->verifyModuleMeetRequirements() ) {
89
+ $this->handleAutoPageRedirects();
90
  $this->setupHooks( $aMod );
91
  $this->doPostConstruction();
92
  }
93
  }
94
 
95
+ protected function setupHooks( array $aModProps ) {
96
+ $con = $this->getCon();
 
 
 
 
97
  $nRunPriority = isset( $aModProps[ 'load_priority' ] ) ? $aModProps[ 'load_priority' ] : 100;
 
 
98
 
99
+ add_action( $con->prefix( 'modules_loaded' ), function () {
100
+ $this->onModulesLoaded();
101
+ }, $nRunPriority );
102
+ add_action( $con->prefix( 'run_processors' ), [ $this, 'onRunProcessors' ], $nRunPriority );
103
+ add_action( 'init', [ $this, 'onWpInit' ], 1 );
 
 
 
 
 
 
 
104
 
105
  $nMenuPri = isset( $aModProps[ 'menu_priority' ] ) ? $aModProps[ 'menu_priority' ] : 100;
106
+ add_filter( $con->prefix( 'submenu_items' ), [ $this, 'supplySubMenuItem' ], $nMenuPri );
107
+ add_action( $con->prefix( 'plugin_shutdown' ), [ $this, 'onPluginShutdown' ] );
108
+ add_action( $con->prefix( 'deactivate_plugin' ), [ $this, 'onPluginDeactivate' ] );
109
+ add_action( $con->prefix( 'delete_plugin' ), [ $this, 'onPluginDelete' ] );
110
+ add_filter( $con->prefix( 'aggregate_all_plugin_options' ), [ $this, 'aggregateOptionsValues' ] );
111
+
112
+ add_filter( $con->prefix( 'register_admin_notices' ), [ $this, 'fRegisterAdminNotices' ] );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
  add_action( 'admin_enqueue_scripts', [ $this, 'onWpEnqueueAdminJs' ], 100 );
115
 
120
  // if ( $this->isAdminOptionsPage() ) {
121
  // add_action( 'current_screen', array( $this, 'onSetCurrentScreen' ) );
122
  // }
123
+ $this->setupCronHooks();
124
  $this->setupCustomHooks();
125
  }
126
 
134
  $this->cleanupDatabases();
135
  }
136
 
 
 
 
137
  protected function cleanupDatabases() {
138
+ foreach ( $this->getDbHandlers( true ) as $dbh ) {
139
+ if ( $dbh instanceof Shield\Databases\Base\Handler && $dbh->isReady() ) {
140
+ $dbh->autoCleanDb();
141
  }
142
  }
143
  }
156
  }
157
 
158
  /**
159
+ * @param string $dbhKey
160
  * @return Shield\Databases\Base\Handler|mixed|false
161
  */
162
+ protected function getDbH( $dbhKey ) {
163
+ $dbh = false;
164
 
165
  if ( !is_array( $this->aDbHandlers ) ) {
166
  $this->aDbHandlers = [];
167
  }
168
 
169
+ if ( !empty( $this->aDbHandlers[ $dbhKey ] ) ) {
170
+ $dbh = $this->aDbHandlers[ $dbhKey ];
171
  }
172
  else {
173
  $aDbClasses = $this->getAllDbClasses();
174
+ if ( isset( $aDbClasses[ $dbhKey ] ) ) {
175
+ /** @var Shield\Databases\Base\Handler $dbh */
176
+ $dbh = new $aDbClasses[ $dbhKey ]();
177
  try {
178
+ $dbh->setMod( $this )->tableInit();
179
  }
180
+ catch ( \Exception $e ) {
181
  }
182
  }
183
+ $this->aDbHandlers[ $dbhKey ] = $dbh;
184
  }
185
 
186
+ return $dbh;
187
  }
188
 
189
  /**
190
  * @return string[]
191
  */
192
  private function getAllDbClasses() {
193
+ $classes = $this->getOptions()->getDef( 'db_classes' );
194
+ return is_array( $classes ) ? $classes : [];
195
  }
196
 
197
  /**
198
+ * @return false|Shield\Modules\Base\Upgrade|mixed
 
199
  */
200
+ public function getUpgradeHandler() {
201
+ return $this->loadModElement( 'Upgrade' );
202
  }
203
 
204
  /**
247
  return array_merge( $aAdminNotices, $this->getOptions()->getAdminNotices() );
248
  }
249
 
250
+ private function verifyModuleMeetRequirements() :bool {
 
 
 
251
  $bMeetsReqs = true;
252
 
253
  $aPhpReqs = $this->getOptions()->getFeatureRequirement( 'php' );
271
  return $bMeetsReqs;
272
  }
273
 
274
+ protected function onModulesLoaded() {
275
+ }
276
+
277
  public function onRunProcessors() {
278
+ $opts = $this->getOptions();
279
+ if ( $opts->getFeatureProperty( 'auto_load_processor' ) ) {
 
 
280
  $this->loadProcessor();
281
  }
282
+ try {
283
+ $bSkip = (bool)$opts->getFeatureProperty( 'skip_processor' );
284
+ if ( !$bSkip && !$this->isUpgrading() && $this->isModuleEnabled() && $this->isReadyToExecute() ) {
285
+ $this->doExecuteProcessor();
286
+ }
287
+ }
288
+ catch ( \Exception $e ) {
289
  }
290
  }
291
 
292
  /**
293
  * @return bool
294
+ * @throws \Exception
295
  */
296
+ protected function isReadyToExecute() :bool {
297
+ return !is_null( $this->getProcessor() );
298
  }
299
 
300
  protected function doExecuteProcessor() {
301
  $this->getProcessor()->execute();
302
  }
303
 
 
 
 
304
  public function onWpInit() {
305
+
306
+ $shieldAction = $this->getCon()->getShieldAction();
307
+ if ( !empty( $shieldAction ) ) {
308
+ do_action( $this->getCon()->prefix( 'shield_action' ), $shieldAction );
309
+ }
310
+
311
+ add_action( 'cli_init', function () {
312
+ try {
313
+ $this->getWpCli()->execute();
314
+ }
315
+ catch ( \Exception $e ) {
316
+ }
317
+ } );
318
+
319
+ if ( $this->isModuleRequest() ) {
320
+
321
+ if ( Services::WpGeneral()->isAjax() ) {
322
+ $this->loadAjaxHandler();
323
+ }
324
+ else {
325
+ try {
326
+ if ( $this->verifyModActionRequest() ) {
327
+ $this->handleModAction( Services::Request()->request( 'exec' ) );
328
+ }
329
+ }
330
+ catch ( \Exception $e ) {
331
+ wp_nonce_ays( '' );
332
+ }
333
+ }
334
+ }
335
 
336
  $this->runWizards();
337
 
340
  add_filter( $this->prefix( 'wpPrivacyExport' ), [ $this, 'onWpPrivacyExport' ], 10, 3 );
341
  add_filter( $this->prefix( 'wpPrivacyErase' ), [ $this, 'onWpPrivacyErase' ], 10, 3 );
342
  }
343
+
344
+ $this->loadDebug();
345
  }
346
 
347
  /**
361
 
362
  /**
363
  * Override this and adapt per feature
364
+ * @return Shield\Modules\Base\BaseProcessor|mixed
365
  */
366
  protected function loadProcessor() {
367
  if ( !isset( $this->oProcessor ) ) {
368
+ try {
369
+ // TODO: Remove 'abstract' from base processor after transition to new processors is complete
370
+ $class = $this->findElementClass( 'Processor', true );
371
+ }
372
+ catch ( \Exception $e ) {
373
+ $class = $this->getProcessorClassName();
374
+ }
375
+ if ( !@class_exists( $class ) ) {
376
  return null;
377
  }
378
+ $this->oProcessor = new $class( $this );
379
  }
380
  return $this->oProcessor;
381
  }
382
 
383
  /**
384
+ * This is the old method
385
+ * @deprecated 10.1
386
  */
387
+ protected function getProcessorClassName() :string {
388
+ return '\\'.implode( '_',
389
+ [
390
+ strtoupper( $this->getCon()->getPluginPrefix( '_' ) ),
391
+ 'Processor',
392
+ str_replace( ' ', '', ucwords( str_replace( '_', ' ', $this->getSlug() ) ) )
393
+ ]
394
+ );
395
  }
396
 
397
  /**
408
  );
409
  }
410
 
411
+ public function isUpgrading() :bool {
 
 
 
412
  return $this->getCon()->getIsRebuildOptionsFromFile() || $this->getOptions()->getRebuildFromFile();
413
  }
414
 
425
  }
426
  }
427
 
428
+ public function getOptionsStorageKey() :string {
 
 
 
429
  return $this->getCon()->prefixOption( $this->sOptionsStoreKey ).'_options';
430
  }
431
 
432
  /**
433
+ * @return Shield\Modules\Base\BaseProcessor|\FernleafSystems\Utilities\Logic\OneTimeExecute|mixed
434
  */
435
  public function getProcessor() {
436
  return $this->loadProcessor();
437
  }
438
 
439
+ public function getUrl_AdminPage() :string {
 
 
 
440
  return Services::WpGeneral()
441
  ->getUrl_AdminPage(
442
  $this->getModSlug(),
445
  }
446
 
447
  /**
448
+ * @param string $sAction
449
  * @return string
450
  */
451
+ public function buildAdminActionNonceUrl( $sAction ) {
452
+ $aActionNonce = $this->getNonceActionData( $sAction );
453
+ $aActionNonce[ 'ts' ] = Services::Request()->ts();
454
+ return add_query_arg( $aActionNonce, $this->getUrl_AdminPage() );
455
+ }
456
+
457
+ protected function getModActionParams( string $action ) :array {
458
+ $con = $this->getCon();
459
+ return [
460
+ 'action' => $con->prefix(),
461
+ 'exec' => $action,
462
+ 'mod_slug' => $this->getModSlug(),
463
+ 'ts' => Services::Request()->ts(),
464
+ 'exec_nonce' => substr(
465
+ hash_hmac( 'md5', $action.Services::Request()->ts(), $con->getSiteInstallationId() )
466
+ , 0, 6 )
467
+ ];
468
  }
469
 
470
  /**
471
+ * @return bool
472
+ * @throws \Exception
473
  */
474
+ protected function verifyModActionRequest() :bool {
475
+ $bValid = false;
476
+
477
+ $con = $this->getCon();
478
+ $req = Services::Request();
479
+
480
+ $sExec = $req->request( 'exec' );
481
+ if ( !empty( $sExec ) && $req->request( 'action' ) == $con->prefix() ) {
482
+
483
+
484
+ if ( wp_verify_nonce( $req->request( 'exec_nonce' ), $sExec ) && $con->getMeetsBasePermissions() ) {
485
+ $bValid = true;
486
+ }
487
+ else {
488
+ $bValid = $req->request( 'exec_nonce' ) ===
489
+ substr( hash_hmac( 'md5', $sExec.$req->request( 'ts' ), $con->getSiteInstallationId() ), 0, 6 );
490
+ }
491
+ if ( !$bValid ) {
492
+ throw new \Exception( 'Invalid request' );
493
+ }
494
+ }
495
+
496
+ return $bValid;
497
+ }
498
+
499
+ public function getUrl_DirectLinkToOption( string $key ) :string {
500
+ $url = $this->getUrl_AdminPage();
501
+ $def = $this->getOptions()->getOptDefinition( $key );
502
+ if ( !empty( $def[ 'section' ] ) ) {
503
+ $url = $this->getUrl_DirectLinkToSection( $def[ 'section' ] );
504
  }
505
+ return $url;
506
+ }
507
+
508
+ public function getUrl_DirectLinkToSection( string $section ) :string {
509
+ if ( $section == 'primary' ) {
510
+ $section = $this->getOptions()->getPrimarySection()[ 'slug' ];
511
+ }
512
+ return $this->getUrl_AdminPage().'#tab-'.$section;
513
  }
514
 
515
  /**
516
  * TODO: Get rid of this crap and/or handle the \Exception thrown in loadFeatureHandler()
517
+ * @return Modules\Email\ModCon
518
  * @throws \Exception
519
+ * @deprecated 10.1
520
  */
521
  public function getEmailHandler() {
522
+ return $this->getCon()->getModule_Email();
 
 
 
523
  }
524
 
525
  /**
526
+ * @return Modules\Email\Processor
527
  */
528
  public function getEmailProcessor() {
529
  return $this->getEmailHandler()->getProcessor();
530
  }
531
 
532
  /**
533
+ * @param bool $enable
534
  * @return $this
535
  */
536
+ public function setIsMainFeatureEnabled( bool $enable ) {
537
+ $this->getOptions()->setOpt( 'enable_'.$this->getSlug(), $enable ? 'Y' : 'N' );
538
+ return $this;
539
  }
540
 
541
+ public function isModuleEnabled() :bool {
 
 
 
 
542
  /** @var Shield\Modules\Plugin\Options $oPluginOpts */
543
  $oPluginOpts = $this->getCon()->getModule_Plugin()->getOptions();
544
 
545
  if ( $this->getOptions()->getFeatureProperty( 'auto_enabled' ) === true ) {
546
  // Auto enabled modules always run regardless
547
+ $enabled = true;
548
  }
549
  elseif ( $oPluginOpts->isPluginGloballyDisabled() ) {
550
+ $enabled = false;
551
  }
552
  elseif ( $this->getCon()->getIfForceOffActive() ) {
553
+ $enabled = false;
554
  }
555
+ elseif ( $this->getOptions()->getFeatureProperty( 'premium' ) === true
556
+ && !$this->isPremium() ) {
557
+ $enabled = false;
558
  }
559
  else {
560
+ $enabled = $this->isModOptEnabled();
561
  }
562
 
563
+ return $enabled;
564
  }
565
 
566
+ public function isModOptEnabled() :bool {
567
+ $opts = $this->getOptions();
568
+ return $opts->isOpt( $this->getEnableModOptKey(), 'Y' )
569
+ || $opts->isOpt( $this->getEnableModOptKey(), true, true );
 
 
570
  }
571
 
572
+ public function getEnableModOptKey() :string {
 
 
 
573
  return 'enable_'.$this->getSlug();
574
  }
575
 
576
+ public function getMainFeatureName() :string {
 
 
 
577
  return __( $this->getOptions()->getFeatureProperty( 'name' ), 'wp-simple-firewall' );
578
  }
579
 
580
+ public function getModSlug( bool $prefix = true ) :string {
581
+ return $prefix ? $this->prefix( $this->getSlug() ) : $this->getSlug();
 
 
 
 
582
  }
583
 
584
  /**
621
  if ( !empty( $aAdditionalItems ) && is_array( $aAdditionalItems ) ) {
622
 
623
  foreach ( $aAdditionalItems as $aMenuItem ) {
 
 
 
 
 
624
  $sMenuPageTitle = $sHumanName.' - '.$aMenuItem[ 'title' ];
625
  $aItems[ $sMenuPageTitle ] = [
626
+ __( $aMenuItem[ 'title' ], 'wp-simple-firewall' ),
627
  $this->prefix( $aMenuItem[ 'slug' ] ),
628
+ [ $this, $aMenuItem[ 'callback' ] ],
629
+ true
630
  ];
631
  }
632
  }
635
  }
636
 
637
  /**
638
+ * Handles the case where we want to redirect certain menu requests to other pages
639
+ * of the plugin automatically. It lets us create custom menu items.
640
+ * This can of course be extended for any other types of redirect.
641
  */
642
+ public function handleAutoPageRedirects() {
643
+ $aConf = $this->getOptions()->getRawData_FullFeatureConfig();
644
+ if ( !empty( $aConf[ 'custom_redirects' ] ) && $this->getCon()->isValidAdminArea() ) {
645
+ foreach ( $aConf[ 'custom_redirects' ] as $aRedirect ) {
646
+ if ( Services::Request()->query( 'page' ) == $this->prefix( $aRedirect[ 'source_mod_page' ] ) ) {
647
+ Services::Response()->redirect(
648
+ $this->getCon()->getModule( $aRedirect[ 'target_mod_page' ] )->getUrl_AdminPage(),
649
+ $aRedirect[ 'query_args' ],
650
+ true,
651
+ false
652
+ );
653
+ }
654
+ }
655
+ }
656
  }
657
 
658
  /**
 
659
  * @return array
660
  */
661
+ protected function getAdditionalMenuItem() {
662
+ return [];
 
 
 
663
  }
664
 
665
  /**
666
+ * TODO: not the place for this method.
667
+ * @return array[]
668
  */
669
+ public function getModulesSummaryData() {
670
+ return array_map(
671
+ function ( $mod ) {
672
+ return $mod->buildSummaryData();
673
+ },
674
+ $this->getCon()->modules
675
+ );
676
  }
677
 
678
  /**
 
679
  * @return array
680
  */
681
+ public function buildSummaryData() {
682
+ $opts = $this->getOptions();
683
+ $sMenuTitle = $opts->getFeatureProperty( 'menu_title' );
684
+
685
+ $aSections = $opts->getSections();
686
+ foreach ( $aSections as $sSlug => $aSection ) {
687
+ try {
688
+ $aStrings = $this->getStrings()->getSectionStrings( $aSection[ 'slug' ] );
689
+ foreach ( $aStrings as $sKey => $sVal ) {
690
+ unset( $aSection[ $sKey ] );
691
+ $aSection[ $sKey ] = $sVal;
692
+ }
693
+ }
694
+ catch ( \Exception $e ) {
695
+ }
696
+ }
697
 
698
+ $aSum = [
699
+ 'slug' => $this->getSlug(),
700
+ 'enabled' => $this->getUIHandler()->isEnabledForUiSummary(),
701
+ 'active' => $this->isThisModulePage() || $this->isPage_InsightsThisModule(),
702
+ 'name' => $this->getMainFeatureName(),
703
+ 'sidebar_name' => $opts->getFeatureProperty( 'sidebar_name' ),
704
+ 'menu_title' => empty( $sMenuTitle ) ? $this->getMainFeatureName() : __( $sMenuTitle, 'wp-simple-firewall' ),
705
+ 'href' => network_admin_url( 'admin.php?page='.$this->getModSlug() ),
706
+ 'sections' => $aSections,
707
+ 'options' => [],
708
+ 'show_mod_opts' => $this->getIfShowModuleOpts(),
709
+ ];
710
+
711
+ foreach ( $opts->getVisibleOptionsKeys() as $sOptKey ) {
712
+ try {
713
+ $aOptData = $this->getStrings()->getOptionStrings( $sOptKey );
714
+ $aOptData[ 'href' ] = $this->getUrl_DirectLinkToOption( $sOptKey );
715
+ $aSum[ 'options' ][ $sOptKey ] = $aOptData;
716
+ }
717
+ catch ( \Exception $e ) {
718
+ }
719
+ }
720
+
721
+ $aSum[ 'tooltip' ] = sprintf(
722
+ '%s',
723
+ empty( $aSum[ 'sidebar_name' ] ) ? $aSum[ 'name' ] : __( $aSum[ 'sidebar_name' ], 'wp-simple-firewall' )
724
+ );
725
+ return $aSum;
726
  }
727
 
728
+ public function getIfShowModuleMenuItem() :bool {
 
 
 
729
  return (bool)$this->getOptions()->getFeatureProperty( 'show_module_menu_item' );
730
  }
731
 
732
+ public function getIfShowModuleOpts() :bool {
 
 
 
733
  return (bool)$this->getOptions()->getFeatureProperty( 'show_module_options' );
734
  }
735
 
 
 
 
 
 
 
 
736
  /**
737
  * Get config 'definition'.
738
+ * @param string $key
739
  * @return mixed|null
740
  */
741
+ public function getDef( string $key ) {
742
+ return $this->getOptions()->getDef( $key );
743
  }
744
 
745
  /**
754
  * @param string $sGlue
755
  * @return string|array
756
  */
757
+ public function getLastErrors( $bAsString = false, $sGlue = " " ) {
758
+ $errors = $this->getOptions()->getOpt( 'last_errors' );
759
+ if ( !is_array( $errors ) ) {
760
+ $errors = [];
761
  }
762
+ return $bAsString ? implode( $sGlue, $errors ) : $errors;
763
  }
764
 
765
+ public function hasLastErrors() :bool {
 
 
 
766
  return count( $this->getLastErrors( false ) ) > 0;
767
  }
768
 
769
+ public function getTextOpt( string $key ) :string {
770
+ $sValue = $this->getOptions()->getOpt( $key, 'default' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
771
  if ( $sValue == 'default' ) {
772
+ $sValue = $this->getTextOptDefault( $key );
773
  }
774
+ return __( $sValue, 'wp-simple-firewall' );
775
  }
776
 
777
+ public function getTextOptDefault( string $key ) :string {
 
 
 
 
 
778
  return 'Undefined Text Opt Default';
779
  }
780
 
791
  $mErrors = [];
792
  }
793
  }
794
+ $this->getOptions()->setOpt( 'last_errors', $mErrors );
 
 
 
 
 
 
 
 
 
 
 
795
  return $this;
796
  }
797
 
798
+ public function setOptions( array $options ) {
799
+ $opts = $this->getOptions();
800
+ foreach ( $options as $key => $value ) {
801
+ $opts->setOpt( $key, $value );
 
 
 
802
  }
803
  }
804
 
805
+ public function isModuleRequest() :bool {
806
+ return $this->getModSlug() === Services::Request()->request( 'mod_slug' );
 
 
 
807
  }
808
 
809
  /**
810
+ * @param string $action
811
+ * @param bool $asJson
812
+ * @return array|string
813
  */
814
+ public function getAjaxActionData( string $action = '', $asJson = false ) {
815
+ $data = $this->getNonceActionData( $action );
816
+ $data[ 'ajaxurl' ] = admin_url( 'admin-ajax.php' );
817
+ return $asJson ? json_encode( (object)$data ) : $data;
818
  }
819
 
820
  /**
830
  /**
831
  * @return string[]
832
  */
833
+ public function getDismissedNotices() :array {
834
+ $notices = $this->getOptions()->getOpt( 'dismissed_notices' );
835
+ return is_array( $notices ) ? $notices : [];
836
  }
837
 
838
  /**
839
  * @return string[]
840
  */
841
+ public function getUiTrack() :array {
842
+ $a = $this->getOptions()->getOpt( 'ui_track' );
843
+ return is_array( $a ) ? $a : [];
844
  }
845
 
846
+ public function setDismissedNotices( array $dis ) {
847
+ $this->getOptions()->setOpt( 'dismissed_notices', $dis );
 
 
 
 
848
  }
849
 
850
+ public function setUiTrack( array $UI ) {
851
+ $this->getOptions()->setOpt( 'ui_track', $UI );
 
 
 
 
852
  }
853
 
854
  /**
867
  }
868
 
869
  /**
870
+ * @param bool $bPreProcessOptions
871
  * @return $this
872
  */
873
+ public function saveModOptions( $bPreProcessOptions = false ) {
874
+
875
+ if ( $bPreProcessOptions ) {
876
+ $this->preProcessOptions();
877
+ }
878
+
879
  $this->doPrePluginOptionsSave();
880
  if ( apply_filters( $this->prefix( 'force_options_resave' ), false ) ) {
881
  $this->getOptions()
888
  return $this;
889
  }
890
 
891
+ protected function preProcessOptions() {
892
+ }
893
+
894
  private function store() {
895
  add_filter( $this->prefix( 'bypass_is_plugin_admin' ), '__return_true', 1000 );
896
  $this->getOptions()
916
  }
917
 
918
  public function onPluginDelete() {
919
+ foreach ( $this->getDbHandlers( true ) as $oDbh ) {
920
+ if ( !empty( $oDbh ) ) {
921
+ $oDbh->tableDelete();
922
+ }
923
+ }
924
  $this->getOptions()->deleteStorage();
925
  }
926
 
930
  protected function getAllFormOptionsAndTypes() {
931
  $aOpts = [];
932
 
933
+ foreach ( $this->getUIHandler()->buildOptions() as $aOptionsSection ) {
934
  if ( !empty( $aOptionsSection ) ) {
935
  foreach ( $aOptionsSection[ 'options' ] as $aOption ) {
936
  $aOpts[ $aOption[ 'key' ] ] = $aOption[ 'type' ];
941
  return $aOpts;
942
  }
943
 
944
+ protected function handleModAction( string $action ) {
945
  }
946
 
947
  /**
951
  if ( !$this->getCon()->isPluginAdmin() ) {
952
  throw new \Exception( __( "You don't currently have permission to save settings.", 'wp-simple-firewall' ) );
953
  }
954
+
955
  $this->doSaveStandardOptions();
 
 
956
 
957
+ $this->saveModOptions( true );
958
+
959
+ // only use this flag when the options are being updated with a MANUAL save.
960
+ if ( isset( $this->bImportExportWhitelistNotify ) && $this->bImportExportWhitelistNotify ) {
961
+ if ( !wp_next_scheduled( $this->prefix( 'importexport_notify' ) ) ) {
962
+ wp_schedule_single_event( Services::Request()->ts() + 15, $this->prefix( 'importexport_notify' ) );
963
+ }
964
+ }
965
  }
966
 
967
  /**
981
  return $this;
982
  }
983
 
984
+ protected function isAdminOptionsPage() :bool {
985
+ return is_admin() && !Services::WpGeneral()->isAjax() && $this->isThisModulePage();
 
 
 
986
  }
987
 
988
  /**
1043
  elseif ( $sOptType == 'comma_separated_lists' ) {
1044
  $sOptionValue = Services::Data()->extractCommaSeparatedList( $sOptionValue );
1045
  }
1046
+ /* elseif ( $sOptType == 'multiple_select' ) { } */
 
1047
  }
1048
 
1049
  // Prevent overwriting of non-editable fields
1050
  if ( !in_array( $sOptType, [ 'noneditable_text' ] ) ) {
1051
+ $this->getOptions()->setOpt( $sKey, $sOptionValue );
1052
  }
1053
  }
1054
 
1055
+ // Handle Import/Export exclusions
1056
+ if ( $this->isPremium() ) {
1057
+ ( new Shield\Modules\Plugin\Lib\ImportExport\Options\SaveExcludedOptions() )
1058
+ ->setMod( $this )
1059
+ ->save( $aForm );
 
 
1060
  }
1061
  }
1062
 
1116
  return $this->getCon()->prefix( $sSuffix, $sGlue );
1117
  }
1118
 
 
 
 
 
 
 
 
 
1119
  /**
1120
  * @uses echo()
1121
  */
1122
  public function displayModuleAdminPage() {
1123
+ echo $this->renderModulePage();
 
 
 
 
 
1124
  }
1125
 
1126
  /**
1128
  * @param array $aData
1129
  * @return string
1130
  */
1131
+ protected function renderModulePage( array $aData = [] ) :string {
1132
+ return $this->renderTemplate(
1133
+ 'index.php',
1134
+ Services::DataManipulation()->mergeArraysRecursive( $this->getUIHandler()->getBaseDisplayData(), $aData )
1135
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1136
  }
1137
 
1138
  /**
1139
  * @return string
1140
  */
1141
  protected function getContentWizardLanding() {
1142
+ $aData = $this->getUIHandler()->getBaseDisplayData();
1143
  if ( $this->hasWizard() ) {
1144
  $aData[ 'content' ][ 'wizard_landing' ] = $this->getWizardHandler()->renderWizardLandingSnippet();
1145
  }
1194
  /**
1195
  * @return string
1196
  */
1197
+ public function getUrl_WizardLanding() {
1198
  return $this->getUrl_Wizard( 'landing' );
1199
  }
1200
 
1219
  return is_array( $aW ) ? $aW : [];
1220
  }
1221
 
1222
+ public function hasWizard() :bool {
1223
+ return count( $this->getWizardDefinitions() ) > 0;
 
 
 
1224
  }
1225
 
1226
  /**
1235
  /**
1236
  * @return bool
1237
  */
1238
+ public function getIsShowMarketing() {
1239
  return apply_filters( $this->prefix( 'show_marketing' ), !$this->isPremium() );
1240
  }
1241
 
1255
  return $this->getCon()
1256
  ->getRenderer()
1257
  ->setTemplate( $sTemplate )
1258
+ ->setRenderVars( $this->getUIHandler()->getBaseDisplayData() )
1259
  ->setTemplateEngineTwig()
1260
  ->render();
1261
  }
1267
  /**
1268
  * @return bool
1269
  */
1270
+ public function canDisplayOptionsForm() {
1271
  return $this->getOptions()->isAccessRestricted() ? $this->getCon()->isPluginAdmin() : true;
1272
  }
1273
 
1337
  return $this->renderTemplate( $sTemplate, $aData, $bTwig );
1338
  }
1339
 
1340
+ public function renderTemplate( string $template, array $data = [], bool $isTwig = false ) :string {
1341
+ if ( empty( $data[ 'unique_render_id' ] ) ) {
1342
+ $data[ 'unique_render_id' ] = 'noticeid-'.substr( md5( mt_rand() ), 0, 5 );
 
 
 
 
 
 
1343
  }
1344
  try {
1345
  $oRndr = $this->getCon()->getRenderer();
1346
+ if ( $isTwig || preg_match( '#^.*\.twig$#i', $template ) ) {
1347
  $oRndr->setTemplateEngineTwig();
1348
  }
1349
 
1350
+ $data[ 'strings' ] = Services::DataManipulation()
1351
+ ->mergeArraysRecursive(
1352
+ $this->getStrings()->getDisplayStrings(),
1353
+ $data[ 'strings' ] ?? []
1354
+ );
1355
+
1356
+ $render = $oRndr->setTemplate( $template )
1357
+ ->setRenderVars( $data )
1358
+ ->render();
1359
  }
1360
  catch ( \Exception $oE ) {
1361
+ $render = $oE->getMessage();
1362
  error_log( $oE->getMessage() );
1363
  }
1364
 
1365
+ return (string)$render;
1366
  }
1367
 
1368
  /**
1377
  return $aTransferableOptions;
1378
  }
1379
 
1380
+ public function getMainWpData() :array {
1381
+ return [
1382
+ 'options' => $this->getOptions()->getTransferableOptions()
1383
+ ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1384
  }
1385
 
1386
  /**
1405
  return $aData;
1406
  }
1407
 
1408
+ /**
1409
+ * @return null|Shield\Modules\Base\ShieldOptions|mixed
1410
+ */
1411
+ public function getOptions() {
1412
+ if ( !isset( $this->oOpts ) ) {
1413
+ $oCon = $this->getCon();
1414
+ $this->oOpts = $this->loadModElement( 'Options' );
1415
+ $this->oOpts->setPathToConfig( $oCon->getPath_ConfigFile( $this->getSlug() ) )
1416
+ ->setRebuildFromFile( $oCon->getIsRebuildOptionsFromFile() )
1417
+ ->setOptionsStorageKey( $this->getOptionsStorageKey() )
1418
+ ->setIfLoadOptionsFromStorage( !$oCon->getIsResetPlugin() );
1419
  }
1420
+ return $this->oOpts;
1421
  }
1422
 
1423
  /**
1424
+ * @return Shield\Modules\Base\WpCli
1425
+ * @throws \Exception
1426
  */
1427
+ public function getWpCli() {
1428
+ if ( !isset( $this->oWpCli ) ) {
1429
+ $this->oWpCli = $this->loadModElement( 'WpCli' );
1430
+ if ( !$this->oWpCli instanceof Shield\Modules\Base\WpCli ) {
1431
+ throw new \Exception( 'WP-CLI not supported' );
1432
+ }
1433
+ }
1434
+ return $this->oWpCli;
1435
  }
1436
 
1437
  /**
1438
+ * @return null|Shield\Modules\Base\Strings
1439
  */
1440
+ public function getStrings() {
1441
+ return $this->loadStrings()->setMod( $this );
1442
  }
1443
 
1444
  /**
1445
+ * @return Shield\Modules\Base\UI
1446
  */
1447
+ public function getUIHandler() {
1448
+ if ( !isset( $this->oUI ) ) {
1449
+ $this->oUI = $this->loadModElement( 'UI' );
1450
+ if ( !$this->oUI instanceof Shield\Modules\Base\UI ) {
1451
+ // TODO: autoloader for base classes
1452
+ $this->oUI = $this->loadModElement( 'ShieldUI' );
1453
+ }
1454
+ }
1455
+ return $this->oUI;
1456
  }
1457
 
1458
  /**
1459
+ * @return Shield\Modules\Base\Reporting|mixed|false
 
1460
  */
1461
+ public function getReportingHandler() {
1462
+ if ( !isset( $this->oReporting ) ) {
1463
+ $this->oReporting = $this->loadModElement( 'Reporting' );
1464
+ }
1465
+ return $this->oReporting;
1466
  }
1467
 
1468
+ protected function loadAdminNotices() {
1469
+ $N = $this->loadModElement( 'AdminNotices' );
1470
+ if ( $N instanceof Shield\Modules\Base\AdminNotices ) {
1471
+ $N->run();
1472
+ }
1473
  }
1474
 
1475
+ protected function loadAjaxHandler() {
1476
+ $this->loadModElement( 'AjaxHandler' );
 
 
 
1477
  }
1478
 
1479
  /**
1480
+ * @return Shield\Modules\Base\ShieldOptions|mixed
1481
+ * @deprecated 10.1
1482
  */
1483
+ protected function loadOptions() {
1484
+ return $this->loadModElement( 'Options' );
1485
  }
1486
 
1487
+ protected function loadDebug() {
1488
+ $req = Services::Request();
1489
+ if ( $req->query( 'debug' ) && $req->query( 'mod' ) == $this->getModSlug()
1490
+ && $this->getCon()->isPluginAdmin() ) {
1491
+ /** @var Shield\Modules\Base\Debug $debug */
1492
+ $debug = $this->loadModElement( 'Debug', true );
1493
+ $debug->run();
1494
+ }
1495
  }
1496
 
1497
  /**
1498
+ * @return Shield\Modules\Base\Strings|mixed
1499
  */
1500
+ protected function loadStrings() {
1501
+ return $this->loadModElement( 'Strings', true );
 
 
 
 
 
 
 
 
1502
  }
1503
 
1504
  /**
1505
+ * @param string $class
1506
+ * @param false $injectMod
1507
+ * @return false|Shield\Modules\ModConsumer
1508
  */
1509
+ private function loadModElement( string $class, $injectMod = true ) {
1510
+ $element = false;
1511
  try {
1512
+ $C = $this->findElementClass( $class, true );
1513
+ /** @var Shield\Modules\ModConsumer $element */
1514
+ $element = @class_exists( $C ) ? new $C() : false;
1515
+ if ( $injectMod && method_exists( $element, 'setMod' ) ) {
1516
+ $element->setMod( $this );
1517
+ }
1518
  }
1519
+ catch ( \Exception $e ) {
 
1520
  }
1521
+ return $element;
1522
  }
1523
 
1524
  /**
1525
+ * @param string $element
1526
+ * @param bool $bThrowException
1527
+ * @return string|null
1528
+ * @throws \Exception
1529
  */
1530
+ protected function findElementClass( string $element, $bThrowException = true ) {
1531
+ $theClass = null;
1532
+
1533
+ $roots = array_map( function ( $root ) {
1534
+ return rtrim( $root, '\\' ).'\\';
1535
+ }, $this->getNamespaceRoots() );
1536
+
1537
+ foreach ( $roots as $NS ) {
1538
+ $maybe = $NS.$element;
1539
+ if ( @class_exists( $maybe ) ) {
1540
+ if ( ( new \ReflectionClass( $maybe ) )->isInstantiable() ) {
1541
+ $theClass = $maybe;
1542
+ break;
1543
+ }
1544
+ }
1545
  }
 
 
1546
 
1547
+ if ( $bThrowException && is_null( $theClass ) ) {
1548
+ throw new \Exception( sprintf( 'Could not find class for element "%s".', $element ) );
 
 
 
 
 
1549
  }
1550
+ return $theClass;
1551
  }
1552
 
1553
+ protected function getBaseNamespace() {
1554
+ return __NAMESPACE__;
 
 
 
 
 
 
 
 
 
1555
  }
1556
 
1557
+ protected function getNamespace() :string {
1558
+ return ( new \ReflectionClass( $this ) )->getNamespaceName();
 
 
 
1559
  }
1560
 
1561
+ protected function getNamespaceRoots() :array {
1562
+ return [
1563
+ $this->getNamespace(),
1564
+ $this->getBaseNamespace()
1565
+ ];
1566
  }
1567
 
1568
  /**
1569
+ * Saves the options to the WordPress Options store.
1570
+ * @return void
1571
+ * @deprecated 8.4
1572
  */
1573
+ public function savePluginOptions() {
1574
+ $this->saveModOptions();
 
1575
  }
1576
  }
src/lib/src/Modules/Base/OneTimeExecute.php DELETED
@@ -1,37 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
-
5
- /**
6
- * Trait OneTimeExecute
7
- * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Base
8
- * @deprecated 10.0
9
- */
10
- trait OneTimeExecute {
11
-
12
- private $bExecuted = false;
13
-
14
- /**
15
- * @return bool
16
- */
17
- protected function canRun() {
18
- return true;
19
- }
20
-
21
- public function execute() {
22
- if ( !$this->isAlreadyExecuted() && $this->canRun() ) {
23
- $this->bExecuted = true;
24
- $this->run();
25
- }
26
- }
27
-
28
- /**
29
- * @return bool
30
- */
31
- protected function isAlreadyExecuted() {
32
- return (bool)$this->bExecuted;
33
- }
34
-
35
- protected function run() {
36
- }
37
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Base/Options.php CHANGED
@@ -364,10 +364,7 @@ class Options {
364
  return $aKeys;
365
  }
366
 
367
- /**
368
- * @return array
369
- */
370
- public function getOptionsForPluginUse() {
371
 
372
  $aOptionsData = [];
373
 
@@ -562,18 +559,15 @@ class Options {
562
  }
563
 
564
  /**
565
- * @param string $sOptKey
566
- * @param string $sProperty
567
  * @return mixed|null
568
  */
569
- public function getOptProperty( $sOptKey, $sProperty ) {
570
- $aOpt = $this->getRawData_SingleOption( $sOptKey );
571
- return ( is_array( $aOpt ) && isset( $aOpt[ $sProperty ] ) ) ? $aOpt[ $sProperty ] : null;
572
  }
573
 
574
- /**
575
- * @return array
576
- */
577
  public function getStoredOptions() :array {
578
  try {
579
  return $this->loadOptionsValuesFromStorage();
@@ -590,22 +584,14 @@ class Options {
590
  return $this->aRawOptionsConfigData;
591
  }
592
 
593
- /**
594
- * Return the section of the Raw config that is the "options" key only.
595
- * @return array
596
- */
597
- protected function getRawData_AllOptions() {
598
- $aRaw = $this->getRawData_FullFeatureConfig();
599
- return ( isset( $aRaw[ 'options' ] ) && is_array( $aRaw[ 'options' ] ) ) ? $aRaw[ 'options' ] : [];
600
  }
601
 
602
- /**
603
- * Return the section of the Raw config that is the "options" key only.
604
- * @return array
605
- */
606
- protected function getRawData_OptionsSections() {
607
- $aAllRawOptions = $this->getRawData_FullFeatureConfig();
608
- return isset( $aAllRawOptions[ 'sections' ] ) ? $aAllRawOptions[ 'sections' ] : [];
609
  }
610
 
611
  protected function getRawData_Requirements() :array {
@@ -613,27 +599,18 @@ class Options {
613
  return $raw[ 'requirements' ] ?? [];
614
  }
615
 
616
- /**
617
- * Return the section of the Raw config that is the "options" key only.
618
- * @return array
619
- */
620
- protected function getRawData_MenuItems() {
621
- $aAllRawOptions = $this->getRawData_FullFeatureConfig();
622
- return isset( $aAllRawOptions[ 'menu_items' ] ) ? $aAllRawOptions[ 'menu_items' ] : [];
623
  }
624
 
625
- /**
626
- * Return the section of the Raw config that is the "options" key only.
627
- * @param string $key
628
- * @return array
629
- */
630
- public function getRawData_SingleOption( $key ) {
631
- foreach ( $this->getRawData_AllOptions() as $aOption ) {
632
- if ( isset( $aOption[ 'key' ] ) && ( $key == $aOption[ 'key' ] ) ) {
633
- return $aOption;
634
  }
635
  }
636
- return null;
637
  }
638
 
639
  public function getRebuildFromFile() :bool {
@@ -644,11 +621,11 @@ class Options {
644
  * @param string $key
645
  * @return string
646
  */
647
- public function getSelectOptionValueText( $key ) {
648
  $sText = '';
649
- foreach ( $this->getOptDefinition( $key )[ 'value_options' ] as $aOpt ) {
650
- if ( $aOpt[ 'value_key' ] == $this->getOpt( $key ) ) {
651
- $sText = $aOpt[ 'text' ];
652
  break;
653
  }
654
  }
@@ -678,16 +655,20 @@ class Options {
678
  return (bool)$this->getFeatureProperty( 'run_if_verified_bot' );
679
  }
680
 
 
 
 
 
681
  public function isOptChanged( string $key ) :bool {
682
  return is_array( $this->aOld ) && isset( $this->aOld[ $key ] );
683
  }
684
 
685
- public function isOptPremium( string $optKey ) :bool {
686
- return (bool)$this->getOptProperty( $optKey, 'premium' );
687
  }
688
 
689
- public function resetOptToDefault( string $optKey ) :self {
690
- return $this->setOpt( $optKey, $this->getOptDefault( $optKey ) );
691
  }
692
 
693
  /**
364
  return $aKeys;
365
  }
366
 
367
+ public function getOptionsForPluginUse() :array {
 
 
 
368
 
369
  $aOptionsData = [];
370
 
559
  }
560
 
561
  /**
562
+ * @param string $key
563
+ * @param string $prop
564
  * @return mixed|null
565
  */
566
+ public function getOptProperty( string $key, string $prop ) {
567
+ $opt = $this->getRawData_SingleOption( $key );
568
+ return $opt[ $prop ] ?? null;
569
  }
570
 
 
 
 
571
  public function getStoredOptions() :array {
572
  try {
573
  return $this->loadOptionsValuesFromStorage();
584
  return $this->aRawOptionsConfigData;
585
  }
586
 
587
+ protected function getRawData_AllOptions() :array {
588
+ $raw = $this->getRawData_FullFeatureConfig();
589
+ return $raw[ 'options' ] ?? [];
 
 
 
 
590
  }
591
 
592
+ protected function getRawData_OptionsSections() :array {
593
+ $raw = $this->getRawData_FullFeatureConfig();
594
+ return $raw[ 'sections' ] ?? [];
 
 
 
 
595
  }
596
 
597
  protected function getRawData_Requirements() :array {
599
  return $raw[ 'requirements' ] ?? [];
600
  }
601
 
602
+ protected function getRawData_MenuItems() :array {
603
+ $raw = $this->getRawData_FullFeatureConfig();
604
+ return $raw[ 'menu_items' ] ?? [];
 
 
 
 
605
  }
606
 
607
+ public function getRawData_SingleOption( string $key ) :array {
608
+ foreach ( $this->getRawData_AllOptions() as $opt ) {
609
+ if ( isset( $opt[ 'key' ] ) && ( $key == $opt[ 'key' ] ) ) {
610
+ return $opt;
 
 
 
 
 
611
  }
612
  }
613
+ return [];
614
  }
615
 
616
  public function getRebuildFromFile() :bool {
621
  * @param string $key
622
  * @return string
623
  */
624
+ public function getSelectOptionValueText( string $key ) {
625
  $sText = '';
626
+ foreach ( $this->getOptDefinition( $key )[ 'value_options' ] as $opt ) {
627
+ if ( $opt[ 'value_key' ] == $this->getOpt( $key ) ) {
628
+ $sText = $opt[ 'text' ];
629
  break;
630
  }
631
  }
655
  return (bool)$this->getFeatureProperty( 'run_if_verified_bot' );
656
  }
657
 
658
+ public function isOptAdvanced( string $key ) :bool {
659
+ return (bool)$this->getOptProperty( $key, 'advanced' );
660
+ }
661
+
662
  public function isOptChanged( string $key ) :bool {
663
  return is_array( $this->aOld ) && isset( $this->aOld[ $key ] );
664
  }
665
 
666
+ public function isOptPremium( string $key ) :bool {
667
+ return (bool)$this->getOptProperty( $key, 'premium' );
668
  }
669
 
670
+ public function resetOptToDefault( string $key ) :self {
671
+ return $this->setOpt( $key, $this->getOptDefault( $key ) );
672
  }
673
 
674
  /**
src/lib/src/Modules/Base/Processor.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
+
5
+ use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
+ use FernleafSystems\Wordpress\Plugin\Shield;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules;
8
+
9
+ abstract class Processor {
10
+
11
+ use Modules\ModConsumer;
12
+ use Shield\Crons\PluginCronsConsumer;
13
+ use OneTimeExecute;
14
+
15
+ /**
16
+ * @param ModCon $mod
17
+ */
18
+ public function __construct( $mod ) {
19
+ $this->setMod( $mod );
20
+ add_action( 'init', [ $this, 'onWpInit' ], 9 );
21
+ add_action( 'wp_loaded', [ $this, 'onWpLoaded' ] );
22
+ add_action( $mod->prefix( 'plugin_shutdown' ), [ $this, 'onModuleShutdown' ] );
23
+ $this->setupCronHooks();
24
+ }
25
+
26
+ public function onWpInit() {
27
+ }
28
+
29
+ public function onWpLoaded() {
30
+ }
31
+
32
+ public function onModuleShutdown() {
33
+ }
34
+
35
+ /**
36
+ * @deprecated 10.1
37
+ */
38
+ public function deactivatePlugin() {
39
+ }
40
+
41
+ /**
42
+ * @var BaseProcessor[]
43
+ * @deprecated 10.1
44
+ */
45
+ protected $aSubPros;
46
+
47
+ /**
48
+ * @deprecated 10.1
49
+ */
50
+ public function onWpEnqueueJs() {
51
+ }
52
+
53
+ /**
54
+ * @return Modules\Email\Processor
55
+ * @deprecated 10.1
56
+ */
57
+ public function getEmailProcessor() {
58
+ return $this->getMod()->getEmailProcessor();
59
+ }
60
+ }
src/lib/src/Modules/Base/Reporting.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Lib\Reports;
7
+
8
+ abstract class Reporting {
9
+
10
+ use ModConsumer;
11
+
12
+ /**
13
+ * @return Reports\BaseReporter[]
14
+ */
15
+ public function getAlertReporters() :array {
16
+ return $this->assignMod( $this->enumAlertReporters() );
17
+ }
18
+
19
+ /**
20
+ * @return Reports\BaseReporter[]
21
+ */
22
+ public function getInfoReporters() :array {
23
+ return $this->assignMod( $this->enumInfoReporters() );
24
+ }
25
+
26
+ /**
27
+ * @return Reports\BaseReporter[]
28
+ */
29
+ protected function enumAlertReporters() :array {
30
+ return [];
31
+ }
32
+
33
+ /**
34
+ * @return Reports\BaseReporter[]
35
+ */
36
+ protected function enumInfoReporters() :array {
37
+ return [];
38
+ }
39
+
40
+ /**
41
+ * @param Reports\BaseReporter[] $aReporters
42
+ * @return array
43
+ */
44
+ protected function assignMod( array $aReporters ) :array {
45
+ return array_map( function ( $reporter ) {
46
+ return $reporter->setMod( $this->getMod() );
47
+ }, $aReporters );
48
+ }
49
+ }
src/lib/src/Modules/Base/ShieldOptions.php CHANGED
@@ -4,6 +4,11 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
 
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
 
 
 
 
 
7
  class ShieldOptions extends Options {
8
 
9
  public function getInstallationDays() :int {
4
 
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
7
+ /**
8
+ * Class ShieldOptions
9
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Base
10
+ * @deprecated 10.1
11
+ */
12
  class ShieldOptions extends Options {
13
 
14
  public function getInstallationDays() :int {
src/lib/src/Modules/Base/ShieldUI.php CHANGED
@@ -5,12 +5,15 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
 
 
 
 
 
8
  class ShieldUI extends UI {
9
 
10
- /**
11
- * @return array
12
- */
13
- public function getBaseDisplayData() {
14
  /** @var \ICWP_WPSF_FeatureHandler_BaseWpsf $mod */
15
  $mod = $this->getMod();
16
 
@@ -40,11 +43,10 @@ class ShieldUI extends UI {
40
  'sec_admin_login' => $mod->getSecAdminLoginAjaxData(),
41
  ],
42
  'flags' => [
43
- 'show_promo' => !$this->getCon()->isPremiumActive(),
44
  'has_session' => $mod->hasSession()
45
  ],
46
  'hrefs' => [
47
- 'aar_forget_key' => $mod->isWlEnabled() ?
48
  $this->getCon()->getLabels()[ 'AuthorURI' ] : 'https://shsec.io/gc'
49
  ],
50
  'classes' => [
@@ -55,7 +57,14 @@ class ShieldUI extends UI {
55
  Services::Request()->query( 'inav', '' )
56
  ] ) )
57
  ],
 
 
 
58
  ]
59
  );
60
  }
 
 
 
 
61
  }
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
+ /**
9
+ * Class ShieldUI
10
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Base
11
+ * @deprecated 10.1
12
+ */
13
  class ShieldUI extends UI {
14
 
15
+ public function getBaseDisplayData() :array {
16
+ $con = $this->getCon();
 
 
17
  /** @var \ICWP_WPSF_FeatureHandler_BaseWpsf $mod */
18
  $mod = $this->getMod();
19
 
43
  'sec_admin_login' => $mod->getSecAdminLoginAjaxData(),
44
  ],
45
  'flags' => [
 
46
  'has_session' => $mod->hasSession()
47
  ],
48
  'hrefs' => [
49
+ 'aar_forget_key' => $con->getModule_SecAdmin()->isWlEnabled() ?
50
  $this->getCon()->getLabels()[ 'AuthorURI' ] : 'https://shsec.io/gc'
51
  ],
52
  'classes' => [
57
  Services::Request()->query( 'inav', '' )
58
  ] ) )
59
  ],
60
+ 'vars' => [
61
+ 'related_hrefs' => $this->getSettingsRelatedLinks()
62
+ ]
63
  ]
64
  );
65
  }
66
+
67
+ protected function getSettingsRelatedLinks() :array {
68
+ return [];
69
+ }
70
  }
src/lib/src/Modules/Base/Strings.php CHANGED
@@ -28,10 +28,13 @@ class Strings {
28
  __( 'Better Bot Detection', 'wp-simple-firewall' ),
29
  __( 'Password Policies', 'wp-simple-firewall' ),
30
  __( 'WooCommerce Support', 'wp-simple-firewall' ),
 
31
  ];
32
  $aProFeaturesDisplay = array_intersect_key( $aProFeatures, array_flip( array_rand( $aProFeatures, 6 ) ) );
33
  $aProFeaturesDisplay[] = __( 'and much more!' );
34
 
 
 
35
  return Services::DataManipulation()->mergeArraysRecursive(
36
  [
37
  'see_help_video' => __( 'Watch Help Video' ),
@@ -53,7 +56,7 @@ class Strings {
53
  'logged_in' => __( 'Logged-In', 'wp-simple-firewall' ),
54
  'username' => __( 'Username' ),
55
  'blog' => __( 'Blog', 'wp-simple-firewall' ),
56
- 'save_all_settings' => sprintf( __( 'Save %s Settings', 'wp-simple-firewall' ), $con->getHumanName() ),
57
  'plugin_name' => $con->getHumanName(),
58
  'options_title' => __( 'Options', 'wp-simple-firewall' ),
59
  'options_summary' => __( 'Configure Module', 'wp-simple-firewall' ),
@@ -66,6 +69,8 @@ class Strings {
66
  'select' => __( 'Select' ),
67
  'filters_clear' => __( 'Clear Filters', 'wp-simple-firewall' ),
68
  'filters_apply' => __( 'Apply Filters', 'wp-simple-firewall' ),
 
 
69
  'jump_to_option' => __( 'Find Plugin Option', 'wp-simple-firewall' ),
70
  'type_below_search' => __( 'Type below to search all plugin options', 'wp-simple-firewall' ),
71
  'pro_only_option' => __( 'Pro Only', 'wp-simple-firewall' ),
@@ -74,6 +79,17 @@ class Strings {
74
  'go_pro_option' => sprintf( '<a href="%s" target="_blank">%s</a>',
75
  'https://shsec.io/shieldgoprofeature', __( 'Please upgrade to Pro to control this option', 'wp-simple-firewall' ) ),
76
 
 
 
 
 
 
 
 
 
 
 
 
77
  'description' => __( 'Description', 'wp-simple-firewall' ),
78
  'loading' => __( 'Loading', 'wp-simple-firewall' ),
79
  'aar_title' => __( 'Plugin Access Restricted', 'wp-simple-firewall' ),
@@ -99,9 +115,19 @@ class Strings {
99
  'pro_features' => __( 'Pro features include', 'wp-simple-firewall' ),
100
  'join_thousands_H' => __( "Join The 1,000s Who've Already Upgraded Their WordPress Security To Better Protect Their Sites.", 'wp-simple-firewall' ),
101
  'join_thousands_P' => implode( ', ', $aProFeaturesDisplay ),
102
- 'get_pro_protection' => __( 'Upgrade To Pro Protection', 'wp-simple-firewall' ),
103
-
104
- 'page_title' => 'Twig Page',
 
 
 
 
 
 
 
 
 
 
105
 
106
  'wphashes_token' => 'WPHashes.com API Token',
107
  'is_opt_importexport' => __( 'Is this option included with import/export?', 'wp-simple-firewall' ),
28
  __( 'Better Bot Detection', 'wp-simple-firewall' ),
29
  __( 'Password Policies', 'wp-simple-firewall' ),
30
  __( 'WooCommerce Support', 'wp-simple-firewall' ),
31
+ __( 'MainWP Integration', 'wp-simple-firewall' ),
32
  ];
33
  $aProFeaturesDisplay = array_intersect_key( $aProFeatures, array_flip( array_rand( $aProFeatures, 6 ) ) );
34
  $aProFeaturesDisplay[] = __( 'and much more!' );
35
 
36
+ $bIsAdvanced = $this->getCon()->getModule_Plugin()->isShowAdvanced();
37
+
38
  return Services::DataManipulation()->mergeArraysRecursive(
39
  [
40
  'see_help_video' => __( 'Watch Help Video' ),
56
  'logged_in' => __( 'Logged-In', 'wp-simple-firewall' ),
57
  'username' => __( 'Username' ),
58
  'blog' => __( 'Blog', 'wp-simple-firewall' ),
59
+ 'save_all_settings' => __( 'Save Settings', 'wp-simple-firewall' ),
60
  'plugin_name' => $con->getHumanName(),
61
  'options_title' => __( 'Options', 'wp-simple-firewall' ),
62
  'options_summary' => __( 'Configure Module', 'wp-simple-firewall' ),
69
  'select' => __( 'Select' ),
70
  'filters_clear' => __( 'Clear Filters', 'wp-simple-firewall' ),
71
  'filters_apply' => __( 'Apply Filters', 'wp-simple-firewall' ),
72
+ 'jump_to_module' => __( 'Jump To Module Settings', 'wp-simple-firewall' ),
73
+ 'this_page' => __( 'This Page', 'wp-simple-firewall' ),
74
  'jump_to_option' => __( 'Find Plugin Option', 'wp-simple-firewall' ),
75
  'type_below_search' => __( 'Type below to search all plugin options', 'wp-simple-firewall' ),
76
  'pro_only_option' => __( 'Pro Only', 'wp-simple-firewall' ),
79
  'go_pro_option' => sprintf( '<a href="%s" target="_blank">%s</a>',
80
  'https://shsec.io/shieldgoprofeature', __( 'Please upgrade to Pro to control this option', 'wp-simple-firewall' ) ),
81
 
82
+ 'mode' => __( 'Mode', 'wp-simple-firewall' ),
83
+ 'mode_simple' => __( 'Simple', 'wp-simple-firewall' ),
84
+ 'mode_advanced' => __( '', 'wp-simple-firewall' ),
85
+ 'mode_switchto' => sprintf( '%s: %s', __( 'Switch To', 'wp-simple-firewall' ),
86
+ $bIsAdvanced ? __( 'Simple', 'wp-simple-firewall' ) : __( 'Advanced', 'wp-simple-firewall' ) ),
87
+ 'mode_switchfrom' => sprintf( '%s: %s', __( 'Mode', 'wp-simple-firewall' ),
88
+ $bIsAdvanced ? __( 'Advanced', 'wp-simple-firewall' ) : __( 'Simple', 'wp-simple-firewall' ) ),
89
+
90
+ 'dashboard' => __( 'Dashboard', 'wp-simple-firewall' ),
91
+ 'dashboard_shield' => sprintf( __( '%s Dashboard', 'wp-simple-firewall' ), $con->getHumanName() ),
92
+
93
  'description' => __( 'Description', 'wp-simple-firewall' ),
94
  'loading' => __( 'Loading', 'wp-simple-firewall' ),
95
  'aar_title' => __( 'Plugin Access Restricted', 'wp-simple-firewall' ),
115
  'pro_features' => __( 'Pro features include', 'wp-simple-firewall' ),
116
  'join_thousands_H' => __( "Join The 1,000s Who've Already Upgraded Their WordPress Security To Better Protect Their Sites.", 'wp-simple-firewall' ),
117
  'join_thousands_P' => implode( ', ', $aProFeaturesDisplay ),
118
+ 'get_pro_protection' => __( 'Get Pro Protection', 'wp-simple-firewall' ),
119
+
120
+ 'recommendation' => ucfirst( __( 'recommendation', 'wp-simple-firewall' ) ),
121
+ 'suggestion' => ucfirst( __( 'suggestion', 'wp-simple-firewall' ) ),
122
+ 'box_welcome_title' => sprintf( __( 'Welcome To %s Security Insights Dashboard', 'wp-simple-firewall' ), $con->getHumanName() ),
123
+ 'options' => __( 'Options', 'wp-simple-firewall' ),
124
+ 'not_available' => __( 'Sorry, this feature is included with Pro subscriptions.', 'wp-simple-firewall' ),
125
+ 'not_enabled' => __( "This feature isn't currently enabled.", 'wp-simple-firewall' ),
126
+ 'please_upgrade' => __( 'You can get this feature (along with loads more) by going Pro.', 'wp-simple-firewall' ),
127
+ 'please_enable' => __( 'Please turn on this feature in the options.', 'wp-simple-firewall' ),
128
+ 'no_security_notices' => __( 'There are no important security notices at this time.', 'wp-simple-firewall' ),
129
+ 'this_is_wonderful' => __( 'This is wonderful!', 'wp-simple-firewall' ),
130
+ 'yyyymmdd' => __( 'YYYY-MM-DD', 'wp-simple-firewall' ),
131
 
132
  'wphashes_token' => 'WPHashes.com API Token',
133
  'is_opt_importexport' => __( 'Is this option included with import/export?', 'wp-simple-firewall' ),
src/lib/src/Modules/Base/UI.php CHANGED
@@ -17,8 +17,10 @@ class UI {
17
  * It has to handle the conversion of stored values to data to be displayed to the user.
18
  */
19
  public function buildOptions() {
 
20
 
21
- $bPremiumEnabled = $this->getCon()->isPremiumExtensionsEnabled();
 
22
 
23
  $opts = $this->getOptions();
24
  $aOptions = $opts->getOptionsForPluginUse();
@@ -30,7 +32,8 @@ class UI {
30
  foreach ( $aSection[ 'options' ] as $nKey => $aOption ) {
31
  $aOption[ 'is_value_default' ] = ( $aOption[ 'value' ] === $aOption[ 'default' ] );
32
  $bIsPrem = isset( $aOption[ 'premium' ] ) && $aOption[ 'premium' ];
33
- if ( !$bIsPrem || $bPremiumEnabled ) {
 
34
  $aSection[ 'options' ][ $nKey ] = $this->buildOptionForUi( $aOption );
35
  }
36
  else {
@@ -50,20 +53,22 @@ class UI {
50
  ->getSectionStrings( $aSection[ 'slug' ] )
51
  );
52
  }
53
- catch ( \Exception $oE ) {
54
  }
55
  $aOptions[ $nSectionKey ] = $aSection;
56
  }
57
 
58
- $aWarnings = [];
59
- if ( !$opts->isSectionReqsMet( $aSection[ 'slug' ] ) ) {
60
- $aWarnings[] = __( 'Unfortunately your WordPress and/or PHP versions are too old to support this feature.', 'wp-simple-firewall' );
 
 
 
 
 
 
 
61
  }
62
- $aOptions[ $nSectionKey ][ 'warnings' ] = array_merge(
63
- $aWarnings,
64
- $this->getSectionWarnings( $aSection[ 'slug' ] )
65
- );
66
- $aOptions[ $nSectionKey ][ 'notices' ] = $this->getSectionNotices( $aSection[ 'slug' ] );
67
  }
68
  }
69
 
@@ -143,10 +148,25 @@ class UI {
143
  return $aOptParams;
144
  }
145
 
146
- /**
147
- * @return array
148
- */
149
- public function getBaseDisplayData() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  $mod = $this->getMod();
151
  $con = $this->getCon();
152
 
@@ -187,7 +207,9 @@ class UI {
187
  'hidden_options' => $this->getOptions()->getHiddenOptions()
188
  ],
189
  'ajax' => [
190
- 'mod_options' => $mod->getAjaxActionData( 'mod_options' ),
 
 
191
  ],
192
  'vendors' => [
193
  'widget_freshdesk' => '3000000081' /* TODO: plugin spec config */
17
  * It has to handle the conversion of stored values to data to be displayed to the user.
18
  */
19
  public function buildOptions() {
20
+ $con = $this->getCon();
21
 
22
+ $bPremiumEnabled = $con->isPremiumExtensionsEnabled();
23
+ $bShowAdvanced = $con->getModule_Plugin()->isShowAdvanced();
24
 
25
  $opts = $this->getOptions();
26
  $aOptions = $opts->getOptionsForPluginUse();
32
  foreach ( $aSection[ 'options' ] as $nKey => $aOption ) {
33
  $aOption[ 'is_value_default' ] = ( $aOption[ 'value' ] === $aOption[ 'default' ] );
34
  $bIsPrem = isset( $aOption[ 'premium' ] ) && $aOption[ 'premium' ];
35
+ $bIsAdv = isset( $aOption[ 'advanced' ] ) && $aOption[ 'advanced' ];
36
+ if ( ( !$bIsPrem || $bPremiumEnabled ) && ( !$bIsAdv || $bShowAdvanced ) ) {
37
  $aSection[ 'options' ][ $nKey ] = $this->buildOptionForUi( $aOption );
38
  }
39
  else {
53
  ->getSectionStrings( $aSection[ 'slug' ] )
54
  );
55
  }
56
+ catch ( \Exception $e ) {
57
  }
58
  $aOptions[ $nSectionKey ] = $aSection;
59
  }
60
 
61
+ if ( isset( $aOptions[ $nSectionKey ] ) ) {
62
+ $aWarnings = [];
63
+ if ( !$opts->isSectionReqsMet( $aSection[ 'slug' ] ) ) {
64
+ $aWarnings[] = __( 'Unfortunately your WordPress and/or PHP versions are too old to support this feature.', 'wp-simple-firewall' );
65
+ }
66
+ $aOptions[ $nSectionKey ][ 'warnings' ] = array_merge(
67
+ $aWarnings,
68
+ $this->getSectionWarnings( $aSection[ 'slug' ] )
69
+ );
70
+ $aOptions[ $nSectionKey ][ 'notices' ] = $this->getSectionNotices( $aSection[ 'slug' ] );
71
  }
 
 
 
 
 
72
  }
73
  }
74
 
148
  return $aOptParams;
149
  }
150
 
151
+ public function buildSelectData_ModuleSettings() :array {
152
+ return $this->getMod()->getModulesSummaryData();
153
+ }
154
+
155
+ public function buildSelectData_OptionsSearch() :array {
156
+ $modsToSearch = array_filter(
157
+ $this->getMod()->getModulesSummaryData(),
158
+ function ( $modSummary ) {
159
+ return !empty( $modSummary[ 'show_mod_opts' ] );
160
+ }
161
+ );
162
+ $searchSelect = [];
163
+ foreach ( $modsToSearch as $slug => $summary ) {
164
+ $searchSelect[ $summary[ 'name' ] ] = $summary[ 'options' ];
165
+ }
166
+ return $searchSelect;
167
+ }
168
+
169
+ public function getBaseDisplayData() :array {
170
  $mod = $this->getMod();
171
  $con = $this->getCon();
172
 
207
  'hidden_options' => $this->getOptions()->getHiddenOptions()
208
  ],
209
  'ajax' => [
210
+ 'mod_options' => $mod->getAjaxActionData( 'mod_options', true ),
211
+ 'mod_opts_form_render' => $mod->getAjaxActionData( 'mod_opts_form_render', true ),
212
+ // 'mod_options' => $mod->getAjaxActionData( 'mod_options' ),
213
  ],
214
  'vendors' => [
215
  'widget_freshdesk' => '3000000081' /* TODO: plugin spec config */
src/lib/src/Modules/Base/Upgrade.php CHANGED
@@ -27,13 +27,12 @@ class Upgrade {
27
  * version is less than the upgrade version, run the upgrade code.
28
  */
29
  protected function upgradeModule() {
30
- $oCon = $this->getCon();
31
- $sPreviousVersion = $oCon->getPreviousVersion();
32
- foreach ( $oCon->getPluginSpec()[ 'version_upgrades' ] as $sVersion ) {
33
- $sMethod = 'upgrade_'.str_replace( '.', '', $sVersion );
34
- if ( version_compare( $sPreviousVersion, $sVersion, '<' )
35
- && method_exists( $this, $sMethod ) ) {
36
- $this->{$sMethod}();
37
  }
38
  }
39
  }
27
  * version is less than the upgrade version, run the upgrade code.
28
  */
29
  protected function upgradeModule() {
30
+ $con = $this->getCon();
31
+ $previous = $con->getPreviousVersion();
32
+ foreach ( $con->getPluginSpec()[ 'version_upgrades' ] as $version ) {
33
+ $upgradeMethod = 'upgrade_'.str_replace( '.', '', $version );
34
+ if ( version_compare( $previous, $version, '<' ) && method_exists( $this, $upgradeMethod ) ) {
35
+ $this->{$upgradeMethod}();
 
36
  }
37
  }
38
  }
src/lib/src/Modules/Base/WpCli.php CHANGED
@@ -16,14 +16,14 @@ class WpCli {
16
  $oHandler->setMod( $this->getMod() )->execute();
17
  }
18
  }
19
- catch ( \Exception $oE ) {
20
  }
21
  }
22
 
23
  /**
24
  * @return WpCli[]
25
  */
26
- protected function getAllCmdHandlers() {
27
  return array_merge(
28
  [ new ModuleStandard() ],
29
  $this->getCmdHandlers()
@@ -33,7 +33,7 @@ class WpCli {
33
  /**
34
  * @return WpCli[]
35
  */
36
- protected function getCmdHandlers() {
37
  return [];
38
  }
39
  }
16
  $oHandler->setMod( $this->getMod() )->execute();
17
  }
18
  }
19
+ catch ( \Exception $e ) {
20
  }
21
  }
22
 
23
  /**
24
  * @return WpCli[]
25
  */
26
+ protected function getAllCmdHandlers() :array {
27
  return array_merge(
28
  [ new ModuleStandard() ],
29
  $this->getCmdHandlers()
33
  /**
34
  * @return WpCli[]
35
  */
36
+ protected function getCmdHandlers() :array {
37
  return [];
38
  }
39
  }
src/lib/src/Modules/Base/WpCli/BaseWpCliCmd.php CHANGED
@@ -21,7 +21,7 @@ abstract class BaseWpCliCmd {
21
  try {
22
  $this->addCmds();
23
  }
24
- catch ( \Exception $oE ) {
25
  }
26
  }
27
 
21
  try {
22
  $this->addCmds();
23
  }
24
+ catch ( \Exception $e ) {
25
  }
26
  }
27
 
src/lib/src/Modules/BaseShield/AjaxHandler.php ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
+
7
+ class AjaxHandler extends Base\AjaxHandler {
8
+
9
+ protected function processAjaxAction( string $action ) :array {
10
+ $response = [];
11
+ $mod = $this->getMod();
12
+
13
+ switch ( $action ) {
14
+
15
+ case 'mod_opts_form_render':
16
+ $response = $this->ajaxExec_ModOptionsFormRender();
17
+ break;
18
+
19
+ case 'mod_options':
20
+ $response = $this->ajaxExec_ModOptions();
21
+ break;
22
+
23
+ case 'wiz_process_step':
24
+ if ( $mod->hasWizard() ) {
25
+ $response = $mod->getWizardHandler()->ajaxExec_WizProcessStep();
26
+ }
27
+ break;
28
+
29
+ case 'wiz_render_step':
30
+ if ( $mod->hasWizard() ) {
31
+ $response = $mod->getWizardHandler()->ajaxExec_WizRenderStep();
32
+ }
33
+ break;
34
+
35
+ default:
36
+ $response = parent::processAjaxAction( $action );
37
+ }
38
+
39
+ return $response;
40
+ }
41
+
42
+ protected function ajaxExec_ModOptions() :array {
43
+
44
+ $name = $this->getCon()->getHumanName();
45
+
46
+ try {
47
+ $this->getMod()->saveOptionsSubmit();
48
+ $success = true;
49
+ $msg = sprintf( __( '%s Plugin options updated successfully.', 'wp-simple-firewall' ), $name );
50
+ }
51
+ catch ( \Exception $e ) {
52
+ $success = false;
53
+ $msg = sprintf( __( 'Failed to update %s plugin options.', 'wp-simple-firewall' ), $name )
54
+ .' '.$e->getMessage();
55
+ }
56
+
57
+ return [
58
+ 'success' => $success,
59
+ 'html' => '', //we reload the page
60
+ 'message' => $msg
61
+ ];
62
+ }
63
+
64
+ protected function ajaxExec_ModOptionsFormRender() :array {
65
+ return [
66
+ 'success' => true,
67
+ 'html' => $this->getMod()->renderOptionsForm(),
68
+ 'message' => 'loaded'
69
+ ];
70
+ }
71
+ }
src/lib/src/Modules/BaseShield/ModCon.php ADDED
@@ -0,0 +1,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
4
+
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
+
14
+ /**
15
+ * @var bool
16
+ */
17
+ protected static $bIsVerifiedBot;
18
+
19
+ /**
20
+ * @var bool
21
+ */
22
+ private static $bVisitorIsWhitelisted;
23
+
24
+ /**
25
+ * @return bool
26
+ * @deprecated 10.1
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\Modules\Sessions\Processor
36
+ * @deprecated 10.1
37
+ */
38
+ public function getSessionsProcessor() :Shield\Modules\Sessions\Processor {
39
+ return $this->getCon()
40
+ ->getModule_Sessions()
41
+ ->getProcessor();
42
+ }
43
+
44
+ /**
45
+ * @return Shield\Databases\Session\Handler
46
+ */
47
+ public function getDbHandler_Sessions() {
48
+ return $this->getCon()
49
+ ->getModule_Sessions()
50
+ ->getDbHandler_Sessions();
51
+ }
52
+
53
+ /**
54
+ * @return Shield\Databases\Session\EntryVO|null
55
+ */
56
+ public function getSession() {
57
+ return $this->getCon()
58
+ ->getModule_Sessions()
59
+ ->getSessionCon()
60
+ ->getCurrent();
61
+ }
62
+
63
+ /**
64
+ * @return bool
65
+ * @deprecated 10.1
66
+ */
67
+ public function hasSession() :bool {
68
+ return $this->getSession() instanceof Shield\Databases\Session\EntryVO;
69
+ }
70
+
71
+ /**
72
+ * @return bool
73
+ */
74
+ public function hasValidRequestIP() {
75
+ return Services::IP()->isValidIp( Services::IP()->getRequestIp() );
76
+ }
77
+
78
+ public function onWpInit() {
79
+ parent::onWpInit();
80
+ if ( $this->isThisModulePage() && !$this->isWizardPage() && ( $this->getSlug() != 'insights' ) ) {
81
+ $this->redirectToInsightsSubPage();
82
+ }
83
+ }
84
+
85
+ protected function redirectToInsightsSubPage() {
86
+ Services::Response()->redirect(
87
+ $this->getCon()->getModule_Insights()->getUrl_AdminPage(),
88
+ [
89
+ 'inav' => 'settings',
90
+ 'subnav' => $this->getSlug()
91
+ ],
92
+ true, false
93
+ );
94
+ }
95
+
96
+ public function getCaptchaCfg() :Plugin\Lib\Captcha\CaptchaConfigVO {
97
+ $plugMod = $this->getCon()->getModule_Plugin();
98
+ /** @var Shield\Modules\Plugin\Options $plugOpts */
99
+ $plugOpts = $plugMod->getOptions();
100
+ $cfg = ( new Plugin\Lib\Captcha\CaptchaConfigVO() )->applyFromArray( $plugOpts->getCaptchaConfig() );
101
+ $cfg->invisible = $cfg->theme === 'invisible';
102
+
103
+ if ( $cfg->provider === Plugin\Lib\Captcha\CaptchaConfigVO::PROV_GOOGLE_RECAP2 ) {
104
+ $cfg->url_api = 'https://www.google.com/recaptcha/api.js';
105
+ }
106
+ elseif ( $cfg->provider === Plugin\Lib\Captcha\CaptchaConfigVO::PROV_HCAPTCHA ) {
107
+ $cfg->url_api = 'https://hcaptcha.com/1/api.js';
108
+ }
109
+ else {
110
+ error_log( 'CAPTCHA Provider not supported: '.$cfg->provider );
111
+ }
112
+
113
+ $cfg->js_handle = $this->getCon()->prefix( $cfg->provider );
114
+
115
+ return $cfg;
116
+ }
117
+
118
+ public function getSecAdminLoginAjaxData() :array {
119
+ // We set a custom mod_slug so that this module handles the ajax request
120
+ $dat = $this->getAjaxActionData( 'sec_admin_login' );
121
+ $dat[ 'mod_slug' ] = $this->prefix( 'admin_access_restriction' );
122
+ return $dat;
123
+ }
124
+
125
+ protected function getSecAdminCheckAjaxData() :array {
126
+ // We set a custom mod_slug so that this module handles the ajax request
127
+ $dat = $this->getAjaxActionData( 'sec_admin_check' );
128
+ $dat[ 'mod_slug' ] = $this->prefix( 'admin_access_restriction' );
129
+ return $dat;
130
+ }
131
+
132
+ public function getPluginReportEmail() :string {
133
+ return $this->getCon()
134
+ ->getModule_Plugin()
135
+ ->getPluginReportEmail();
136
+ }
137
+
138
+ /**
139
+ * @uses echo()
140
+ */
141
+ public function displayModuleAdminPage() {
142
+ if ( $this->canDisplayOptionsForm() ) {
143
+ parent::displayModuleAdminPage();
144
+ }
145
+ else {
146
+ echo $this->renderRestrictedPage();
147
+ }
148
+ }
149
+
150
+ protected function renderRestrictedPage() :string {
151
+ /** @var Shield\Modules\SecurityAdmin\Options $secOpts */
152
+ $secOpts = $this->getCon()
153
+ ->getModule_SecAdmin()
154
+ ->getOptions();
155
+ $aData = Services::DataManipulation()
156
+ ->mergeArraysRecursive(
157
+ $this->getUIHandler()->getBaseDisplayData(),
158
+ [
159
+ 'ajax' => [
160
+ 'restricted_access' => $this->getAjaxActionData( 'restricted_access' ),
161
+ ],
162
+ 'strings' => [
163
+ 'force_remove_email' => __( "If you've forgotten your PIN, a link can be sent to the plugin administrator email address to remove this restriction.", 'wp-simple-firewall' ),
164
+ 'click_email' => __( "Click here to send the verification email.", 'wp-simple-firewall' ),
165
+ 'send_to_email' => sprintf( __( "Email will be sent to %s", 'wp-simple-firewall' ),
166
+ Utilities\Obfuscate::Email( $this->getPluginReportEmail() ) ),
167
+ 'no_email_override' => __( "The Security Administrator has restricted the use of the email override feature.", 'wp-simple-firewall' ),
168
+ ],
169
+ 'flags' => [
170
+ 'allow_email_override' => $secOpts->isEmailOverridePermitted()
171
+ ]
172
+ ]
173
+ );
174
+ return $this->renderTemplate( '/wpadmin_pages/security_admin/index.twig', $aData, true );
175
+ }
176
+
177
+ /**
178
+ * @return bool
179
+ */
180
+ public function getIfSupport3rdParty() {
181
+ return $this->isPremium();
182
+ }
183
+
184
+ /**
185
+ * @return bool
186
+ * @throws \Exception
187
+ */
188
+ protected function isReadyToExecute() :bool {
189
+ $opts = $this->getOptions();
190
+ return ( $opts->isModuleRunIfWhitelisted() || !$this->isVisitorWhitelisted() )
191
+ && ( $opts->isModuleRunIfVerifiedBot() || !$this->isVerifiedBot() )
192
+ && ( $opts->isModuleRunUnderWpCli() || !Services::WpGeneral()->isWpCli() )
193
+ && parent::isReadyToExecute();
194
+ }
195
+
196
+ public function isVisitorWhitelisted() :bool {
197
+ if ( !isset( self::$bVisitorIsWhitelisted ) ) {
198
+ self::$bVisitorIsWhitelisted =
199
+ ( new Shield\Modules\IPs\Lib\Ops\LookupIpOnList() )
200
+ ->setDbHandler( $this->getCon()->getModule_IPs()->getDbHandler_IPs() )
201
+ ->setIP( Services::IP()->getRequestIp() )
202
+ ->setListTypeWhite()
203
+ ->lookup()
204
+ instanceof Shield\Databases\IPs\EntryVO;
205
+ }
206
+ return self::$bVisitorIsWhitelisted;
207
+ }
208
+
209
+ public function isVerifiedBot() :bool {
210
+ if ( !isset( self::$bIsVerifiedBot ) ) {
211
+ $srvIP = Services::IP();
212
+ self::$bIsVerifiedBot = !$srvIP->isLoopback() &&
213
+ !in_array( $srvIP->getIpDetector()->getIPIdentity(), [
214
+ IpIdentify::UNKNOWN,
215
+ IpIdentify::THIS_SERVER,
216
+ IpIdentify::VISITOR,
217
+ ] );
218
+ }
219
+ return self::$bIsVerifiedBot;
220
+ }
221
+
222
+ public function isXmlrpcBypass() :bool {
223
+ return $this->getCon()
224
+ ->getModule_Plugin()
225
+ ->isXmlrpcBypass();
226
+ }
227
+
228
+ /**
229
+ * @param string[] $aArray
230
+ * @param string $sPregReplacePattern
231
+ * @return string[]
232
+ */
233
+ protected function cleanStringArray( $aArray, $sPregReplacePattern ) {
234
+ $aCleaned = [];
235
+ if ( !is_array( $aArray ) ) {
236
+ return $aCleaned;
237
+ }
238
+
239
+ foreach ( $aArray as $nKey => $sVal ) {
240
+ $sVal = preg_replace( $sPregReplacePattern, '', $sVal );
241
+ if ( !empty( $sVal ) ) {
242
+ $aCleaned[] = $sVal;
243
+ }
244
+ }
245
+ return array_unique( array_filter( $aCleaned ) );
246
+ }
247
+
248
+ protected function getNamespaceRoots() :array {
249
+ // Ensure order of namespaces is 'Module', 'BaseShield', then 'Base'
250
+ return [
251
+ $this->getNamespace(),
252
+ $this->getCon()->getModulesNamespace().'\\BaseShield',
253
+ $this->getBaseNamespace(),
254
+ ];
255
+ }
256
+ }
src/lib/src/Modules/BaseShield/Options.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class Options extends Base\Options {
9
+
10
+ public function getInstallationDays() :int {
11
+ $nTimeInstalled = $this->getCon()
12
+ ->getModule_Plugin()
13
+ ->getInstallDate();
14
+ if ( empty( $nTimeInstalled ) ) {
15
+ return 0;
16
+ }
17
+ return (int)round( ( Services::Request()->ts() - $nTimeInstalled )/DAY_IN_SECONDS );
18
+ }
19
+
20
+ public function isPremium() :bool {
21
+ return $this->getCon()->isPremiumActive();
22
+ }
23
+
24
+ public function isShowPromoAdminNotices() :bool {
25
+ return $this->getCon()
26
+ ->getModule_Plugin()
27
+ ->getOptions()
28
+ ->isOpt( 'enable_upgrade_admin_notice', 'Y' );
29
+ }
30
+ }
src/lib/src/Modules/BaseShield/{ShieldModCon.php → Processor.php} RENAMED
@@ -1,9 +1,9 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
 
7
- class ShieldModCon extends Base\BaseModCon {
8
 
9
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
 
7
+ class Processor extends Base\Processor {
8
 
9
  }
src/lib/src/Modules/BaseShield/ShieldProcessor.php CHANGED
@@ -5,24 +5,15 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
 
 
 
 
 
8
  class ShieldProcessor extends Base\BaseProcessor {
9
 
10
  const RECAPTCHA_JS_HANDLE = 'icwp-google-recaptcha';
11
 
12
- /**
13
- * @var bool
14
- */
15
- private static $bRecaptchaEnqueue = false;
16
-
17
- /**
18
- * Resets the object values to be re-used anew
19
- */
20
- public function init() {
21
- parent::init();
22
- $con = $this->getCon();
23
- add_filter( $con->prefix( 'collect_tracking_data' ), [ $this, 'tracking_DataCollect' ] );
24
- }
25
-
26
  /**
27
  * @param \WP_User $oUser
28
  * @return bool
@@ -39,25 +30,4 @@ class ShieldProcessor extends Base\BaseProcessor {
39
 
40
  return $bIsSubject;
41
  }
42
-
43
- /**
44
- * Filter used to collect plugin data for tracking. Fired from the plugin processor only if the option is enabled
45
- * - it is not enabled by default.
46
- * Note that in this case we "mask" options that have been identified as "sensitive" - i.e. could contain
47
- * identifiable data.
48
- *
49
- * @param $aData
50
- * @return array
51
- */
52
- public function tracking_DataCollect( $aData ) {
53
- if ( !is_array( $aData ) ) {
54
- $aData = [];
55
- }
56
- $oMod = $this->getMod();
57
- $aOptions = $oMod->collectOptionsForTracking();
58
- if ( !empty( $aOptions ) ) {
59
- $aData[ $oMod->getSlug() ] = [ 'options' => $oMod->collectOptionsForTracking() ];
60
- }
61
- return $aData;
62
- }
63
  }
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
+ /**
9
+ * Class ShieldProcessor
10
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield
11
+ * @deprecated 10.1
12
+ */
13
  class ShieldProcessor extends Base\BaseProcessor {
14
 
15
  const RECAPTCHA_JS_HANDLE = 'icwp-google-recaptcha';
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  /**
18
  * @param \WP_User $oUser
19
  * @return bool
30
 
31
  return $bIsSubject;
32
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  }
src/lib/src/Modules/BaseShield/UI.php ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
+ use FernleafSystems\Wordpress\Plugin\Shield;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+
9
+ class UI extends Base\UI {
10
+
11
+ public function getBaseDisplayData() :array {
12
+ $con = $this->getCon();
13
+ /** @var \ICWP_WPSF_FeatureHandler_BaseWpsf $mod */
14
+ $mod = $this->getMod();
15
+
16
+ return Services::DataManipulation()->mergeArraysRecursive(
17
+ parent::getBaseDisplayData(),
18
+ [
19
+ 'head' => [
20
+ 'html' => [
21
+ 'lang' => Services::WpGeneral()->getLocale( '-' ),
22
+ 'dir' => is_rtl() ? 'rtl' : 'ltr',
23
+ ],
24
+ 'meta' => [
25
+ [
26
+ 'type' => 'http-equiv',
27
+ 'type_type' => 'Cache-Control',
28
+ 'content' => 'no-store, no-cache',
29
+ ],
30
+ [
31
+ 'type' => 'http-equiv',
32
+ 'type_type' => 'Expires',
33
+ 'content' => '0',
34
+ ],
35
+ ],
36
+ 'scripts' => []
37
+ ],
38
+ 'ajax' => [
39
+ 'sec_admin_login' => $mod->getSecAdminLoginAjaxData(),
40
+ ],
41
+ 'flags' => [
42
+ 'has_session' => $mod->hasSession()
43
+ ],
44
+ 'hrefs' => [
45
+ 'aar_forget_key' => $con->getModule_SecAdmin()->isWlEnabled() ?
46
+ $this->getCon()->getLabels()[ 'AuthorURI' ] : 'https://shsec.io/gc'
47
+ ],
48
+ 'classes' => [
49
+ 'top_container' => implode( ' ', array_filter( [
50
+ 'odp-outercontainer',
51
+ $this->getCon()->isPremiumActive() ? 'is-pro' : 'is-not-pro',
52
+ $mod->getModSlug(),
53
+ Services::Request()->query( 'inav', '' )
54
+ ] ) )
55
+ ],
56
+ 'vars' => [
57
+ 'related_hrefs' => $this->getSettingsRelatedLinks()
58
+ ]
59
+ ]
60
+ );
61
+ }
62
+
63
+ protected function getSettingsRelatedLinks() :array {
64
+ return [];
65
+ }
66
+ }
src/lib/src/Modules/CommentsFilter/AdminNotices.php CHANGED
@@ -3,35 +3,32 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
 
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class AdminNotices extends Shield\Modules\Base\AdminNotices {
9
 
10
  /**
11
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
12
- * @throws \Exception
13
  */
14
- protected function processNotice( $oNotice ) {
15
 
16
- switch ( $oNotice->id ) {
17
 
18
  case 'akismet-running':
19
- $this->buildNotice_AkismetRunning( $oNotice );
20
  break;
21
 
22
  default:
23
- parent::processNotice( $oNotice );
24
  break;
25
  }
26
  }
27
 
28
- /**
29
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
30
- */
31
- private function buildNotice_AkismetRunning( $oNotice ) {
32
  $oWpPlugins = Services::WpPlugins();
33
 
34
- $oNotice->render_data = [
35
  'notice_attributes' => [],
36
  'strings' => [
37
  'title' => ucwords( __( 'Akismet Anti-SPAM plugin is also running', 'wp-simple-firewall' ) ),
@@ -46,28 +43,28 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
46
  }
47
 
48
  /**
49
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
50
  * @return bool
51
  */
52
- protected function isDisplayNeeded( $oNotice ) {
53
  /** @var Options $opts */
54
  $opts = $this->getOptions();
55
 
56
- switch ( $oNotice->id ) {
57
 
58
  case 'akismet-running':
59
  $oWpPlugins = Services::WpPlugins();
60
  $sPluginFile = $oWpPlugins->findPluginFileFromDirName( 'akismet' );
61
- $bNeeded = $this->getMod()->isModuleEnabled()
62
- && !empty( $sPluginFile )
63
- && $oWpPlugins->isActive( $sPluginFile )
64
- && $opts->isEnabledHumanCheck();
65
  break;
66
 
67
  default:
68
- $bNeeded = parent::isDisplayNeeded( $oNotice );
69
  break;
70
  }
71
- return $bNeeded;
72
  }
73
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\AdminNotices\NoticeVO;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class AdminNotices extends Shield\Modules\Base\AdminNotices {
10
 
11
  /**
12
+ * @inheritDoc
 
13
  */
14
+ protected function processNotice( NoticeVO $notice ) {
15
 
16
+ switch ( $notice->id ) {
17
 
18
  case 'akismet-running':
19
+ $this->buildNotice_AkismetRunning( $notice );
20
  break;
21
 
22
  default:
23
+ parent::processNotice( $notice );
24
  break;
25
  }
26
  }
27
 
28
+ private function buildNotice_AkismetRunning( NoticeVO $notice ) {
 
 
 
29
  $oWpPlugins = Services::WpPlugins();
30
 
31
+ $notice->render_data = [
32
  'notice_attributes' => [],
33
  'strings' => [
34
  'title' => ucwords( __( 'Akismet Anti-SPAM plugin is also running', 'wp-simple-firewall' ) ),
43
  }
44
 
45
  /**
46
+ * @param Shield\Utilities\AdminNotices\NoticeVO $notice
47
  * @return bool
48
  */
49
+ protected function isDisplayNeeded( Shield\Utilities\AdminNotices\NoticeVO $notice ) :bool {
50
  /** @var Options $opts */
51
  $opts = $this->getOptions();
52
 
53
+ switch ( $notice->id ) {
54
 
55
  case 'akismet-running':
56
  $oWpPlugins = Services::WpPlugins();
57
  $sPluginFile = $oWpPlugins->findPluginFileFromDirName( 'akismet' );
58
+ $needed = $this->getMod()->isModuleEnabled()
59
+ && !empty( $sPluginFile )
60
+ && $oWpPlugins->isActive( $sPluginFile )
61
+ && $opts->isEnabledHumanCheck();
62
  break;
63
 
64
  default:
65
+ $needed = parent::isDisplayNeeded( $notice );
66
  break;
67
  }
68
+ return $needed;
69
  }
70
  }
src/lib/src/Modules/CommentsFilter/AjaxHandler.php CHANGED
@@ -5,7 +5,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
- class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
9
 
10
  protected function processAjaxAction( string $action ) :array {
11
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
+ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
9
 
10
  protected function processAjaxAction( string $action ) :array {
11
 
src/lib/src/Modules/CommentsFilter/Forms/Gasp.php ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
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;
9
+
10
+ class Gasp {
11
+
12
+ use ModConsumer;
13
+ use OneTimeExecute;
14
+
15
+ /**
16
+ * The unique comment token assigned to this page
17
+ * @var string
18
+ */
19
+ private $formID;
20
+
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();
30
+ }
31
+
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',
108
+ [ 'uniq' => $this->getUniqueFormId() ],
109
+ true
110
+ );
111
+ }
112
+
113
+ private function getUniqueFormId() :string {
114
+ if ( !isset( $this->formID ) ) {
115
+ $DP = Services::Data();
116
+ $id = $DP->generateRandomLetter().$DP->generateRandomString( rand( 7, 23 ), 7 );
117
+ $this->formID = preg_replace(
118
+ '#[^a-zA-Z0-9]#', '',
119
+ apply_filters( 'icwp_shield_cf_gasp_uniqid', $id ) );
120
+ }
121
+ return $this->formID;
122
+ }
123
+ }
src/lib/src/Modules/CommentsFilter/Forms/GoogleRecaptcha.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
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;
9
+
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 */
19
+ $opts = $this->getOptions();
20
+ return $opts->isEnabledCaptcha() && $mod->getCaptchaCfg()->ready;
21
+ }
22
+
23
+ protected function run() {
24
+ add_action( 'wp', [ $this, 'setup' ] );
25
+ }
26
+
27
+ public function setup() {
28
+ if ( Services::WpComments()->isCommentsOpen() ) {
29
+ $this->getCon()
30
+ ->getModule_Plugin()
31
+ ->getCaptchaEnqueue()
32
+ ->setMod( $this->getMod() )
33
+ ->setToEnqueue();
34
+ add_action( 'comment_form_after_fields', [ $this, 'printGoogleRecaptchaCheck' ] );
35
+ }
36
+ }
37
+
38
+ public function printGoogleRecaptchaCheck() {
39
+ echo $this->getCon()
40
+ ->getModule_Plugin()
41
+ ->getCaptchaEnqueue()
42
+ ->getCaptchaHtml();
43
+ }
44
+ }
src/lib/src/Modules/CommentsFilter/Insights/OverviewCards.php CHANGED
@@ -3,14 +3,14 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Insights;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Options;
7
 
8
  class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
9
 
10
  public function build() :array {
11
- /** @var \ICWP_WPSF_FeatureHandler_CommentsFilter $mod */
12
  $mod = $this->getMod();
13
- /** @var Options $opts */
14
  $opts = $this->getOptions();
15
 
16
  $cardSection = [
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Insights;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
7
 
8
  class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
9
 
10
  public function build() :array {
11
+ /** @var CommentsFilter\ModCon $mod */
12
  $mod = $this->getMod();
13
+ /** @var CommentsFilter\Options $opts */
14
  $opts = $this->getOptions();
15
 
16
  $cardSection = [
src/lib/src/Modules/CommentsFilter/ModCon.php ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Captcha\CaptchaConfigVO;
8
+
9
+ class ModCon extends BaseShield\ModCon {
10
+
11
+ public function getCaptchaCfg() :CaptchaConfigVO {
12
+ $cfg = parent::getCaptchaCfg();
13
+ $sStyle = $this->getOptions()->getOpt( 'google_recaptcha_style_comments' );
14
+ if ( $sStyle !== 'default' && $this->isPremium() ) {
15
+ $cfg->theme = $sStyle;
16
+ $cfg->invisible = $cfg->theme == 'invisible';
17
+ }
18
+ return $cfg;
19
+ }
20
+
21
+ public function ensureCorrectCaptchaConfig() {
22
+ /** @var Options $opts */
23
+ $opts = $this->getOptions();
24
+
25
+ $sStyle = $opts->getOpt( 'google_recaptcha_style_comments' );
26
+ if ( $this->isPremium() ) {
27
+ $oCfg = $this->getCaptchaCfg();
28
+ if ( $oCfg->provider == $oCfg::PROV_GOOGLE_RECAP2 ) {
29
+ if ( !$oCfg->invisible && $sStyle == 'invisible' ) {
30
+ $opts->setOpt( 'google_recaptcha_style_comments', 'default' );
31
+ }
32
+ }
33
+ }
34
+ elseif ( !in_array( $sStyle, [ 'disabled', 'default' ] ) ) {
35
+ $opts->setOpt( 'google_recaptcha_style_comments', 'default' );
36
+ }
37
+ }
38
+
39
+ public function getTextOptDefault( string $key ) :string {
40
+
41
+ switch ( $key ) {
42
+ case 'custom_message_checkbox':
43
+ $text = __( "I'm not a spammer.", 'wp-simple-firewall' );
44
+ break;
45
+ case 'custom_message_alert':
46
+ $text = __( "Please check the box to confirm you're not a spammer.", 'wp-simple-firewall' );
47
+ break;
48
+ case 'custom_message_comment_wait':
49
+ $text = __( "Please wait %s seconds before posting your comment.", 'wp-simple-firewall' );
50
+ break;
51
+ case 'custom_message_comment_reload':
52
+ $text = __( "Please reload this page to post a comment.", 'wp-simple-firewall' );
53
+ break;
54
+ default:
55
+ $text = parent::getTextOptDefault( $key );
56
+ break;
57
+ }
58
+ return $text;
59
+ }
60
+
61
+ protected function preProcessOptions() {
62
+ /** @var Options $opts */
63
+ $opts = $this->getOptions();
64
+
65
+ // clean roles
66
+ $opts->setOpt( 'trusted_user_roles',
67
+ array_unique( array_filter( array_map(
68
+ function ( $sRole ) {
69
+ return sanitize_key( strtolower( $sRole ) );
70
+ },
71
+ $opts->getTrustedRoles()
72
+ ) ) )
73
+ );
74
+
75
+ $this->ensureCorrectCaptchaConfig();
76
+ }
77
+
78
+ public function isEnabledCaptcha() :bool {
79
+ /** @var Options $opts */
80
+ $opts = $this->getOptions();
81
+ return $this->isModOptEnabled() && !$opts->isEnabledCaptcha()
82
+ && $this->getCaptchaCfg()->ready;
83
+ }
84
+
85
+ public function setEnabledGasp( bool $enabled = true ) {
86
+ $this->getOptions()->setOpt( 'enable_comments_gasp_protection', $enabled ? 'Y' : 'N' );
87
+ }
88
+
89
+ /**
90
+ * @return string
91
+ */
92
+ public function getSpamBlacklistFile() {
93
+ return $this->getCon()->getPluginCachePath( 'spamblacklist.txt' );
94
+ }
95
+ }
src/lib/src/Modules/CommentsFilter/Options.php CHANGED
@@ -2,21 +2,15 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
 
7
- class Options extends Base\ShieldOptions {
8
 
9
- /**
10
- * @return int
11
- */
12
- public function getApprovedMinimum() {
13
  return (int)$this->getOpt( 'trusted_commenter_minimum', 1 );
14
  }
15
 
16
- /**
17
- * @return string[]
18
- */
19
- public function getHumanSpamFilterItems() {
20
  $aDefault = $this->getOptDefault( 'human_spam_items' );
21
  $aItems = apply_filters(
22
  $this->getCon()->prefix( 'human_spam_items' ),
@@ -25,10 +19,7 @@ class Options extends Base\ShieldOptions {
25
  return is_array( $aItems ) ? array_intersect( $aDefault, $aItems ) : $aDefault;
26
  }
27
 
28
- /**
29
- * @return int
30
- */
31
- public function getTokenCooldown() {
32
  if ( (int)$this->getOpt( 'comments_cooldown', 10 ) < 1 ) {
33
  $this->resetOptToDefault( 'comments_cooldown' );
34
  }
@@ -40,10 +31,7 @@ class Options extends Base\ShieldOptions {
40
  );
41
  }
42
 
43
- /**
44
- * @return int
45
- */
46
- public function getTokenExpireInterval() {
47
  return (int)max( 0,
48
  apply_filters(
49
  $this->getCon()->prefix( 'comments_expire' ),
@@ -63,25 +51,16 @@ class Options extends Base\ShieldOptions {
63
  return is_array( $aRoles ) ? $aRoles : [];
64
  }
65
 
66
- /**
67
- * @return bool
68
- */
69
- public function isEnabledGaspCheck() {
70
  return $this->isOpt( 'enable_comments_gasp_protection', 'Y' )
71
  && ( $this->getTokenExpireInterval() > $this->getTokenCooldown() );
72
  }
73
 
74
- /**
75
- * @return bool
76
- */
77
- public function isEnabledCaptcha() {
78
  return !$this->isOpt( 'google_recaptcha_style_comments', 'disabled' );
79
  }
80
 
81
- /**
82
- * @return bool
83
- */
84
- public function isEnabledHumanCheck() {
85
  return $this->isOpt( 'enable_comments_human_spam_filter', 'Y' )
86
  && count( $this->getHumanSpamFilterItems() ) > 0;
87
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
 
7
+ class Options extends BaseShield\Options {
8
 
9
+ public function getApprovedMinimum() :int {
 
 
 
10
  return (int)$this->getOpt( 'trusted_commenter_minimum', 1 );
11
  }
12
 
13
+ public function getHumanSpamFilterItems() :array {
 
 
 
14
  $aDefault = $this->getOptDefault( 'human_spam_items' );
15
  $aItems = apply_filters(
16
  $this->getCon()->prefix( 'human_spam_items' ),
19
  return is_array( $aItems ) ? array_intersect( $aDefault, $aItems ) : $aDefault;
20
  }
21
 
22
+ public function getTokenCooldown() :int {
 
 
 
23
  if ( (int)$this->getOpt( 'comments_cooldown', 10 ) < 1 ) {
24
  $this->resetOptToDefault( 'comments_cooldown' );
25
  }
31
  );
32
  }
33
 
34
+ public function getTokenExpireInterval() :int {
 
 
 
35
  return (int)max( 0,
36
  apply_filters(
37
  $this->getCon()->prefix( 'comments_expire' ),
51
  return is_array( $aRoles ) ? $aRoles : [];
52
  }
53
 
54
+ public function isEnabledGaspCheck() :bool {
 
 
 
55
  return $this->isOpt( 'enable_comments_gasp_protection', 'Y' )
56
  && ( $this->getTokenExpireInterval() > $this->getTokenCooldown() );
57
  }
58
 
59
+ public function isEnabledCaptcha() :bool {
 
 
 
60
  return !$this->isOpt( 'google_recaptcha_style_comments', 'disabled' );
61
  }
62
 
63
+ public function isEnabledHumanCheck() :bool {
 
 
 
64
  return $this->isOpt( 'enable_comments_human_spam_filter', 'Y' )
65
  && count( $this->getHumanSpamFilterItems() ) > 0;
66
  }
src/lib/src/Modules/CommentsFilter/Processor.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class Processor extends BaseShield\Processor {
9
+
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() )
26
+ ->execute();
27
+
28
+ if ( Services::Request()->isPost() ) {
29
+ ( new Scan\Scanner() )
30
+ ->setMod( $this->getMod() )
31
+ ->execute();
32
+ add_filter( 'comment_notification_recipients', [ $this, 'clearCommentNotificationEmail' ], 100, 1 );
33
+ }
34
+ else {
35
+ ( new Forms\Gasp() )
36
+ ->setMod( $this->getMod() )
37
+ ->execute();
38
+ }
39
+ }
40
+ }
41
+
42
+ public function runHourlyCron() {
43
+ /** @var Options $opts */
44
+ $opts = $this->getOptions();
45
+ if ( $opts->isEnabledGaspCheck() && function_exists( 'delete_expired_transients' ) ) {
46
+ delete_expired_transients(); // cleanup unused comment tokens
47
+ }
48
+ }
49
+
50
+ /**
51
+ * When you set a new comment as anything but 'spam' a notification email is sent to the post author.
52
+ * We suppress this for when we mark as trash by emptying the email notifications list.
53
+ * @param array $aEmails
54
+ * @return array
55
+ */
56
+ public function clearCommentNotificationEmail( $aEmails ) {
57
+ $sStatus = apply_filters( $this->getCon()->prefix( 'cf_status' ), '' );
58
+ if ( in_array( $sStatus, [ 'reject', 'trash' ] ) ) {
59
+ $aEmails = [];
60
+ }
61
+ return $aEmails;
62
+ }
63
+ }
src/lib/src/Modules/CommentsFilter/Scan/Human.php CHANGED
@@ -18,8 +18,8 @@ class Human {
18
  * @return \WP_Error|true
19
  */
20
  public function scan( $aCommData ) {
21
- /** @var CommentsFilter\Options $oOpts */
22
- $oOpts = $this->getOptions();
23
 
24
  $aItemsToCheck = array_intersect_key(
25
  [
@@ -30,7 +30,7 @@ class Human {
30
  'ip_address' => Services::IP()->getRequestIp(),
31
  'user_agent' => substr( Services::Request()->getUserAgent(), 0, 254 )
32
  ],
33
- array_flip( $oOpts->getHumanSpamFilterItems() )
34
  );
35
 
36
  $mResult = true;
@@ -58,11 +58,11 @@ class Human {
58
  * @return string[]
59
  */
60
  private function getSpamBlacklist() {
61
- /** @var \ICWP_WPSF_FeatureHandler_CommentsFilter $oMod */
62
- $oMod = $this->getMod();
63
  $aList = [];
64
  $oFs = Services::WpFs();
65
- $sBLFile = $oMod->getSpamBlacklistFile();
66
 
67
  // Download if doesn't exist or expired.
68
  if ( !$oFs->exists( $sBLFile )
@@ -81,11 +81,11 @@ class Human {
81
  }
82
 
83
  private function importBlacklist() {
84
- /** @var \ICWP_WPSF_FeatureHandler_CommentsFilter $oMod */
85
- $oMod = $this->getMod();
86
- $oFs = Services::WpFs();
87
- $sBLFile = $oMod->getSpamBlacklistFile();
88
- if ( !$oFs->exists( $sBLFile ) ) {
89
  $sRawList = Services::HttpRequest()->getContent( $this->getOptions()
90
  ->getDef( 'url_spam_blacklist_terms' ) );
91
  $sList = '';
@@ -93,7 +93,7 @@ class Human {
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
- $oFs->putFileContent( $sBLFile, $sList, true );
97
  }
98
  }
99
  }
18
  * @return \WP_Error|true
19
  */
20
  public function scan( $aCommData ) {
21
+ /** @var CommentsFilter\Options $opts */
22
+ $opts = $this->getOptions();
23
 
24
  $aItemsToCheck = array_intersect_key(
25
  [
30
  'ip_address' => Services::IP()->getRequestIp(),
31
  'user_agent' => substr( Services::Request()->getUserAgent(), 0, 254 )
32
  ],
33
+ array_flip( $opts->getHumanSpamFilterItems() )
34
  );
35
 
36
  $mResult = true;
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 )
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 = '';
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
  }
src/lib/src/Modules/CommentsFilter/Scan/Scanner.php CHANGED
@@ -2,7 +2,8 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Scan;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Options;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Utilities;
8
  use FernleafSystems\Wordpress\Services\Services;
@@ -10,6 +11,7 @@ use FernleafSystems\Wordpress\Services\Services;
10
  class Scanner {
11
 
12
  use ModConsumer;
 
13
 
14
  /**
15
  * @var string|int|null
@@ -21,7 +23,11 @@ class Scanner {
21
  */
22
  private $sCommentExplanation;
23
 
24
- public function run() {
 
 
 
 
25
  if ( Services::WpComments()->isCommentSubmission() ) {
26
  add_filter( 'preprocess_comment', [ $this, 'checkComment' ], 5 );
27
  }
@@ -117,9 +123,9 @@ class Scanner {
117
  * @return true|\WP_Error|null
118
  */
119
  private function runScans( $aCommData ) {
120
- /** @var \ICWP_WPSF_FeatureHandler_CommentsFilter $mod */
121
  $mod = $this->getMod();
122
- /** @var Options $opts */
123
  $opts = $this->getOptions();
124
 
125
  $mResult = true;
@@ -163,7 +169,7 @@ class Scanner {
163
  * @return bool
164
  */
165
  public function getIfDoCommentsCheck( $nPostId, $sCommentEmail ) {
166
- /** @var Options $opts */
167
  $opts = $this->getOptions();
168
  $post = Services::WpPost()->getById( $nPostId );
169
  return $post instanceof \WP_Post
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;
9
  use FernleafSystems\Wordpress\Services\Services;
11
  class Scanner {
12
 
13
  use ModConsumer;
14
+ use OneTimeExecute;
15
 
16
  /**
17
  * @var string|int|null
23
  */
24
  private $sCommentExplanation;
25
 
26
+ protected function canRun() {
27
+ return Services::Request()->isPost();
28
+ }
29
+
30
+ protected function run() {
31
  if ( Services::WpComments()->isCommentSubmission() ) {
32
  add_filter( 'preprocess_comment', [ $this, 'checkComment' ], 5 );
33
  }
123
  * @return true|\WP_Error|null
124
  */
125
  private function runScans( $aCommData ) {
126
+ /** @var CommentsFilter\ModCon $mod */
127
  $mod = $this->getMod();
128
+ /** @var CommentsFilter\Options $opts */
129
  $opts = $this->getOptions();
130
 
131
  $mResult = true;
169
  * @return bool
170
  */
171
  public function getIfDoCommentsCheck( $nPostId, $sCommentEmail ) {
172
+ /** @var CommentsFilter\Options $opts */
173
  $opts = $this->getOptions();
174
  $post = Services::WpPost()->getById( $nPostId );
175
  return $post instanceof \WP_Post
src/lib/src/Modules/CommentsFilter/Strings.php CHANGED
@@ -87,14 +87,14 @@ class Strings extends Base\Strings {
87
  * @throws \Exception
88
  */
89
  public function getOptionStrings( string $key ) :array {
90
- /** @var \ICWP_WPSF_FeatureHandler_CommentsFilter $oMod */
91
- $oMod = $this->getMod();
92
- $sModName = $this->getMod()->getMainFeatureName();
93
 
94
  switch ( $key ) {
95
 
96
  case 'enable_comments_filter' :
97
- $sName = sprintf( __( 'Enable %s Module', 'wp-simple-firewall' ), $sModName );
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;
@@ -154,7 +154,7 @@ class Strings extends Base\Strings {
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 ( !$oMod->getCaptchaCfg()->ready ) {
158
  $sDescription[] = sprintf( '<a href="%s">%s</a>',
159
  $this->getCon()
160
  ->getModule_Plugin()
87
  * @throws \Exception
88
  */
89
  public function getOptionStrings( string $key ) :array {
90
+ /** @var ModCon $mod */
91
+ $mod = $this->getMod();
92
+ $modName = $this->getMod()->getMainFeatureName();
93
 
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;
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()
src/lib/src/Modules/CommentsFilter/UI.php CHANGED
@@ -2,7 +2,8 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
 
 
6
 
7
- class UI extends Base\ShieldUI {
8
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+
7
+ class UI extends BaseShield\UI {
8
 
 
9
  }
src/lib/src/Modules/Comms/ModCon.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Comms;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+
7
+ class ModCon extends BaseShield\ModCon {
8
+
9
+ public function getSureSendController() :Lib\SureSend\SureSendController {
10
+ return ( new Lib\SureSend\SureSendController() )
11
+ ->setMod( $this );
12
+ }
13
+ }
src/lib/src/Modules/Comms/Options.php CHANGED
@@ -2,8 +2,8 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Comms;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
 
7
- class Options extends Base\ShieldOptions {
8
 
9
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Comms;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
 
7
+ class Options extends BaseShield\Options {
8
 
9
  }
src/lib/src/Modules/Comms/Processor.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Comms;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+
7
+ class Processor extends BaseShield\Processor {
8
+
9
+ }
src/lib/src/Modules/Email/ModCon.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Email;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+
7
+ class ModCon extends BaseShield\ModCon {
8
+
9
+ }
src/lib/src/Modules/Email/Options.php CHANGED
@@ -1,9 +1,9 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Email;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
 
7
- class Options extends Base\ShieldOptions {
8
 
9
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Email;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
 
7
+ class Options extends BaseShield\Options {
8
 
9
  }
src/lib/src/Modules/Email/Processor.php ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Email;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class Processor extends BaseShield\Processor {
9
+
10
+ protected function getEmailHeader() :array {
11
+ return [
12
+ __( 'Hi !', 'wp-simple-firewall' ),
13
+ '',
14
+ ];
15
+ }
16
+
17
+ protected function getEmailFooter() :array {
18
+ $con = $this->getCon();
19
+ $WP = Services::WpGeneral();
20
+
21
+ {
22
+ $goPro = [
23
+ 'Go PRO For The Equivalent Of 1 Cappuccino Per Month &#9749;',
24
+ 'Go PRO For The Equivalent Of 1 Beer Per Month &#127866;',
25
+ 'Go PRO For The Equivalent Of 1 Glass Of Wine Per Month &#127863;',
26
+ ];
27
+ $benefits = [
28
+ 'The Easiest, Frustration-Free Pro-Upgrade Available Anywhere',
29
+ 'MainWP Integration',
30
+ 'Powerful, Auto-Learning Malware Scanner',
31
+ 'Plugin and Theme File Guard',
32
+ 'Vulnerability Scanner',
33
+ 'Traffic Rate Limiting',
34
+ 'WooCommerce Support',
35
+ 'Automatic Import/Export Sync Of Options Across Your WP Portfolio',
36
+ 'Powerful User Password Policies',
37
+ 'Exclusive Customer Support',
38
+ 'That Warm And Fuzzy Feeling That Comes From Supporting Future Development',
39
+ ];
40
+ shuffle( $benefits );
41
+ }
42
+
43
+ $footer = [
44
+ $this->getMod()
45
+ ->renderTemplate( '/email/footer.twig', [
46
+ 'strings' => [
47
+ 'benefits' => $benefits,
48
+ 'much_more' => 'And So Much More',
49
+ 'upgrade' => $goPro[ array_rand( $goPro ) ],
50
+ 'sent_from' => sprintf( __( 'Email sent from the %s Plugin v%s, on %s.', 'wp-simple-firewall' ),
51
+ $this->getCon()->getHumanName(),
52
+ $this->getCon()->getVersion(),
53
+ $WP->getHomeUrl()
54
+ ),
55
+ 'delays' => __( 'Note: Email delays are caused by website hosting and email providers.', 'wp-simple-firewall' ),
56
+ 'time_sent' => sprintf( __( 'Time Sent: %s', 'wp-simple-firewall' ), $WP->getTimeStampForDisplay() ),
57
+ ],
58
+ 'hrefs' => [
59
+ 'upgrade' => 'https://shsec.io/buyshieldproemailfooter',
60
+ 'much_more' => 'https://shsec.io/gp'
61
+ ],
62
+ 'flags' => [
63
+ 'is_pro' => $con->isPremiumActive(),
64
+ 'is_whitelabelled' => $con->getModule_SecAdmin()->isWlEnabled()
65
+ ]
66
+ ] ),
67
+ ];
68
+
69
+ return apply_filters( 'icwp_shield_email_footer', $footer );
70
+ }
71
+
72
+ /**
73
+ * Wraps up a message with header and footer
74
+ * @param string $to
75
+ * @param string $subject
76
+ * @param array $message
77
+ * @return bool
78
+ */
79
+ public function sendEmailWithWrap( $to = '', $subject = '', $message = [] ) :bool {
80
+ $WP = Services::WpGeneral();
81
+ return $this->send(
82
+ $to,
83
+ $subject,
84
+ sprintf( '<html lang="%s">%s</html>',
85
+ $WP->getLocale( '-' ),
86
+ implode( "<br />", array_merge( $this->getEmailHeader(), $message, $this->getEmailFooter() ) )
87
+ )
88
+ );
89
+ }
90
+
91
+ public function sendEmailWithTemplate( string $templ, string $to, string $subject, array $body ) :bool {
92
+ return $this->send(
93
+ $to,
94
+ $subject,
95
+ $this->getMod()->renderTemplate(
96
+ $templ,
97
+ [
98
+ 'header' => $this->getEmailHeader(),
99
+ 'body' => $body,
100
+ 'footer' => $this->getEmailFooter(),
101
+ 'vars' => [
102
+ 'lang' => Services::WpGeneral()->getLocale( '-' )
103
+ ]
104
+ ],
105
+ true
106
+ )
107
+ );
108
+ }
109
+
110
+ /**
111
+ * @param string $to
112
+ * @param string $subject
113
+ * @param string $body
114
+ * @return bool
115
+ * @uses wp_mail
116
+ */
117
+ public function send( $to = '', $subject = '', $body = '' ) :bool {
118
+
119
+ $this->emailFilters( true );
120
+ $success = wp_mail(
121
+ $this->verifyEmailAddress( $to ),
122
+ sprintf( '[%s] %s', html_entity_decode( Services::WpGeneral()->getSiteName(), ENT_QUOTES ), $subject ),
123
+ $body
124
+ );
125
+ $this->emailFilters( false );
126
+ return (bool)$success;
127
+ }
128
+
129
+ /**
130
+ * @param $add - true to add, false to remove
131
+ */
132
+ private function emailFilters( bool $add ) {
133
+ if ( $add ) {
134
+ add_filter( 'wp_mail_from', [ $this, 'setMailFrom' ], 100 );
135
+ add_filter( 'wp_mail_from_name', [ $this, 'setMailFromName' ], 100 );
136
+ add_filter( 'wp_mail_content_type', [ $this, 'setMailContentType' ], 100, 0 );
137
+ }
138
+ else {
139
+ remove_filter( 'wp_mail_from', [ $this, 'setMailFrom' ], 100 );
140
+ remove_filter( 'wp_mail_from_name', [ $this, 'setMailFromName' ], 100 );
141
+ remove_filter( 'wp_mail_content_type', [ $this, 'setMailContentType' ], 100 );
142
+ }
143
+ }
144
+
145
+ public function setMailContentType() :string {
146
+ return 'text/html';
147
+ }
148
+
149
+ /**
150
+ * @param string $from
151
+ * @return string
152
+ */
153
+ public function setMailFrom( $from ) {
154
+ $DP = Services::Data();
155
+ $proposed = apply_filters( 'icwp_shield_from_email', '' );
156
+ if ( $DP->validEmail( $proposed ) ) {
157
+ $from = $proposed;
158
+ }
159
+ // We help out by trying to correct any funky "from" addresses
160
+ // So, at the very least, we don't fail on this for our emails.
161
+ if ( !$DP->validEmail( $from ) ) {
162
+ $urlParts = @parse_url( Services::WpGeneral()->getWpUrl() );
163
+ if ( !empty( $urlParts[ 'host' ] ) ) {
164
+ $proposed = 'wordpress@'.$urlParts[ 'host' ];
165
+ if ( $DP->validEmail( $proposed ) ) {
166
+ $from = $proposed;
167
+ }
168
+ }
169
+ }
170
+ return $from;
171
+ }
172
+
173
+ /**
174
+ * @param string $name
175
+ * @return string
176
+ */
177
+ public function setMailFromName( $name ) :string {
178
+ $proposed = apply_filters( 'icwp_shield_from_email_name', '' );
179
+ if ( !empty( $proposed ) ) {
180
+ $name = $proposed;
181
+ }
182
+ else {
183
+ $name = sprintf( '%s - %s', $name, $this->getCon()->getHumanName() );
184
+ }
185
+ return $name;
186
+ }
187
+
188
+ /**
189
+ * Will send email to the default recipient setup in the object.
190
+ * @param string $subject
191
+ * @param array $message
192
+ * @return bool
193
+ */
194
+ public function sendEmail( $subject, $message ) {
195
+ return $this->sendEmailWithWrap( null, $subject, $message );
196
+ }
197
+
198
+ /**
199
+ * @param string $email
200
+ * @return string
201
+ */
202
+ public function verifyEmailAddress( $email = '' ) {
203
+ return Services::Data()->validEmail( $email ) ? $email : Services::WpGeneral()->getSiteAdminEmail();
204
+ }
205
+ }
src/lib/src/Modules/Email/Strings.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Email;
4
 
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Email;
4
 
src/lib/src/Modules/Events/AjaxHandler.php CHANGED
@@ -6,7 +6,7 @@ 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\Base\AjaxHandlerShield {
10
 
11
  protected function processAjaxAction( string $action ) :array {
12
 
@@ -27,7 +27,7 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
27
  }
28
 
29
  private function ajaxExec_RenderChart() :array {
30
- /** @var \ICWP_WPSF_FeatureHandler_Events $mod */
31
  $mod = $this->getMod();
32
 
33
  $aParams = $this->getAjaxFormParams();
@@ -46,15 +46,15 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
46
  }
47
 
48
  private function ajaxExec_RenderChartPost() :array {
49
- /** @var \ICWP_WPSF_FeatureHandler_Events $oMod */
50
- $oMod = $this->getMod();
51
- $oReq = Services::Request();
52
  $oChartReq = new Events\Charts\ChartRequestVO();
53
- $oChartReq->render_location = $oReq->post( 'render_location' );
54
- $oChartReq->chart_params = $oReq->post( 'chart_params' );
55
 
56
  $aChart = ( new Events\Charts\BuildData() )
57
- ->setMod( $oMod )
58
  ->build( $oChartReq );
59
 
60
  return [
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
 
27
  }
28
 
29
  private function ajaxExec_RenderChart() :array {
30
+ /** @var ModCon $mod */
31
  $mod = $this->getMod();
32
 
33
  $aParams = $this->getAjaxFormParams();
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 [
src/lib/src/Modules/Events/Charts/BuildData.php CHANGED
@@ -3,6 +3,7 @@
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\ModConsumer;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
@@ -11,17 +12,17 @@ class BuildData {
11
  use ModConsumer;
12
 
13
  /**
14
- * @param ChartRequestVO $oReq
15
  * @return array
16
  */
17
- public function build( ChartRequestVO $oReq ) {
18
- /** @var \ICWP_WPSF_FeatureHandler_Events $oMod */
19
- $oMod = $this->getMod();
20
 
21
- $this->preProcessRequest( $oReq );
22
 
23
  /** @var Events\Handler $oDbhEvts */
24
- $oDbhEvts = $oMod->getDbHandler_Events();
25
 
26
  $nTick = 0;
27
  $oTime = Services::Request()->carbon();
@@ -33,7 +34,7 @@ class BuildData {
33
 
34
  /** @var Events\Select $oSelEvts */
35
  $oSelEvts = $oDbhEvts->getQuerySelector();
36
- switch ( $oReq->interval ) {
37
  case 'hourly':
38
  $oSelEvts->filterByBoundary_Hour( $oTime->timestamp );
39
  $oTime->subHour();
@@ -56,10 +57,10 @@ class BuildData {
56
  break;
57
  }
58
 
59
- $aSeries[] = $oSelEvts->sumEvents( $oReq->events );
60
 
61
  $nTick++;
62
- } while ( $nTick < $oReq->ticks );
63
 
64
  return [
65
  'data' => [
@@ -88,7 +89,7 @@ class BuildData {
88
  }
89
  }
90
 
91
- $aAll = array_keys( $this->getCon()->getAllEvents() );
92
  if ( !empty( $oReq->chart_params[ 'stat_id' ] ) ) {
93
  switch ( $oReq->chart_params[ 'stat_id' ] ) {
94
  case 'comment_block':
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
 
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();
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();
57
  break;
58
  }
59
 
60
+ $aSeries[] = $oSelEvts->sumEvents( $req->events );
61
 
62
  $nTick++;
63
+ } while ( $nTick < $req->ticks );
64
 
65
  return [
66
  'data' => [
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':
src/lib/src/Modules/Events/Consolidate/ConsolidateAllEvents.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Events\Consolidate;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Events;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
@@ -24,9 +25,9 @@ class ConsolidateAllEvents {
24
  * @param $sEvent
25
  */
26
  protected function consolidateEventIntoHourly( $sEvent ) {
27
- /** @var \ICWP_WPSF_FeatureHandler_Events $oMod */
28
- $oMod = $this->getMod();
29
- $oDbH = $oMod->getDbHandler_Events();
30
 
31
  $oTime = Services::Request()
32
  ->carbon()
@@ -76,9 +77,9 @@ class ConsolidateAllEvents {
76
  * @param $sEvent
77
  */
78
  protected function consolidateEventIntoDaily( $sEvent ) {
79
- /** @var \ICWP_WPSF_FeatureHandler_Events $oMod */
80
- $oMod = $this->getMod();
81
- $oDbH = $oMod->getDbHandler_Events();
82
 
83
  $oTime = Services::Request()
84
  ->carbon()
@@ -125,12 +126,12 @@ class ConsolidateAllEvents {
125
  /**
126
  * Consolidates each event in weekly sums. Doesn't process events from the previous 2 whole weeks.
127
  * Processes event for the previous 8 weeks.
128
- * @param $sEvent
129
  */
130
- protected function consolidateEventIntoWeekly( $sEvent ) {
131
- /** @var \ICWP_WPSF_FeatureHandler_Events $oMod */
132
- $oMod = $this->getMod();
133
- $oDbH = $oMod->getDbHandler_Events();
134
 
135
  $oTime = Services::Request()
136
  ->carbon()
@@ -142,7 +143,7 @@ class ConsolidateAllEvents {
142
  /** @var Events\Select $oSel */
143
  $oSel = $oDbH->getQuerySelector();
144
  $nRecords = $oSel->filterByBoundary_Week( $oTime->timestamp )
145
- ->filterByEvent( $sEvent )
146
  ->count();
147
 
148
  if ( $nRecords > 1 ) {
@@ -150,17 +151,17 @@ class ConsolidateAllEvents {
150
  $oSel = $oDbH->getQuerySelector();
151
  /** @var Events\EntryVO[] $aRecords */
152
  $nSum = $oSel->filterByBoundary_Week( $oTime->timestamp )
153
- ->sumEvent( $sEvent );
154
 
155
  if ( $nSum > 0 ) {
156
  /** @var Events\Delete $oDel */
157
  $oDel = $oDbH->getQueryDeleter();
158
  $oDel->filterByBoundary_Week( $oTime->timestamp )
159
- ->filterByEvent( $sEvent )
160
  ->query();
161
 
162
  $oEntry = new Events\EntryVO();
163
- $oEntry->event = $sEvent;
164
  $oEntry->count = $nSum;
165
  $oEntry->created_at = $oTime->timestamp + 1;
166
  /** @var Events\Insert $oQI */
@@ -180,9 +181,9 @@ class ConsolidateAllEvents {
180
  * @param $sEvent
181
  */
182
  protected function consolidateEventIntoMonthly( $sEvent ) {
183
- /** @var \ICWP_WPSF_FeatureHandler_Events $oMod */
184
- $oMod = $this->getMod();
185
- $oDbH = $oMod->getDbHandler_Events();
186
 
187
  $oTime = Services::Request()
188
  ->carbon()
@@ -192,21 +193,21 @@ class ConsolidateAllEvents {
192
  $nMonthCount = 0;
193
  do {
194
  /** @var Events\Select $oSel */
195
- $oSel = $oDbH->getQuerySelector();
196
  $nRecords = $oSel->filterByBoundary_Month( $oTime->timestamp )
197
  ->filterByEvent( $sEvent )
198
  ->count();
199
 
200
  if ( $nRecords > 1 ) {
201
  /** @var Events\Select $oSel */
202
- $oSel = $oDbH->getQuerySelector();
203
  /** @var Events\EntryVO[] $aRecords */
204
  $nSum = $oSel->filterByBoundary_Month( $oTime->timestamp )
205
  ->sumEvent( $sEvent );
206
 
207
  if ( $nSum > 0 ) {
208
  /** @var Events\Delete $oDel */
209
- $oDel = $oDbH->getQueryDeleter();
210
  $oDel->filterByBoundary_Month( $oTime->timestamp )
211
  ->filterByEvent( $sEvent )
212
  ->query();
@@ -216,7 +217,7 @@ class ConsolidateAllEvents {
216
  $oEntry->count = $nSum;
217
  $oEntry->created_at = $oTime->timestamp + 1;
218
  /** @var Events\Insert $oQI */
219
- $oQI = $oDbH->getQueryInserter();
220
  $oQI->insert( $oEntry );
221
  }
222
  }
@@ -227,12 +228,12 @@ class ConsolidateAllEvents {
227
  }
228
 
229
  /**
230
- * @param $sEvent
231
  */
232
- protected function consolidateEventIntoYearly( $sEvent ) {
233
- /** @var \ICWP_WPSF_FeatureHandler_Events $oMod */
234
- $oMod = $this->getMod();
235
- $oDbH = $oMod->getDbHandler_Events();
236
 
237
  $oTime = Services::Request()
238
  ->carbon()
@@ -241,13 +242,13 @@ class ConsolidateAllEvents {
241
 
242
  /** @var Events\Select $oSel */
243
  $oSel = $oDbH->getQuerySelector();
244
- $oOldest = $oSel->getOldestForEvent( $sEvent );
245
 
246
  do {
247
  /** @var Events\Select $oSel */
248
  $oSel = $oDbH->getQuerySelector();
249
  $nRecords = $oSel->filterByBoundary_Year( $oTime->timestamp )
250
- ->filterByEvent( $sEvent )
251
  ->count();
252
 
253
  if ( $nRecords > 1 ) {
@@ -255,17 +256,17 @@ class ConsolidateAllEvents {
255
  $oSel = $oDbH->getQuerySelector();
256
  /** @var Events\EntryVO[] $aRecords */
257
  $nSum = $oSel->filterByBoundary_Year( $oTime->timestamp )
258
- ->sumEvent( $sEvent );
259
 
260
  if ( $nSum > 0 ) {
261
  /** @var Events\Delete $oDel */
262
  $oDel = $oDbH->getQueryDeleter();
263
  $oDel->filterByBoundary_Year( $oTime->timestamp )
264
- ->filterByEvent( $sEvent )
265
  ->query();
266
 
267
  $oEntry = new Events\EntryVO();
268
- $oEntry->event = $sEvent;
269
  $oEntry->count = $nSum;
270
  $oEntry->created_at = $oTime->timestamp + 1;
271
  /** @var Events\Insert $oQI */
@@ -282,10 +283,10 @@ class ConsolidateAllEvents {
282
  * @return string[]
283
  */
284
  protected function getAllEvents() {
285
- /** @var \ICWP_WPSF_FeatureHandler_Events $oMod */
286
- $oMod = $this->getMod();
287
  /** @var Events\Select $oSel */
288
- $oSel = $oMod->getDbHandler_Events()->getQuerySelector();
289
  return $oSel->getAllEvents();
290
  }
291
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Events\Consolidate;
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
 
25
  * @param $sEvent
26
  */
27
  protected function consolidateEventIntoHourly( $sEvent ) {
28
+ /** @var ModCon $mod */
29
+ $mod = $this->getMod();
30
+ $oDbH = $mod->getDbHandler_Events();
31
 
32
  $oTime = Services::Request()
33
  ->carbon()
77
  * @param $sEvent
78
  */
79
  protected function consolidateEventIntoDaily( $sEvent ) {
80
+ /** @var ModCon $mod */
81
+ $mod = $this->getMod();
82
+ $oDbH = $mod->getDbHandler_Events();
83
 
84
  $oTime = Services::Request()
85
  ->carbon()
126
  /**
127
  * Consolidates each event in weekly sums. Doesn't process events from the previous 2 whole weeks.
128
  * Processes event for the previous 8 weeks.
129
+ * @param $event
130
  */
131
+ protected function consolidateEventIntoWeekly( $event ) {
132
+ /** @var ModCon $mod */
133
+ $mod = $this->getMod();
134
+ $oDbH = $mod->getDbHandler_Events();
135
 
136
  $oTime = Services::Request()
137
  ->carbon()
143
  /** @var Events\Select $oSel */
144
  $oSel = $oDbH->getQuerySelector();
145
  $nRecords = $oSel->filterByBoundary_Week( $oTime->timestamp )
146
+ ->filterByEvent( $event )
147
  ->count();
148
 
149
  if ( $nRecords > 1 ) {
151
  $oSel = $oDbH->getQuerySelector();
152
  /** @var Events\EntryVO[] $aRecords */
153
  $nSum = $oSel->filterByBoundary_Week( $oTime->timestamp )
154
+ ->sumEvent( $event );
155
 
156
  if ( $nSum > 0 ) {
157
  /** @var Events\Delete $oDel */
158
  $oDel = $oDbH->getQueryDeleter();
159
  $oDel->filterByBoundary_Week( $oTime->timestamp )
160
+ ->filterByEvent( $event )
161
  ->query();
162
 
163
  $oEntry = new Events\EntryVO();
164
+ $oEntry->event = $event;
165
  $oEntry->count = $nSum;
166
  $oEntry->created_at = $oTime->timestamp + 1;
167
  /** @var Events\Insert $oQI */
181
  * @param $sEvent
182
  */
183
  protected function consolidateEventIntoMonthly( $sEvent ) {
184
+ /** @var ModCon $mod */
185
+ $mod = $this->getMod();
186
+ $dbh = $mod->getDbHandler_Events();
187
 
188
  $oTime = Services::Request()
189
  ->carbon()
193
  $nMonthCount = 0;
194
  do {
195
  /** @var Events\Select $oSel */
196
+ $oSel = $dbh->getQuerySelector();
197
  $nRecords = $oSel->filterByBoundary_Month( $oTime->timestamp )
198
  ->filterByEvent( $sEvent )
199
  ->count();
200
 
201
  if ( $nRecords > 1 ) {
202
  /** @var Events\Select $oSel */
203
+ $oSel = $dbh->getQuerySelector();
204
  /** @var Events\EntryVO[] $aRecords */
205
  $nSum = $oSel->filterByBoundary_Month( $oTime->timestamp )
206
  ->sumEvent( $sEvent );
207
 
208
  if ( $nSum > 0 ) {
209
  /** @var Events\Delete $oDel */
210
+ $oDel = $dbh->getQueryDeleter();
211
  $oDel->filterByBoundary_Month( $oTime->timestamp )
212
  ->filterByEvent( $sEvent )
213
  ->query();
217
  $oEntry->count = $nSum;
218
  $oEntry->created_at = $oTime->timestamp + 1;
219
  /** @var Events\Insert $oQI */
220
+ $oQI = $dbh->getQueryInserter();
221
  $oQI->insert( $oEntry );
222
  }
223
  }
228
  }
229
 
230
  /**
231
+ * @param $event
232
  */
233
+ protected function consolidateEventIntoYearly( $event ) {
234
+ /** @var ModCon $mod */
235
+ $mod = $this->getMod();
236
+ $oDbH = $mod->getDbHandler_Events();
237
 
238
  $oTime = Services::Request()
239
  ->carbon()
242
 
243
  /** @var Events\Select $oSel */
244
  $oSel = $oDbH->getQuerySelector();
245
+ $oOldest = $oSel->getOldestForEvent( $event );
246
 
247
  do {
248
  /** @var Events\Select $oSel */
249
  $oSel = $oDbH->getQuerySelector();
250
  $nRecords = $oSel->filterByBoundary_Year( $oTime->timestamp )
251
+ ->filterByEvent( $event )
252
  ->count();
253
 
254
  if ( $nRecords > 1 ) {
256
  $oSel = $oDbH->getQuerySelector();
257
  /** @var Events\EntryVO[] $aRecords */
258
  $nSum = $oSel->filterByBoundary_Year( $oTime->timestamp )
259
+ ->sumEvent( $event );
260
 
261
  if ( $nSum > 0 ) {
262
  /** @var Events\Delete $oDel */
263
  $oDel = $oDbH->getQueryDeleter();
264
  $oDel->filterByBoundary_Year( $oTime->timestamp )
265
+ ->filterByEvent( $event )
266
  ->query();
267
 
268
  $oEntry = new Events\EntryVO();
269
+ $oEntry->event = $event;
270
  $oEntry->count = $nSum;
271
  $oEntry->created_at = $oTime->timestamp + 1;
272
  /** @var Events\Insert $oQI */
283
  * @return string[]
284
  */
285
  protected function getAllEvents() {
286
+ /** @var ModCon $mod */
287
+ $mod = $this->getMod();
288
  /** @var Events\Select $oSel */
289
+ $oSel = $mod->getDbHandler_Events()->getQuerySelector();
290
  return $oSel->getAllEvents();
291
  }
292
  }
src/lib/src/Modules/Events/Lib/EventsListener.php CHANGED
@@ -22,9 +22,9 @@ abstract class EventsListener {
22
  $this->setCon( $con );
23
 
24
  add_action( $con->prefix( 'event' ),
25
- function ( $sEvent, $aMeta = [] ) use ( $con ) {
26
- if ( $con->loadEventsService()->isSupportedEvent( $sEvent ) ) {
27
- $this->captureEvent( $sEvent, $aMeta );
28
  }
29
  }, 10, 2 );
30
 
@@ -34,10 +34,10 @@ abstract class EventsListener {
34
  }
35
 
36
  /**
37
- * @param string $sEvent
38
  * @param array $aMeta
39
  */
40
- abstract protected function captureEvent( $sEvent, $aMeta = [] );
41
 
42
  protected function onShutdown() {
43
 
22
  $this->setCon( $con );
23
 
24
  add_action( $con->prefix( 'event' ),
25
+ function ( $event, $meta = [] ) use ( $con ) {
26
+ if ( $con->loadEventsService()->isSupportedEvent( $event ) ) {
27
+ $this->captureEvent( $event, $meta );
28
  }
29
  }, 10, 2 );
30
 
34
  }
35
 
36
  /**
37
+ * @param string $evt
38
  * @param array $aMeta
39
  */
40
+ abstract protected function captureEvent( $evt, $aMeta = [] );
41
 
42
  protected function onShutdown() {
43
 
src/lib/src/Modules/Events/Lib/EventsService.php CHANGED
@@ -14,13 +14,13 @@ class EventsService {
14
  private $aEvents;
15
 
16
  /**
17
- * @param string $sEventTag
18
- * @param array $aMetaData
19
  * @return $this
20
  */
21
- public function fireEvent( $sEventTag, $aMetaData = [] ) {
22
- if ( $this->isSupportedEvent( $sEventTag ) ) {
23
- do_action( $this->getCon()->prefix( 'event' ), $sEventTag, $aMetaData );
24
  }
25
  return $this;
26
  }
@@ -30,41 +30,46 @@ class EventsService {
30
  */
31
  public function getEvents() :array {
32
  if ( empty( $this->aEvents ) ) {
33
- $aEvts = apply_filters( $this->getCon()->prefix( 'get_all_events' ), [] );
34
- $this->aEvents = is_array( $aEvts ) ? $this->buildEvents( $aEvts ) : [];
 
 
 
 
 
 
 
 
 
 
 
 
35
  }
36
  return $this->aEvents;
37
  }
38
 
39
  /**
40
- * @param string $sEventKey
41
  * @return array|null
42
  */
43
- public function getEventDef( $sEventKey ) {
44
- return $this->isSupportedEvent( $sEventKey ) ? $this->getEvents()[ $sEventKey ] : null;
45
  }
46
 
47
  /**
48
  * @return string[]
 
49
  */
50
  public function getEventKeys() {
51
  return array_keys( $this->getEvents() );
52
  }
53
 
54
- /**
55
- * @param string $sEventKey
56
- * @return bool
57
- */
58
- public function isSupportedEvent( $sEventKey ) {
59
- return in_array( $sEventKey, $this->getEventKeys() );
60
  }
61
 
62
- /**
63
- * @param array[] $aEvents
64
- * @return array[]
65
- */
66
- protected function buildEvents( $aEvents ) {
67
- $aDefaults = [
68
  'cat' => 1,
69
  'stat' => true,
70
  'audit' => true,
@@ -73,10 +78,10 @@ class EventsService {
73
  'audit_multiple' => false, // allow multiple audit entries in the same request
74
  'suppress_offense' => false, // events that normally trigger offense can be forcefully suppressed
75
  ];
76
- foreach ( $aEvents as $sEventKey => $aEvt ) {
77
- $aEvents[ $sEventKey ] = array_merge( $aDefaults, $aEvt );
78
- $aEvents[ $sEventKey ][ 'key' ] = $sEventKey;
79
  }
80
- return $aEvents;
81
  }
82
  }
14
  private $aEvents;
15
 
16
  /**
17
+ * @param string $event
18
+ * @param array $meta
19
  * @return $this
20
  */
21
+ public function fireEvent( $event, $meta = [] ) {
22
+ if ( $this->isSupportedEvent( $event ) ) {
23
+ do_action( $this->getCon()->prefix( 'event' ), $event, $meta );
24
  }
25
  return $this;
26
  }
30
  */
31
  public function getEvents() :array {
32
  if ( empty( $this->aEvents ) ) {
33
+ $events = [];
34
+ foreach ( $this->getCon()->modules as $mod ) {
35
+ $events = array_merge(
36
+ $events,
37
+ array_map(
38
+ function ( $evt ) use ( $mod ) {
39
+ $evt[ 'context' ] = $mod->getSlug();
40
+ return $evt;
41
+ },
42
+ is_array( $mod->getDef( 'events' ) ) ? $mod->getDef( 'events' ) : []
43
+ )
44
+ );
45
+ }
46
+ $this->aEvents = $this->buildEvents( $events );
47
  }
48
  return $this->aEvents;
49
  }
50
 
51
  /**
52
+ * @param string $eventKey
53
  * @return array|null
54
  */
55
+ public function getEventDef( string $eventKey ) {
56
+ return $this->isSupportedEvent( $eventKey ) ? $this->getEvents()[ $eventKey ] : null;
57
  }
58
 
59
  /**
60
  * @return string[]
61
+ * @deprecated 10.1
62
  */
63
  public function getEventKeys() {
64
  return array_keys( $this->getEvents() );
65
  }
66
 
67
+ public function isSupportedEvent( string $eventKey ) :bool {
68
+ return in_array( $eventKey, array_keys( $this->getEvents() ) );
 
 
 
 
69
  }
70
 
71
+ private function buildEvents( array $events ) :array {
72
+ $defaults = [
 
 
 
 
73
  'cat' => 1,
74
  'stat' => true,
75
  'audit' => true,
78
  'audit_multiple' => false, // allow multiple audit entries in the same request
79
  'suppress_offense' => false, // events that normally trigger offense can be forcefully suppressed
80
  ];
81
+ foreach ( $events as $eventKey => $evt ) {
82
+ $events[ $eventKey ] = array_merge( $defaults, $evt );
83
+ $events[ $eventKey ][ 'key' ] = $eventKey;
84
  }
85
+ return $events;
86
  }
87
  }
src/lib/src/Modules/Events/Lib/Reports/KeyStats.php CHANGED
@@ -14,12 +14,12 @@ class KeyStats extends BaseReporter {
14
  public function build() {
15
  $aAlerts = [];
16
 
17
- /** @var \ICWP_WPSF_FeatureHandler_Events $oMod */
18
- $oMod = $this->getMod();
19
  /** @var DBEvents\Select $oSelEvts */
20
- $oSelEvts = $oMod->getDbHandler_Events()->getQuerySelector();
21
  /** @var Events\Strings $oStrings */
22
- $oStrings = $oMod->getStrings();
23
 
24
  $aEventKeys = [
25
  'ip_offense',
14
  public function build() {
15
  $aAlerts = [];
16
 
17
+ /** @var Events\ModCon $mod */
18
+ $mod = $this->getMod();
19
  /** @var DBEvents\Select $oSelEvts */
20
+ $oSelEvts = $mod->getDbHandler_Events()->getQuerySelector();
21
  /** @var Events\Strings $oStrings */
22
+ $oStrings = $mod->getStrings();
23
 
24
  $aEventKeys = [
25
  'ip_offense',
src/lib/src/Modules/Events/Lib/Reports/ScanRepairs.php CHANGED
@@ -15,12 +15,12 @@ class ScanRepairs extends BaseReporter {
15
  public function build() {
16
  $aAlerts = [];
17
 
18
- /** @var \ICWP_WPSF_FeatureHandler_Events $oMod */
19
- $oMod = $this->getMod();
20
  /** @var DBEvents\Select $oSelEvts */
21
- $oSelEvts = $oMod->getDbHandler_Events()->getQuerySelector();
22
  /** @var Events\Strings $oStrings */
23
- $oStrings = $oMod->getStrings();
24
 
25
  $oRep = $this->getReport();
26
 
15
  public function build() {
16
  $aAlerts = [];
17
 
18
+ /** @var Events\ModCon $mod */
19
+ $mod = $this->getMod();
20
  /** @var DBEvents\Select $oSelEvts */
21
+ $oSelEvts = $mod->getDbHandler_Events()->getQuerySelector();
22
  /** @var Events\Strings $oStrings */
23
+ $oStrings = $mod->getStrings();
24
 
25
  $oRep = $this->getReport();
26
 
src/lib/src/Modules/Events/Lib/StatsWriter.php CHANGED
@@ -16,12 +16,12 @@ class StatsWriter extends EventsListener {
16
  private $aEventStats;
17
 
18
  /**
19
- * @param string $sEvent
20
  * @param array $aMeta
21
  */
22
- protected function captureEvent( $sEvent, $aMeta = [] ) {
23
  $aStats = $this->getEventStats();
24
- $aStats[ $sEvent ] = isset( $aMeta[ 'ts' ] ) ? $aMeta[ 'ts' ] : Services::Request()->ts();
25
  $this->setEventStats( $aStats );
26
  }
27
 
16
  private $aEventStats;
17
 
18
  /**
19
+ * @param string $evt
20
  * @param array $aMeta
21
  */
22
+ protected function captureEvent( $evt, $aMeta = [] ) {
23
  $aStats = $this->getEventStats();
24
+ $aStats[ $evt ] = isset( $aMeta[ 'ts' ] ) ? $aMeta[ 'ts' ] : Services::Request()->ts();
25
  $this->setEventStats( $aStats );
26
  }
27
 
src/lib/src/Modules/Events/ModCon.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Events;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
+
8
+ class ModCon extends BaseShield\ModCon {
9
+
10
+ public function getDbHandler_Events() :Shield\Databases\Events\Handler {
11
+ return $this->getDbH( 'events' );
12
+ }
13
+
14
+ /**
15
+ * @return bool
16
+ * @throws \Exception
17
+ */
18
+ protected function isReadyToExecute() :bool {
19
+ return ( $this->getDbHandler_Events() instanceof Shield\Databases\Events\Handler )
20
+ && $this->getDbHandler_Events()->isReady()
21
+ && parent::isReadyToExecute();
22
+ }
23
+ }
src/lib/src/Modules/Events/Options.php CHANGED
@@ -1,16 +1,9 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Events;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
 
7
- class Options extends Base\ShieldOptions {
8
 
9
- /**
10
- * @return string
11
- * @deprecated 10.0
12
- */
13
- public function getDbTable_Events() :string {
14
- return $this->getCon()->prefixOption( $this->getDef( 'events_table_name' ) );
15
- }
16
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Events;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
 
7
+ class Options extends BaseShield\Options {
8
 
 
 
 
 
 
 
 
9
  }
src/lib/src/Modules/Events/Processor.php ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
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 {
10
+
11
+ /**
12
+ * @var Events\Lib\StatsWriter
13
+ */
14
+ private $oStatsWriter;
15
+
16
+ protected function run() {
17
+ $this->loadStatsWriter()->setIfCommit( true );
18
+ add_action( $this->getCon()->prefix( 'dashboard_widget_content' ), [ $this, 'statsWidget' ], 10 );
19
+ }
20
+
21
+ /**
22
+ * @return Events\Lib\StatsWriter
23
+ */
24
+ public function loadStatsWriter() {
25
+ if ( !isset( $this->oStatsWriter ) ) {
26
+ /** @var ModCon $mod */
27
+ $mod = $this->getMod();
28
+ $this->oStatsWriter = ( new Events\Lib\StatsWriter( $this->getCon() ) )
29
+ ->setDbHandler( $mod->getDbHandler_Events() );
30
+ }
31
+ return $this->oStatsWriter;
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'
48
+ ] )
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
+
87
+ public function runDailyCron() {
88
+ ( new Events\Consolidate\ConsolidateAllEvents() )
89
+ ->setMod( $this->getMod() )
90
+ ->run();
91
+ }
92
+ }
src/lib/src/Modules/Events/Reporting.php CHANGED
@@ -2,10 +2,10 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Events;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\BaseReporting;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Events\Lib\Reports;
7
 
8
- class Reporting extends BaseReporting {
9
 
10
  /**
11
  * @inheritDoc
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Events;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Events\Lib\Reports;
7
 
8
+ class Reporting extends Base\Reporting {
9
 
10
  /**
11
  * @inheritDoc
src/lib/src/Modules/Events/Strings.php CHANGED
@@ -20,7 +20,7 @@ class Strings extends Base\Strings {
20
  * @return string[]
21
  */
22
  public function getEventNames( $bAuto = true ) {
23
- $aNames = [
24
  'test_cron_run' => __( 'Test Cron Run', 'wp-simple-firewall' ),
25
  'import_notify_sent' => __( 'Import Notify Sent', 'wp-simple-firewall' ),
26
  'import_notify_received' => __( 'Import Notify Received', 'wp-simple-firewall' ),
@@ -237,9 +237,11 @@ class Strings extends Base\Strings {
237
  'session_terminate' => __( 'Session Terminated', 'wp-simple-firewall' ),
238
  'plugin_activated' => __( 'Plugin Activated', 'wp-simple-firewall' ),
239
  'plugin_deactivated' => __( 'Plugin Deactivated', 'wp-simple-firewall' ),
 
240
  'plugin_file_edited' => __( 'Plugin File Edited', 'wp-simple-firewall' ),
241
  'theme_activated' => __( 'Theme Activated', 'wp-simple-firewall' ),
242
  'theme_file_edited' => __( 'Theme File Edited', 'wp-simple-firewall' ),
 
243
  'core_updated' => __( 'WP Core Updated', 'wp-simple-firewall' ),
244
  'permalinks_structure' => __( 'Permalinks Updated', 'wp-simple-firewall' ),
245
  'post_deleted' => __( 'Post Deleted', 'wp-simple-firewall' ),
@@ -260,13 +262,13 @@ class Strings extends Base\Strings {
260
  ];
261
 
262
  if ( $bAuto ) {
263
- foreach ( $aNames as $sKey => $sName ) {
264
- if ( empty( $sName ) ) {
265
- $aNames[ $sKey ] = ucwords( str_replace( '_', ' ', $sKey ) );
266
  }
267
  }
268
  }
269
 
270
- return $aNames;
271
  }
272
  }
20
  * @return string[]
21
  */
22
  public function getEventNames( $bAuto = true ) {
23
+ $names = [
24
  'test_cron_run' => __( 'Test Cron Run', 'wp-simple-firewall' ),
25
  'import_notify_sent' => __( 'Import Notify Sent', 'wp-simple-firewall' ),
26
  'import_notify_received' => __( 'Import Notify Received', 'wp-simple-firewall' ),
237
  'session_terminate' => __( 'Session Terminated', 'wp-simple-firewall' ),
238
  'plugin_activated' => __( 'Plugin Activated', 'wp-simple-firewall' ),
239
  'plugin_deactivated' => __( 'Plugin Deactivated', 'wp-simple-firewall' ),
240
+ 'plugin_upgraded' => __( 'Plugin Upgraded', 'wp-simple-firewall' ),
241
  'plugin_file_edited' => __( 'Plugin File Edited', 'wp-simple-firewall' ),
242
  'theme_activated' => __( 'Theme Activated', 'wp-simple-firewall' ),
243
  'theme_file_edited' => __( 'Theme File Edited', 'wp-simple-firewall' ),
244
+ 'theme_upgraded' => __( 'Theme Upgraded', 'wp-simple-firewall' ),
245
  'core_updated' => __( 'WP Core Updated', 'wp-simple-firewall' ),
246
  'permalinks_structure' => __( 'Permalinks Updated', 'wp-simple-firewall' ),
247
  'post_deleted' => __( 'Post Deleted', 'wp-simple-firewall' ),
262
  ];
263
 
264
  if ( $bAuto ) {
265
+ foreach ( $names as $key => $name ) {
266
+ if ( empty( $name ) ) {
267
+ $names[ $key ] = ucwords( str_replace( '_', ' ', $key ) );
268
  }
269
  }
270
  }
271
 
272
+ return $names;
273
  }
274
  }
src/lib/src/Modules/Firewall/Insights/OverviewCards.php CHANGED
@@ -3,11 +3,12 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Insights;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
 
6
 
7
  class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
8
 
9
  public function build() :array {
10
- /** @var \ICWP_WPSF_FeatureHandler_Firewall $mod */
11
  $mod = $this->getMod();
12
  /** @var \FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Options $opts */
13
  $opts = $this->getOptions();
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Insights;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\ModCon;
7
 
8
  class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
9
 
10
  public function build() :array {
11
+ /** @var ModCon $mod */
12
  $mod = $this->getMod();
13
  /** @var \FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Options $opts */
14
  $opts = $this->getOptions();
src/lib/src/Modules/Firewall/ModCon.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
+
8
+ class ModCon extends BaseShield\ModCon {
9
+
10
+ /**
11
+ * @param string $sParam
12
+ * @param string $sPage
13
+ */
14
+ public function addParamToWhitelist( $sParam, $sPage = '*' ) {
15
+ /** @var Options $opts */
16
+ $opts = $this->getOptions();
17
+
18
+ if ( empty( $sPage ) ) {
19
+ $sPage = '*';
20
+ }
21
+
22
+ $aW = $opts->getCustomWhitelist();
23
+ $aParams = isset( $aW[ $sPage ] ) ? $aW[ $sPage ] : [];
24
+ $aParams[] = $sParam;
25
+ natsort( $aParams );
26
+ $aW[ $sPage ] = array_unique( $aParams );
27
+
28
+ $opts->setOpt( 'page_params_whitelist', $aW );
29
+ }
30
+
31
+ public function getBlockResponse() :string {
32
+ $response = $this->getOptions()->getOpt( 'block_response', '' );
33
+ return !empty( $response ) ? $response : 'redirect_die_message'; // TODO: use default
34
+ }
35
+
36
+ public function getTextOptDefault( string $key ) :string {
37
+
38
+ switch ( $key ) {
39
+ case 'text_firewalldie':
40
+ $text = sprintf(
41
+ __( "You were blocked by the %s.", 'wp-simple-firewall' ),
42
+ '<a href="https://wordpress.org/plugins/wp-simple-firewall/" target="_blank">'.$this->getCon()
43
+ ->getHumanName().'</a>'
44
+ );
45
+ break;
46
+
47
+ default:
48
+ $text = parent::getTextOptDefault( $key );
49
+ break;
50
+ }
51
+ return $text;
52
+ }
53
+ }
src/lib/src/Modules/Firewall/Options.php CHANGED
@@ -2,9 +2,9 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
 
7
- class Options extends Base\ShieldOptions {
8
 
9
  public function getCustomWhitelist() :array {
10
  $aW = $this->getOpt( 'page_params_whitelist', [] );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
 
7
+ class Options extends BaseShield\Options {
8
 
9
  public function getCustomWhitelist() :array {
10
  $aW = $this->getOpt( 'page_params_whitelist', [] );
src/lib/src/Modules/Firewall/Processor.php ADDED
@@ -0,0 +1,435 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class Processor extends BaseShield\Processor {
9
+
10
+ /**
11
+ * @var array
12
+ */
13
+ private $aDieMessage;
14
+
15
+ /**
16
+ * @var array
17
+ */
18
+ protected $aPatterns;
19
+
20
+ /**
21
+ * @var array
22
+ */
23
+ private $aAuditBlockMessage;
24
+
25
+ /**
26
+ * After any parameter whitelisting has been accounted for
27
+ *
28
+ * @var array
29
+ */
30
+ private $params;
31
+
32
+ protected function run() {
33
+ if ( $this->getIfPerformFirewallScan() && $this->getIfDoFirewallBlock() ) {
34
+ // Hooked here to ensure "plugins_loaded" has completely finished as some mailers aren't init'd.
35
+ add_action( 'init', function () {
36
+ $this->doPreFirewallBlock();
37
+ $this->doFirewallBlock();
38
+ }, 0 );
39
+ }
40
+ }
41
+
42
+ private function getIfDoFirewallBlock() :bool {
43
+ return !$this->isVisitorRequestPermitted();
44
+ }
45
+
46
+ private function getIfPerformFirewallScan() :bool {
47
+ $bPerformScan = true;
48
+ /** @var Options $opts */
49
+ $opts = $this->getOptions();
50
+
51
+ $sPath = Services::Request()->getPath();
52
+
53
+ if ( count( $this->getRawRequestParams() ) == 0 ) {
54
+ $bPerformScan = false;
55
+ }
56
+ elseif ( empty( $sPath ) ) {
57
+ $this->getCon()->fireEvent( 'firewall_skip' );
58
+ $bPerformScan = false;
59
+ }
60
+ elseif ( count( $this->getParamsToCheck() ) == 0 ) {
61
+ $bPerformScan = false;
62
+ }
63
+ // TODO: are we calling is_super_admin() too early?
64
+ elseif ( $opts->isIgnoreAdmin() && is_super_admin() ) {
65
+ $bPerformScan = false;
66
+ }
67
+
68
+ return $bPerformScan;
69
+ }
70
+
71
+ private function isVisitorRequestPermitted() :bool {
72
+ $opts = $this->getOptions();
73
+
74
+ $bRequestIsPermitted = true;
75
+ if ( $bRequestIsPermitted && $opts->isOpt( 'block_dir_traversal', 'Y' ) ) {
76
+ $bRequestIsPermitted = $this->doPassCheck( 'dirtraversal' );
77
+ }
78
+ if ( $bRequestIsPermitted && $opts->isOpt( 'block_sql_queries', 'Y' ) ) {
79
+ $bRequestIsPermitted = $this->doPassCheck( 'sqlqueries' );
80
+ }
81
+ if ( $bRequestIsPermitted && $opts->isOpt( 'block_wordpress_terms', 'Y' ) ) {
82
+ $bRequestIsPermitted = $this->doPassCheck( 'wpterms' );
83
+ }
84
+ if ( $bRequestIsPermitted && $opts->isOpt( 'block_field_truncation', 'Y' ) ) {
85
+ $bRequestIsPermitted = $this->doPassCheck( 'fieldtruncation' );
86
+ }
87
+ if ( $bRequestIsPermitted && $opts->isOpt( 'block_php_code', 'Y' ) ) {
88
+ $bRequestIsPermitted = $this->doPassCheck( 'phpcode' );
89
+ }
90
+ if ( $bRequestIsPermitted && $opts->isOpt( 'block_leading_schema', 'Y' ) ) {
91
+ $bRequestIsPermitted = $this->doPassCheck( 'schema' );
92
+ }
93
+ if ( $bRequestIsPermitted && $opts->isOpt( 'block_aggressive', 'Y' ) ) {
94
+ $bRequestIsPermitted = $this->doPassCheck( 'aggressive' );
95
+ }
96
+ if ( $bRequestIsPermitted && $opts->isOpt( 'block_exe_file_uploads', 'Y' ) ) {
97
+ $bRequestIsPermitted = $this->doPassCheckBlockExeFileUploads();
98
+ }
99
+ return $bRequestIsPermitted;
100
+ }
101
+
102
+ protected function doPassCheckBlockExeFileUploads() :bool {
103
+ /** @var ModCon $mod */
104
+ $mod = $this->getMod();
105
+
106
+ $sKey = 'exefile';
107
+ $bFAIL = false;
108
+ if ( isset( $_FILES ) && !empty( $_FILES ) ) {
109
+ $aFileNames = [];
110
+ foreach ( $_FILES as $aFile ) {
111
+ if ( !empty( $aFile[ 'name' ] ) ) {
112
+ $aFileNames[] = $aFile[ 'name' ];
113
+ }
114
+ }
115
+ $aMatchTerms = $this->getFirewallPatterns( 'exefile' );
116
+ if ( isset( $aMatchTerms[ 'regex' ] ) && is_array( $aMatchTerms[ 'regex' ] ) ) {
117
+
118
+ $aMatchTerms[ 'regex' ] = array_map(
119
+ function ( $term ) {
120
+ return '/'.$term.'/i';
121
+ },
122
+ $aMatchTerms[ 'regex' ]
123
+ );
124
+ foreach ( $aMatchTerms[ 'regex' ] as $sTerm ) {
125
+ foreach ( $aFileNames as $sParam => $mValue ) {
126
+ if ( is_scalar( $mValue ) && preg_match( $sTerm, (string)$mValue ) ) {
127
+ $bFAIL = true;
128
+ break( 2 );
129
+ }
130
+ }
131
+ }
132
+ }
133
+ if ( $bFAIL ) {
134
+ $this->getCon()
135
+ ->fireEvent(
136
+ 'block_exefile',
137
+ [
138
+ 'audit' => [
139
+ 'blockresponse' => $mod->getBlockResponse(),
140
+ 'blockkey' => $sKey,
141
+ ]
142
+ ]
143
+
144
+ );
145
+ }
146
+ }
147
+ return !$bFAIL;
148
+ }
149
+
150
+ /**
151
+ * Returns false when check fails - that is, it should be blocked by the firewall.
152
+ *
153
+ * @param string $sBlockKey
154
+ * @return bool
155
+ */
156
+ private function doPassCheck( string $sBlockKey ) :bool {
157
+ /** @var ModCon $mod */
158
+ $mod = $this->getMod();
159
+
160
+ $aMatchTerms = $this->getFirewallPatterns( $sBlockKey );
161
+ $aParamValues = $this->getParamsToCheck();
162
+ if ( empty( $aMatchTerms ) || empty( $aParamValues ) ) {
163
+ return true;
164
+ }
165
+
166
+ $sParam = '';
167
+ $mValue = '';
168
+
169
+ $bFAIL = false;
170
+ if ( isset( $aMatchTerms[ 'simple' ] ) && is_array( $aMatchTerms[ 'simple' ] ) ) {
171
+
172
+ foreach ( $aMatchTerms[ 'simple' ] as $sTerm ) {
173
+ foreach ( $aParamValues as $sParam => $mValue ) {
174
+ if ( is_scalar( $mValue ) && ( stripos( (string)$mValue, $sTerm ) !== false ) ) {
175
+ $bFAIL = true;
176
+ break( 2 );
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ if ( !$bFAIL && isset( $aMatchTerms[ 'regex' ] ) && is_array( $aMatchTerms[ 'regex' ] ) ) {
183
+ $aMatchTerms[ 'regex' ] = array_map(
184
+ function ( $term ) {
185
+ return '/'.$term.'/i';
186
+ },
187
+ $aMatchTerms[ 'regex' ]
188
+ );
189
+ foreach ( $aMatchTerms[ 'regex' ] as $sTerm ) {
190
+ foreach ( $aParamValues as $sParam => $mValue ) {
191
+ if ( is_scalar( $mValue ) && preg_match( $sTerm, (string)$mValue ) ) {
192
+ $sParam = sanitize_text_field( $sParam );
193
+ $mValue = sanitize_text_field( $mValue );
194
+ $bFAIL = true;
195
+ break( 2 );
196
+ }
197
+ }
198
+ }
199
+ }
200
+
201
+ if ( $bFAIL ) {
202
+ $this->addToFirewallDieMessage( __( "Something in the URL, Form or Cookie data wasn't appropriate.", 'wp-simple-firewall' ) );
203
+
204
+ $this->aAuditBlockMessage = [
205
+ sprintf( __( 'Firewall Trigger: %s.', 'wp-simple-firewall' ), $this->getFirewallBlockKeyName( $sBlockKey ) ),
206
+ __( 'Page parameter failed firewall check.', 'wp-simple-firewall' ),
207
+ sprintf( __( 'The offending parameter was "%s" with a value of "%s".', 'wp-simple-firewall' ), $sParam, $mValue )
208
+ ];
209
+
210
+ $this->getCon()
211
+ ->fireEvent(
212
+ 'blockparam_'.$sBlockKey,
213
+ [
214
+ 'audit' => [
215
+ 'param' => $sParam,
216
+ 'val' => $mValue,
217
+ 'blockresponse' => $mod->getBlockResponse(),
218
+ 'blockkey' => $sBlockKey,
219
+ ]
220
+ ]
221
+ );
222
+ }
223
+
224
+ return !$bFAIL;
225
+ }
226
+
227
+ /**
228
+ * @param string $sKey
229
+ * @return array|null
230
+ */
231
+ protected function getFirewallPatterns( $sKey = null ) {
232
+ if ( !isset( $this->aPatterns ) ) {
233
+ $this->aPatterns = $this->getOptions()->getDef( 'firewall_patterns' );
234
+ }
235
+ if ( !empty( $sKey ) ) {
236
+ return isset( $this->aPatterns[ $sKey ] ) ? $this->aPatterns[ $sKey ] : null;
237
+ }
238
+ return $this->aPatterns;
239
+ }
240
+
241
+ private function doPreFirewallBlock() {
242
+ /** @var Options $opts */
243
+ $opts = $this->getOptions();
244
+ if ( $opts->isSendBlockEmail() ) {
245
+ $recipient = $this->getMod()->getPluginReportEmail();
246
+ $this->getCon()->fireEvent(
247
+ $this->sendBlockEmail( $recipient ) ? 'fw_email_success' : 'fw_email_fail',
248
+ [ 'audit' => [ 'recipient' => $recipient ] ]
249
+ );
250
+ }
251
+ $this->getCon()->fireEvent( 'firewall_block' );
252
+ }
253
+
254
+ private function doFirewallBlock() {
255
+ /** @var ModCon $mod */
256
+ $mod = $this->getMod();
257
+
258
+ switch ( $mod->getBlockResponse() ) {
259
+ case 'redirect_die':
260
+ Services::WpGeneral()->wpDie( 'Firewall Triggered' );
261
+ break;
262
+ case 'redirect_die_message':
263
+ Services::WpGeneral()->wpDie( $this->getFirewallDieMessageForDisplay() );
264
+ break;
265
+ case 'redirect_home':
266
+ Services::Response()->redirectToHome();
267
+ break;
268
+ case 'redirect_404':
269
+ header( 'Cache-Control: no-store, no-cache' );
270
+ Services::WpGeneral()->turnOffCache();
271
+ Services::Response()->sendApache404();
272
+ break;
273
+ default:
274
+ break;
275
+ }
276
+ die();
277
+ }
278
+
279
+ protected function getFirewallDieMessage() :array {
280
+ if ( !isset( $this->aDieMessage ) || !is_array( $this->aDieMessage ) ) {
281
+ $this->aDieMessage = [ $this->getMod()->getTextOpt( 'text_firewalldie' ) ];
282
+ }
283
+ return $this->aDieMessage;
284
+ }
285
+
286
+ protected function getFirewallDieMessageForDisplay() :string {
287
+ $messages = apply_filters(
288
+ $this->getCon()->prefix( 'firewall_die_message' ),
289
+ $this->getFirewallDieMessage()
290
+ );
291
+ return implode( ' ', is_array( $messages ) ? $messages : [] );
292
+ }
293
+
294
+ /**
295
+ * @param string $sMessagePart
296
+ * @return $this
297
+ */
298
+ protected function addToFirewallDieMessage( $sMessagePart ) {
299
+ $aMessages = $this->getFirewallDieMessage();
300
+ $aMessages[] = $sMessagePart;
301
+ $this->aDieMessage = $aMessages;
302
+ return $this;
303
+ }
304
+
305
+ private function getParamsToCheck() :array {
306
+ if ( isset( $this->params ) ) {
307
+ return $this->params;
308
+ }
309
+
310
+ /** @var Options $opts */
311
+ $opts = $this->getOptions();
312
+
313
+ $this->params = $this->getRawRequestParams();
314
+ $aWhitelist = Services::DataManipulation()
315
+ ->mergeArraysRecursive( $opts->getDef( 'default_whitelist' ), $opts->getCustomWhitelist() );
316
+
317
+ // first we remove globally whitelisted request parameters
318
+ if ( !empty( $aWhitelist[ '*' ] ) && is_array( $aWhitelist[ '*' ] ) ) {
319
+ foreach ( $aWhitelist[ '*' ] as $sWhitelistParam ) {
320
+
321
+ if ( preg_match( '#^/.+/$#', $sWhitelistParam ) ) {
322
+ foreach ( array_keys( $this->params ) as $sParamKey ) {
323
+ if ( preg_match( $sWhitelistParam, $sParamKey ) ) {
324
+ unset( $this->params[ $sParamKey ] );
325
+ }
326
+ }
327
+ }
328
+ elseif ( isset( $this->params[ $sWhitelistParam ] ) ) {
329
+ unset( $this->params[ $sWhitelistParam ] );
330
+ }
331
+ }
332
+ }
333
+
334
+ // If the parameters to check is already empty, we return it to save any further processing.
335
+ if ( empty( $this->params ) ) {
336
+ return $this->params;
337
+ }
338
+
339
+ // Now we run through the list of whitelist pages
340
+ $sRequestPage = Services::Request()->getPath();
341
+ foreach ( $aWhitelist as $sWhitelistPageName => $aWhitelistPageParams ) {
342
+
343
+ // if the page is white listed
344
+ if ( strpos( $sRequestPage, $sWhitelistPageName ) !== false ) {
345
+
346
+ // if the page has no particular parameters specified there is nothing to check since the whole page is white listed.
347
+ if ( empty( $aWhitelistPageParams ) ) {
348
+ $this->params = [];
349
+ }
350
+ else {
351
+ // Otherwise we run through any whitelisted parameters and remove them.
352
+ foreach ( $aWhitelistPageParams as $sWhitelistParam ) {
353
+ if ( array_key_exists( $sWhitelistParam, $this->params ) ) {
354
+ unset( $this->params[ $sWhitelistParam ] );
355
+ }
356
+ }
357
+ }
358
+ break;
359
+ }
360
+ }
361
+
362
+ return $this->params;
363
+ }
364
+
365
+ private function getRawRequestParams() :array {
366
+ return Services::Request()->getRawRequestParams( $this->getOptions()->isOpt( 'include_cookie_checks', 'Y' ) );
367
+ }
368
+
369
+ private function sendBlockEmail( string $recipient ) :bool {
370
+ $success = false;
371
+ if ( !empty( $this->aAuditBlockMessage ) ) {
372
+ $sIp = Services::IP()->getRequestIp();
373
+ $message = array_merge(
374
+ [
375
+ sprintf( __( '%s has blocked a page visit to your site.', 'wp-simple-firewall' ), $this->getCon()
376
+ ->getHumanName() ),
377
+ __( 'Log details for this visitor are below:', 'wp-simple-firewall' ),
378
+ '- '.sprintf( '%s: %s', __( 'IP Address', 'wp-simple-firewall' ), $sIp ),
379
+ ],
380
+ array_map(
381
+ function ( $sLine ) {
382
+ return '- '.$sLine;
383
+ },
384
+ $this->aAuditBlockMessage
385
+ ),
386
+ [
387
+ '',
388
+ sprintf( __( 'You can look up the offending IP Address here: %s', 'wp-simple-firewall' ), 'http://ip-lookup.net/?ip='.$sIp )
389
+ ]
390
+ );
391
+
392
+ $success = $this->getMod()
393
+ ->getEmailProcessor()
394
+ ->sendEmailWithWrap(
395
+ $recipient,
396
+ __( 'Firewall Block Alert', 'wp-simple-firewall' ),
397
+ $message
398
+ );
399
+ }
400
+ return $success;
401
+ }
402
+
403
+ private function getFirewallBlockKeyName( string $blockKey ) :string {
404
+ switch ( $blockKey ) {
405
+ case 'dirtraversal':
406
+ $name = __( 'Directory Traversal', 'wp-simple-firewall' );
407
+ break;
408
+ case 'wpterms':
409
+ $name = __( 'WordPress Terms', 'wp-simple-firewall' );
410
+ break;
411
+ case 'fieldtruncation':
412
+ $name = __( 'Field Truncation', 'wp-simple-firewall' );
413
+ break;
414
+ case 'sqlqueries':
415
+ $name = __( 'SQL Queries', 'wp-simple-firewall' );
416
+ break;
417
+ case 'exefile':
418
+ $name = __( 'EXE File Uploads', 'wp-simple-firewall' );
419
+ break;
420
+ case 'schema':
421
+ $name = __( 'Leading Schema', 'wp-simple-firewall' );
422
+ break;
423
+ case 'phpcode':
424
+ $name = __( 'PHP Code', 'wp-simple-firewall' );
425
+ break;
426
+ case 'aggressive':
427
+ $name = __( 'Aggressive Rules', 'wp-simple-firewall' );
428
+ break;
429
+ default:
430
+ $name = __( 'Unknown Rules', 'wp-simple-firewall' );
431
+ break;
432
+ }
433
+ return $name;
434
+ }
435
+ }
src/lib/src/Modules/Firewall/Strings.php CHANGED
@@ -183,7 +183,7 @@ class Strings extends Base\Strings {
183
  * @return string[][]
184
  */
185
  protected function getAuditMessages() :array {
186
- /** @var \ICWP_WPSF_FeatureHandler_Firewall $mod */
187
  $mod = $this->getMod();
188
 
189
  $aMsgs = [
183
  * @return string[][]
184
  */
185
  protected function getAuditMessages() :array {
186
+ /** @var ModCon $mod */
187
  $mod = $this->getMod();
188
 
189
  $aMsgs = [
src/lib/src/Modules/Firewall/UI.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
 
7
- class UI extends Base\ShieldUI {
8
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
 
7
+ class UI extends BaseShield\UI {
8
  }
src/lib/src/Modules/HackGuard/AjaxHandler.php CHANGED
@@ -8,7 +8,7 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan;
9
  use FernleafSystems\Wordpress\Services\Services;
10
 
11
- class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
12
 
13
  protected function processAjaxAction( string $action ) :array {
14
 
@@ -62,12 +62,9 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
62
  return $aResponse;
63
  }
64
 
65
- /**
66
- * @return array
67
- */
68
- private function ajaxExec_BuildTableScan() {
69
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
70
- $oMod = $this->getMod();
71
 
72
  $sScanSlug = Services::Request()->post( 'fScan' );
73
  switch ( $sScanSlug ) {
@@ -109,8 +106,8 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
109
  }
110
  else {
111
  $sHtml = $oTableBuilder
112
- ->setMod( $oMod )
113
- ->setDbHandler( $oMod->getDbHandler_ScanResults() )
114
  ->render();
115
  }
116
 
@@ -120,13 +117,10 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
120
  ];
121
  }
122
 
123
- /**
124
- * @return array
125
- */
126
- private function ajaxExec_FileLockerShowDiff() {
127
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
128
- $oMod = $this->getMod();
129
- $oFLCon = $oMod->getFileLocker();
130
  $FS = Services::WpFs();
131
 
132
  $nRID = Services::Request()->post( 'rid' );
@@ -187,7 +181,7 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
187
  $aData[ 'vars' ][ 'change_detected_at' ] = $oCarb->setTimestamp( $oLock->detected_at )->diffForHumans();
188
  $aData[ 'vars' ][ 'file_size_locked' ] = Shield\Utilities\Tool\FormatBytes::Format( strlen(
189
  ( new FileLocker\Ops\ReadOriginalFileContent() )
190
- ->setMod( $oMod )
191
  ->run( $oLock )
192
  ), 3 );
193
  $aData[ 'vars' ][ 'file_size_modified' ] = $FS->exists( $oLock->file ) ?
@@ -212,10 +206,7 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
212
  ];
213
  }
214
 
215
- /**
216
- * @return array
217
- */
218
- private function ajaxExec_FileLockerFileAction() {
219
  $oReq = Services::Request();
220
  $bSuccess = false;
221
 
@@ -242,45 +233,42 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
242
  ];
243
  }
244
 
245
- /**
246
- * @return array
247
- */
248
- private function ajaxExec_PluginReinstall() {
249
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
250
- $oMod = $this->getMod();
251
- $oReq = Services::Request();
252
 
253
- $bReinstall = (bool)$oReq->post( 'reinstall' );
254
- $bActivate = (bool)$oReq->post( 'activate' );
255
- $sFile = sanitize_text_field( wp_unslash( $oReq->post( 'file' ) ) );
256
 
257
  if ( $bReinstall ) {
258
- /** @var Scan\Controller\Ptg $oPtgScan */
259
- $oPtgScan = $oMod->getScanCon( 'ptg' );
260
- $bActivate = $oPtgScan->actionPluginReinstall( $sFile );
261
  }
262
 
263
  if ( $bActivate ) {
264
- Services::WpPlugins()->activate( $sFile );
265
  }
266
 
267
  return [ 'success' => true ];
268
  }
269
 
270
  /**
271
- * @param string $sAction
272
  * @param bool $bIsBulkAction
273
  * @return array
274
  */
275
- private function ajaxExec_ScanItemAction( $sAction, $bIsBulkAction = false ) {
276
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
277
- $oMod = $this->getMod();
278
 
279
- $bSuccess = false;
280
 
281
- if ( $sAction == 'download' ) {
282
  // A special case since this action is handled using Javascript
283
- $bSuccess = true;
284
  $sMessage = __( 'File download has started.', 'wp-simple-firewall' );
285
  }
286
  else {
@@ -301,20 +289,20 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
301
  $aScanSlugs = [];
302
  $aSuccessfulItems = [];
303
  foreach ( $aItemIdsToProcess as $nId ) {
304
- /** @var Shield\Databases\Scanner\EntryVO $oEntry */
305
- $oEntry = $oMod->getDbHandler_ScanResults()
306
- ->getQuerySelector()
307
- ->byId( $nId );
308
- if ( $oEntry instanceof Shield\Databases\Scanner\EntryVO ) {
309
- $aScanSlugs[] = $oEntry->scan;
310
- if ( $oMod->getScanCon( $oEntry->scan )->executeItemAction( $nId, $sAction ) ) {
311
  $aSuccessfulItems[] = $nId;
312
  }
313
  }
314
  }
315
 
316
  if ( count( $aSuccessfulItems ) === count( $aItemIdsToProcess ) ) {
317
- $bSuccess = true;
318
  $sMessage = __( 'Action successful.' );
319
  }
320
  else {
@@ -322,12 +310,12 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
322
  }
323
 
324
  // We don't rescan for ignores.
325
- if ( in_array( $sAction, [ 'ignore' ] ) ) {
326
  $sMessage .= ' '.__( 'Reloading', 'wp-simple-firewall' ).' ...';
327
  }
328
  else {
329
  // rescan
330
- $oMod->getScanQueueController()->startScans( $aScanSlugs );
331
  $sMessage .= ' '.__( 'Rescanning', 'wp-simple-firewall' ).' ...';
332
  }
333
  }
@@ -338,24 +326,21 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
338
  }
339
 
340
  return [
341
- 'success' => $bSuccess,
342
- 'page_reload' => !in_array( $sAction, [ 'download' ] ),
343
  'message' => $sMessage,
344
  ];
345
  }
346
 
347
- /**
348
- * @return array
349
- */
350
- private function ajaxExec_CheckScans() {
351
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
352
- $oMod = $this->getMod();
353
  /** @var Strings $oStrings */
354
- $oStrings = $oMod->getStrings();
355
  /** @var Shield\Databases\ScanQueue\Select $oSel */
356
- $oSel = $oMod->getDbHandler_ScanQueue()->getQuerySelector();
357
 
358
- $oQueCon = $oMod->getScanQueueController();
359
  $sCurrent = $oSel->getCurrentScan();
360
  $bHasCurrent = !empty( $sCurrent );
361
  if ( $bHasCurrent ) {
@@ -369,7 +354,7 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
369
  'success' => true,
370
  'running' => $oQueCon->getScansRunningStates(),
371
  'vars' => [
372
- 'progress_html' => $oMod->renderTemplate(
373
  '/wpadmin_pages/insights/scans/modal/progress_snippet.twig',
374
  [
375
  'current_scan' => __( 'Current Scan', 'wp-simple-firewall' ),
@@ -387,55 +372,58 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
387
  ];
388
  }
389
 
390
- /**
391
- * @return array
392
- */
393
- private function ajaxExec_StartScans() {
394
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $mod */
395
  $mod = $this->getMod();
396
- $bSuccess = false;
397
- $bPageReload = false;
398
- $sMessage = __( 'No scans were selected', 'wp-simple-firewall' );
399
- $aFormParams = $this->getAjaxFormParams();
 
 
400
 
401
- $oScanCon = $mod->getScanQueueController();
402
 
403
- if ( !empty( $aFormParams ) ) {
404
- $aSelectedScans = array_keys( $aFormParams );
405
 
406
  $aUiTrack = $mod->getUiTrack();
407
- $aUiTrack[ 'selected_scans' ] = $aSelectedScans;
408
  $mod->setUiTrack( $aUiTrack );
409
 
410
- $aScansToStart = [];
411
- foreach ( $aSelectedScans as $sScanSlug ) {
412
- $oThisScanCon = $mod->getScanCon( $sScanSlug );
413
- if ( !empty( $oThisScanCon ) && $oThisScanCon->isScanningAvailable() ) {
 
414
 
415
- $aScansToStart[] = $sScanSlug;
416
 
417
- if ( isset( $aFormParams[ 'opt_clear_ignore' ] ) ) {
418
- $oThisScanCon->resetIgnoreStatus();
419
- }
420
- if ( isset( $aFormParams[ 'opt_clear_notification' ] ) ) {
421
- $oThisScanCon->resetNotifiedStatus();
422
- }
423
 
424
- $bSuccess = true;
425
- $bPageReload = true;
426
- $sMessage = __( 'Scans started.', 'wp-simple-firewall' ).' '.__( 'Please wait, as this will take a few moments.', 'wp-simple-firewall' );
 
 
 
427
  }
428
  }
429
- $oScanCon->startScans( $aScansToStart );
430
  }
431
 
432
- $bScansRunning = $oScanCon->hasRunningScans();
433
 
434
  return [
435
- 'success' => $bSuccess,
436
- 'scans_running' => $bScansRunning,
437
- 'page_reload' => $bPageReload && !$bScansRunning,
438
- 'message' => $sMessage,
439
  ];
440
  }
441
  }
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan;
9
  use FernleafSystems\Wordpress\Services\Services;
10
 
11
+ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
12
 
13
  protected function processAjaxAction( string $action ) :array {
14
 
62
  return $aResponse;
63
  }
64
 
65
+ private function ajaxExec_BuildTableScan() :array {
66
+ /** @var ModCon $mod */
67
+ $mod = $this->getMod();
 
 
 
68
 
69
  $sScanSlug = Services::Request()->post( 'fScan' );
70
  switch ( $sScanSlug ) {
106
  }
107
  else {
108
  $sHtml = $oTableBuilder
109
+ ->setMod( $mod )
110
+ ->setDbHandler( $mod->getDbHandler_ScanResults() )
111
  ->render();
112
  }
113
 
117
  ];
118
  }
119
 
120
+ private function ajaxExec_FileLockerShowDiff() :array {
121
+ /** @var ModCon $mod */
122
+ $mod = $this->getMod();
123
+ $oFLCon = $mod->getFileLocker();
 
 
 
124
  $FS = Services::WpFs();
125
 
126
  $nRID = Services::Request()->post( 'rid' );
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 ) ?
206
  ];
207
  }
208
 
209
+ private function ajaxExec_FileLockerFileAction() :array {
 
 
 
210
  $oReq = Services::Request();
211
  $bSuccess = false;
212
 
233
  ];
234
  }
235
 
236
+ private function ajaxExec_PluginReinstall() :array {
237
+ /** @var ModCon $mod */
238
+ $mod = $this->getMod();
239
+ $req = Services::Request();
 
 
 
240
 
241
+ $bReinstall = (bool)$req->post( 'reinstall' );
242
+ $bActivate = (bool)$req->post( 'activate' );
243
+ $file = sanitize_text_field( wp_unslash( $req->post( 'file' ) ) );
244
 
245
  if ( $bReinstall ) {
246
+ /** @var Scan\Controller\Ptg $scan */
247
+ $scan = $mod->getScansCon()->getScanCon( 'ptg' );
248
+ $bActivate = $scan->actionPluginReinstall( $file );
249
  }
250
 
251
  if ( $bActivate ) {
252
+ Services::WpPlugins()->activate( $file );
253
  }
254
 
255
  return [ 'success' => true ];
256
  }
257
 
258
  /**
259
+ * @param string $action
260
  * @param bool $bIsBulkAction
261
  * @return array
262
  */
263
+ private function ajaxExec_ScanItemAction( $action, $bIsBulkAction = false ) :array {
264
+ /** @var ModCon $mod */
265
+ $mod = $this->getMod();
266
 
267
+ $success = false;
268
 
269
+ if ( $action == 'download' ) {
270
  // A special case since this action is handled using Javascript
271
+ $success = true;
272
  $sMessage = __( 'File download has started.', 'wp-simple-firewall' );
273
  }
274
  else {
289
  $aScanSlugs = [];
290
  $aSuccessfulItems = [];
291
  foreach ( $aItemIdsToProcess as $nId ) {
292
+ /** @var Shield\Databases\Scanner\EntryVO $entry */
293
+ $entry = $mod->getDbHandler_ScanResults()
294
+ ->getQuerySelector()
295
+ ->byId( $nId );
296
+ if ( $entry instanceof Shield\Databases\Scanner\EntryVO ) {
297
+ $aScanSlugs[] = $entry->scan;
298
+ if ( $mod->getScanCon( $entry->scan )->executeItemAction( $nId, $action ) ) {
299
  $aSuccessfulItems[] = $nId;
300
  }
301
  }
302
  }
303
 
304
  if ( count( $aSuccessfulItems ) === count( $aItemIdsToProcess ) ) {
305
+ $success = true;
306
  $sMessage = __( 'Action successful.' );
307
  }
308
  else {
310
  }
311
 
312
  // We don't rescan for ignores.
313
+ if ( in_array( $action, [ 'ignore' ] ) ) {
314
  $sMessage .= ' '.__( 'Reloading', 'wp-simple-firewall' ).' ...';
315
  }
316
  else {
317
  // rescan
318
+ $mod->getScanQueueController()->startScans( $aScanSlugs );
319
  $sMessage .= ' '.__( 'Rescanning', 'wp-simple-firewall' ).' ...';
320
  }
321
  }
326
  }
327
 
328
  return [
329
+ 'success' => $success,
330
+ 'page_reload' => !in_array( $action, [ 'download' ] ),
331
  'message' => $sMessage,
332
  ];
333
  }
334
 
335
+ private function ajaxExec_CheckScans() :array {
336
+ /** @var ModCon $mod */
337
+ $mod = $this->getMod();
 
 
 
338
  /** @var Strings $oStrings */
339
+ $oStrings = $mod->getStrings();
340
  /** @var Shield\Databases\ScanQueue\Select $oSel */
341
+ $oSel = $mod->getDbHandler_ScanQueue()->getQuerySelector();
342
 
343
+ $oQueCon = $mod->getScanQueueController();
344
  $sCurrent = $oSel->getCurrentScan();
345
  $bHasCurrent = !empty( $sCurrent );
346
  if ( $bHasCurrent ) {
354
  'success' => true,
355
  'running' => $oQueCon->getScansRunningStates(),
356
  'vars' => [
357
+ 'progress_html' => $mod->renderTemplate(
358
  '/wpadmin_pages/insights/scans/modal/progress_snippet.twig',
359
  [
360
  'current_scan' => __( 'Current Scan', 'wp-simple-firewall' ),
372
  ];
373
  }
374
 
375
+ private function ajaxExec_StartScans() :array {
376
+ /** @var ModCon $mod */
 
 
 
377
  $mod = $this->getMod();
378
+ /** @var Options $opts */
379
+ $opts = $this->getOptions();
380
+ $success = false;
381
+ $reloadPage = false;
382
+ $msg = __( 'No scans were selected', 'wp-simple-firewall' );
383
+ $formParams = $this->getAjaxFormParams();
384
 
385
+ $scanCon = $mod->getScanQueueController();
386
 
387
+ if ( !empty( $formParams ) ) {
388
+ $selected = array_keys( $formParams );
389
 
390
  $aUiTrack = $mod->getUiTrack();
391
+ $aUiTrack[ 'selected_scans' ] = array_intersect( array_keys( $formParams ), $opts->getScanSlugs() );
392
  $mod->setUiTrack( $aUiTrack );
393
 
394
+ $toScan = [];
395
+ foreach ( $selected as $slug ) {
396
+ try {
397
+ $thisScanCon = $mod->getScanCon( $slug );
398
+ if ( $thisScanCon->isScanningAvailable() ) {
399
 
400
+ $toScan[] = $slug;
401
 
402
+ if ( isset( $formParams[ 'opt_clear_ignore' ] ) ) {
403
+ $thisScanCon->resetIgnoreStatus();
404
+ }
405
+ if ( isset( $formParams[ 'opt_clear_notification' ] ) ) {
406
+ $thisScanCon->resetNotifiedStatus();
407
+ }
408
 
409
+ $success = true;
410
+ $reloadPage = true;
411
+ $msg = __( 'Scans started.', 'wp-simple-firewall' ).' '.__( 'Please wait, as this will take a few moments.', 'wp-simple-firewall' );
412
+ }
413
+ }
414
+ catch ( \Exception $e ) {
415
  }
416
  }
417
+ $scanCon->startScans( $toScan );
418
  }
419
 
420
+ $isScanRunning = $scanCon->hasRunningScans();
421
 
422
  return [
423
+ 'success' => $success,
424
+ 'scans_running' => $isScanRunning,
425
+ 'page_reload' => $reloadPage && !$isScanRunning,
426
+ 'message' => $msg,
427
  ];
428
  }
429
  }
src/lib/src/Modules/HackGuard/Insights/OverviewCards.php CHANGED
@@ -8,7 +8,7 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
8
  class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
9
 
10
  public function build() :array {
11
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $mod */
12
  $mod = $this->getMod();
13
  /** @var HackGuard\Options $opts */
14
  $opts = $this->getOptions();
@@ -51,7 +51,7 @@ class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
51
  }
52
 
53
  private function getCardsForWcf() :array {
54
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $mod */
55
  $mod = $this->getMod();
56
  /** @var HackGuard\Options $opts */
57
  $opts = $this->getOptions();
@@ -95,7 +95,7 @@ class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
95
  }
96
 
97
  private function getCardsForUfc() :array {
98
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $mod */
99
  $mod = $this->getMod();
100
  /** @var HackGuard\Options $opts */
101
  $opts = $this->getOptions();
@@ -137,7 +137,7 @@ class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
137
  }
138
 
139
  private function getCardsForPtg() :array {
140
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $mod */
141
  $mod = $this->getMod();
142
  $scanCon = $mod->getScanCon( HackGuard\Scan\Controller\Ptg::SCAN_SLUG );
143
 
@@ -168,7 +168,7 @@ class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
168
  }
169
 
170
  private function getCardsForMal() :array {
171
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $mod */
172
  $mod = $this->getMod();
173
  $scanCon = $mod->getScanCon( HackGuard\Scan\Controller\Mal::SCAN_SLUG );
174
 
@@ -198,7 +198,7 @@ class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
198
  }
199
 
200
  private function getCardsForApc() :array {
201
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $mod */
202
  $mod = $this->getMod();
203
  $scanCon = $mod->getScanCon( HackGuard\Scan\Controller\Apc::SCAN_SLUG );
204
 
@@ -227,7 +227,7 @@ class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
227
  }
228
 
229
  private function getCardsForWpv() :array {
230
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $mod */
231
  $mod = $this->getMod();
232
  $scanCon = $mod->getScanCon( HackGuard\Scan\Controller\Wpv::SCAN_SLUG );
233
 
8
  class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
9
 
10
  public function build() :array {
11
+ /** @var HackGuard\ModCon $mod */
12
  $mod = $this->getMod();
13
  /** @var HackGuard\Options $opts */
14
  $opts = $this->getOptions();
51
  }
52
 
53
  private function getCardsForWcf() :array {
54
+ /** @var HackGuard\ModCon $mod */
55
  $mod = $this->getMod();
56
  /** @var HackGuard\Options $opts */
57
  $opts = $this->getOptions();
95
  }
96
 
97
  private function getCardsForUfc() :array {
98
+ /** @var HackGuard\ModCon $mod */
99
  $mod = $this->getMod();
100
  /** @var HackGuard\Options $opts */
101
  $opts = $this->getOptions();
137
  }
138
 
139
  private function getCardsForPtg() :array {
140
+ /** @var HackGuard\ModCon $mod */
141
  $mod = $this->getMod();
142
  $scanCon = $mod->getScanCon( HackGuard\Scan\Controller\Ptg::SCAN_SLUG );
143
 
168
  }
169
 
170
  private function getCardsForMal() :array {
171
+ /** @var HackGuard\ModCon $mod */
172
  $mod = $this->getMod();
173
  $scanCon = $mod->getScanCon( HackGuard\Scan\Controller\Mal::SCAN_SLUG );
174
 
198
  }
199
 
200
  private function getCardsForApc() :array {
201
+ /** @var HackGuard\ModCon $mod */
202
  $mod = $this->getMod();
203
  $scanCon = $mod->getScanCon( HackGuard\Scan\Controller\Apc::SCAN_SLUG );
204
 
227
  }
228
 
229
  private function getCardsForWpv() :array {
230
+ /** @var HackGuard\ModCon $mod */
231
  $mod = $this->getMod();
232
  $scanCon = $mod->getScanCon( HackGuard\Scan\Controller\Wpv::SCAN_SLUG );
233
 
src/lib/src/Modules/HackGuard/Lib/FileLocker/FileLockerController.php CHANGED
@@ -18,9 +18,9 @@ class FileLockerController {
18
  * @return bool
19
  */
20
  public function isEnabled() {
21
- /** @var HackGuard\Options $oOpts */
22
- $oOpts = $this->getOptions();
23
- return ( count( $oOpts->getFilesToLock() ) > 0 )
24
  && $this->getCon()
25
  ->getModule_Plugin()
26
  ->getShieldNetApiController()
@@ -31,10 +31,9 @@ class FileLockerController {
31
  * @return bool
32
  */
33
  protected function canRun() {
34
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
35
- $oMod = $this->getMod();
36
- return $this->isEnabled()
37
- && $oMod->getDbHandler_FileLocker()->isReady();
38
  }
39
 
40
  protected function run() {
@@ -80,18 +79,18 @@ class FileLockerController {
80
  }
81
 
82
  /**
83
- * @param FileLocker\EntryVO $oVO
84
  * @return string[]
85
  */
86
- public function createFileDownloadLinks( $oVO ) {
87
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
88
- $oMod = $this->getMod();
89
  $aLinks = [];
90
  foreach ( [ 'original', 'current' ] as $sType ) {
91
- $aActionNonce = $oMod->getNonceActionData( 'filelocker_download_'.$sType );
92
- $aActionNonce[ 'rid' ] = $oVO->id;
93
  $aActionNonce[ 'rand' ] = rand();
94
- $aLinks[ $sType ] = add_query_arg( $aActionNonce, $oMod->getUrl_AdminPage() );
95
  }
96
  return $aLinks;
97
  }
@@ -124,13 +123,13 @@ class FileLockerController {
124
  }
125
 
126
  public function deleteAllLocks() {
127
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $mod */
128
  $mod = $this->getMod();
129
  $mod->getDbHandler_FileLocker()->tableDelete( true );
130
  }
131
 
132
  public function purge() {
133
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $mod */
134
  $mod = $this->getMod();
135
  $mod->getDbHandler_FileLocker()->tableDelete();
136
  }
@@ -221,13 +220,4 @@ class FileLockerController {
221
  $oFile->max_paths = $nMaxPaths;
222
  return $oFile;
223
  }
224
-
225
- /**
226
- * @return FileLocker\Handler
227
- */
228
- private function getDbHandler() {
229
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
230
- $oMod = $this->getMod();
231
- return $oMod->getDbHandler_FileLocker();
232
- }
233
  }
18
  * @return bool
19
  */
20
  public function isEnabled() {
21
+ /** @var HackGuard\Options $opts */
22
+ $opts = $this->getOptions();
23
+ return ( count( $opts->getFilesToLock() ) > 0 )
24
  && $this->getCon()
25
  ->getModule_Plugin()
26
  ->getShieldNetApiController()
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() {
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
  }
123
  }
124
 
125
  public function deleteAllLocks() {
126
+ /** @var HackGuard\ModCon $mod */
127
  $mod = $this->getMod();
128
  $mod->getDbHandler_FileLocker()->tableDelete( true );
129
  }
130
 
131
  public function purge() {
132
+ /** @var HackGuard\ModCon $mod */
133
  $mod = $this->getMod();
134
  $mod->getDbHandler_FileLocker()->tableDelete();
135
  }
220
  $oFile->max_paths = $nMaxPaths;
221
  return $oFile;
222
  }
 
 
 
 
 
 
 
 
 
223
  }
src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/Accept.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker\Ops;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\FileLocker;
 
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  /**
@@ -17,16 +18,16 @@ class Accept extends BaseOps {
17
  * @throws \ErrorException
18
  */
19
  public function run( $oLock ) {
20
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
21
- $oMod = $this->getMod();
22
 
23
  $aPublicKey = $this->getPublicKey();
24
  $sRawContent = ( new BuildEncryptedFilePayload() )
25
- ->setMod( $oMod )
26
  ->build( $oLock->file, reset( $aPublicKey ) );
27
 
28
  /** @var FileLocker\Update $oUpdater */
29
- $oUpdater = $oMod->getDbHandler_FileLocker()->getQueryUpdater();
30
  $bSuccess = $oUpdater->updateEntry( $oLock, [
31
  'hash_original' => hash_file( 'sha1', $oLock->file ),
32
  'content' => base64_encode( $sRawContent ),
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker\Ops;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\FileLocker;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\ModCon;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  /**
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 ),
src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/AssessLocks.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker\Ops;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\FileLocker;
 
6
  use FernleafSystems\Wordpress\Services\Utilities\File\Compare\CompareHash;
7
 
8
  class AssessLocks extends BaseOps {
@@ -11,10 +12,10 @@ class AssessLocks extends BaseOps {
11
  * @return int[]
12
  */
13
  public function run() {
14
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
15
- $oMod = $this->getMod();
16
  /** @var FileLocker\Update $oUpd */
17
- $oUpd = $oMod->getDbHandler_FileLocker()->getQueryUpdater();
18
 
19
  $this->removeDuplicates();
20
 
@@ -47,11 +48,11 @@ class AssessLocks extends BaseOps {
47
  $aPaths = [];
48
  foreach ( $this->getFileLocks() as $oLock ) {
49
  if ( in_array( $oLock->file, $aPaths ) ) {
50
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
51
- $oMod = $this->getMod();
52
- $oMod->getDbHandler_FileLocker()
53
- ->getQueryDeleter()
54
- ->deleteById( $oLock->id );
55
  }
56
  else {
57
  $aPaths[] = $oLock->file;
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker\Ops;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\FileLocker;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\ModCon;
7
  use FernleafSystems\Wordpress\Services\Utilities\File\Compare\CompareHash;
8
 
9
  class AssessLocks extends BaseOps {
12
  * @return int[]
13
  */
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
 
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;
src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/CreateFileLocks.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker\Ops;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\FileLocker;
 
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  /**
@@ -31,26 +32,26 @@ class CreateFileLocks extends BaseOps {
31
  }
32
 
33
  /**
34
- * @param string $sPath
35
  * @throws \Exception
36
  */
37
- private function processPath( $sPath ) {
38
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
39
- $oMod = $this->getMod();
40
 
41
- if ( Services::WpFs()->isFile( $sPath ) ) {
42
  $oEntry = new FileLocker\EntryVO();
43
- $oEntry->file = $sPath;
44
- $oEntry->hash_original = hash_file( 'sha1', $sPath );
45
 
46
  $aPublicKey = $this->getPublicKey();
47
  $oEntry->public_key_id = key( $aPublicKey );
48
  $oEntry->content = ( new BuildEncryptedFilePayload() )
49
- ->setMod( $oMod )
50
- ->build( $sPath, reset( $aPublicKey ) );
51
 
52
  /** @var FileLocker\Insert $oInserter */
53
- $oInserter = $oMod->getDbHandler_FileLocker()->getQueryInserter();
54
  $oInserter->insert( $oEntry );
55
 
56
  $this->clearFileLocksCache();
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker\Ops;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\FileLocker;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\ModCon;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  /**
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();
src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/DeleteFileLock.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker\Ops;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\FileLocker;
 
6
 
7
  /**
8
  * Class DeleteFileLock
@@ -15,13 +16,13 @@ class DeleteFileLock extends BaseOps {
15
  * @return bool
16
  */
17
  public function delete( $oLock = null ) {
18
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
19
- $oMod = $this->getMod();
20
  if ( empty( $oLock ) ) {
21
  $oLock = $this->findLockRecordForFile();
22
  }
23
  $bSuccess = $oLock instanceof FileLocker\EntryVO
24
- && $oMod->getDbHandler_FileLocker()
25
  ->getQueryDeleter()
26
  ->deleteEntry( $oLock );
27
  if ( $bSuccess ) {
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker\Ops;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\FileLocker;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\ModCon;
7
 
8
  /**
9
  * Class DeleteFileLock
16
  * @return bool
17
  */
18
  public function delete( $oLock = null ) {
19
+ /** @var ModCon $mod */
20
+ $mod = $this->getMod();
21
  if ( empty( $oLock ) ) {
22
  $oLock = $this->findLockRecordForFile();
23
  }
24
  $bSuccess = $oLock instanceof FileLocker\EntryVO
25
+ && $mod->getDbHandler_FileLocker()
26
  ->getQueryDeleter()
27
  ->deleteEntry( $oLock );
28
  if ( $bSuccess ) {
src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/LoadFileLocks.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker\Ops;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\FileLocker;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
 
8
  /**
@@ -23,12 +24,12 @@ class LoadFileLocks {
23
  */
24
  public function loadLocks() {
25
  if ( is_null( self::$aFileLockRecords ) ) {
26
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
27
- $oMod = $this->getMod();
28
 
29
  self::$aFileLockRecords = [];
30
- if ( $oMod->getFileLocker()->isEnabled() ) {
31
- $aAll = $oMod->getDbHandler_FileLocker()->getQuerySelector()->all();
32
  if ( is_array( $aAll ) ) {
33
  foreach ( $aAll as $oLock ) {
34
  self::$aFileLockRecords[ $oLock->id ] = $oLock;
@@ -58,7 +59,6 @@ class LoadFileLocks {
58
  return array_filter(
59
  $this->withProblems(),
60
  function ( $oLock ) {
61
- /** @var FileLocker\EntryVO $oLock */
62
  return $oLock->notified_at == 0;
63
  }
64
  );
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker\Ops;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\FileLocker;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\ModCon;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
 
9
  /**
24
  */
25
  public function loadLocks() {
26
  if ( is_null( self::$aFileLockRecords ) ) {
27
+ /** @var ModCon $mod */
28
+ $mod = $this->getMod();
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;
59
  return array_filter(
60
  $this->withProblems(),
61
  function ( $oLock ) {
 
62
  return $oLock->notified_at == 0;
63
  }
64
  );
src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/PerformAction.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker\Ops;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
 
6
 
7
  /**
8
  * Class PerformAction
@@ -17,8 +18,8 @@ class PerformAction extends BaseOps {
17
  * @throws \Exception
18
  */
19
  public function run( $nLockID, $sAction ) {
20
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
21
- $oMod = $this->getMod();
22
 
23
  if ( !in_array( $sAction, [ 'accept', 'restore', 'diff' ] ) ) {
24
  throw new \Exception( __( 'Not a supported file lock action.', 'wp-simple-firewall' ) );
@@ -26,7 +27,7 @@ class PerformAction extends BaseOps {
26
  if ( !is_numeric( $nLockID ) ) {
27
  throw new \Exception( __( 'Please select a valid file.', 'wp-simple-firewall' ) );
28
  }
29
- $oLock = $oMod->getDbHandler_FileLocker()
30
  ->getQuerySelector()
31
  ->byId( (int)$nLockID );
32
  if ( !$oLock instanceof Databases\FileLocker\EntryVO ) {
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker\Ops;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\ModCon;
7
 
8
  /**
9
  * Class PerformAction
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' ) );
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 ) {
src/lib/src/Modules/HackGuard/Lib/FileLocker/Ops/Restore.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker\Ops;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
 
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  /**
@@ -23,10 +24,10 @@ class Restore extends BaseOps {
23
  ->run( $oRecord )
24
  );
25
  if ( $bReverted ) {
26
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
27
- $oMod = $this->getMod();
28
  /** @var Databases\FileLocker\Update $oUpd */
29
- $oUpd = $oMod->getDbHandler_FileLocker()->getQueryUpdater();
30
  $oUpd->markReverted( $oRecord );
31
  $this->clearFileLocksCache();
32
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker\Ops;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\ModCon;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  /**
24
  ->run( $oRecord )
25
  );
26
  if ( $bReverted ) {
27
+ /** @var ModCon $mod */
28
+ $mod = $this->getMod();
29
  /** @var Databases\FileLocker\Update $oUpd */
30
+ $oUpd = $mod->getDbHandler_FileLocker()->getQueryUpdater();
31
  $oUpd->markReverted( $oRecord );
32
  $this->clearFileLocksCache();
33
  }
src/lib/src/Modules/HackGuard/Lib/Reports/FileLockerAlerts.php CHANGED
@@ -14,8 +14,8 @@ class FileLockerAlerts extends BaseReporter {
14
  public function build() {
15
  $aAlerts = [];
16
 
17
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
18
- $oMod = $this->getMod();
19
 
20
  $oLockOps = ( new HackGuard\Lib\FileLocker\Ops\LoadFileLocks() )
21
  ->setMod( $this->getMod() );
@@ -26,7 +26,7 @@ class FileLockerAlerts extends BaseReporter {
26
  '/components/reports/mod/hack_protect/alert_filelocker.twig',
27
  [
28
  'vars' => [
29
- 'count' => $oMod->getFileLocker()->countProblems()
30
  ],
31
  'strings' => [
32
  'title' => __( 'File Locker Changes Detected', 'wp-simple-firewall' ),
@@ -50,10 +50,10 @@ class FileLockerAlerts extends BaseReporter {
50
  * @param FileLocker\EntryVO[] $aNotNotified
51
  */
52
  private function markAlertsAsNotified( $aNotNotified ) {
53
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
54
- $oMod = $this->getMod();
55
  /** @var FileLocker\Update $oUpdater */
56
- $oUpdater = $oMod->getDbHandler_FileLocker()->getQueryUpdater();
57
  foreach ( $aNotNotified as $oEntry ) {
58
  $oUpdater->markNotified( $oEntry );
59
  }
14
  public function build() {
15
  $aAlerts = [];
16
 
17
+ /** @var HackGuard\ModCon $mod */
18
+ $mod = $this->getMod();
19
 
20
  $oLockOps = ( new HackGuard\Lib\FileLocker\Ops\LoadFileLocks() )
21
  ->setMod( $this->getMod() );
26
  '/components/reports/mod/hack_protect/alert_filelocker.twig',
27
  [
28
  'vars' => [
29
+ 'count' => $mod->getFileLocker()->countProblems()
30
  ],
31
  'strings' => [
32
  'title' => __( 'File Locker Changes Detected', 'wp-simple-firewall' ),
50
  * @param FileLocker\EntryVO[] $aNotNotified
51
  */
52
  private function markAlertsAsNotified( $aNotNotified ) {
53
+ /** @var HackGuard\ModCon $mod */
54
+ $mod = $this->getMod();
55
  /** @var FileLocker\Update $oUpdater */
56
+ $oUpdater = $mod->getDbHandler_FileLocker()->getQueryUpdater();
57
  foreach ( $aNotNotified as $oEntry ) {
58
  $oUpdater->markNotified( $oEntry );
59
  }
src/lib/src/Modules/HackGuard/Lib/Reports/Query/ScanCounts.php ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\Reports\Query;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases\Scanner;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
+ use FernleafSystems\Wordpress\Services\Services;
9
+
10
+ class ScanCounts {
11
+
12
+ use ModConsumer;
13
+
14
+ /**
15
+ * @var int
16
+ */
17
+ public $from;
18
+
19
+ /**
20
+ * @var int
21
+ */
22
+ public $to;
23
+
24
+ /**
25
+ * @var bool
26
+ */
27
+ public $ignored = false;
28
+
29
+ /**
30
+ * @var bool
31
+ */
32
+ public $notified = false;
33
+
34
+ public function __construct( $from = null, $to = null ) {
35
+ $this->from = is_int( $from ) ? $from : 0;
36
+ $this->to = is_int( $to ) ? $to : Services::Request()->ts();
37
+ }
38
+
39
+ /**
40
+ * @return int[] - key is scan slug
41
+ */
42
+ public function all() :array {
43
+ return array_merge(
44
+ $this->standard(),
45
+ $this->filelocker()
46
+ );
47
+ }
48
+
49
+ /**
50
+ * @return int[]
51
+ */
52
+ public function filelocker() :array {
53
+ return [
54
+ 'filelocker' => count( ( new HackGuard\Lib\FileLocker\Ops\LoadFileLocks() )
55
+ ->setMod( $this->getMod() )
56
+ ->withProblemsNotNotified() )
57
+ ];
58
+ }
59
+
60
+ /**
61
+ * @return int[] - key is scan slug
62
+ */
63
+ public function standard() :array {
64
+ /** @var HackGuard\ModCon $mod */
65
+ $mod = $this->getMod();
66
+ /** @var HackGuard\Options $opts */
67
+ $opts = $this->getOptions();
68
+ /** @var Scanner\Select $qSel */
69
+ $qSel = $mod->getDbHandler_ScanResults()->getQuerySelector();
70
+
71
+ $counts = [];
72
+
73
+ foreach ( $opts->getScanSlugs() as $slug ) {
74
+ $qSel->filterByScan( $slug )
75
+ ->filterByCreatedAt( $this->from, '>=' )
76
+ ->filterByCreatedAt( $this->to, '<=' );
77
+ if ( isset( $this->ignored ) ) {
78
+ $this->ignored ? $qSel->filterByIgnored() : $qSel->filterByNotIgnored();
79
+ }
80
+ if ( isset( $this->notified ) ) {
81
+ $this->notified ? $qSel->filterByNotified() : $qSel->filterByNotNotified();
82
+ }
83
+ $counts[ $slug ] = $qSel->count();
84
+ }
85
+
86
+ return $counts;
87
+ }
88
+ }
src/lib/src/Modules/HackGuard/Lib/Reports/ScanAlerts.php CHANGED
@@ -13,25 +13,30 @@ class ScanAlerts extends BaseReporter {
13
  * @inheritDoc
14
  */
15
  public function build() {
16
- $aAlerts = [];
17
 
18
- /** @var HackGuard\Strings $oStrings */
19
- $oStrings = $this->getMod()->getStrings();
20
- $aScanNames = $oStrings->getScanNames();
21
 
22
- $aScanItemCounts = array_filter( $this->countItemsForEachScan() );
23
- if ( !empty( $aScanItemCounts ) ) {
24
- foreach ( $aScanItemCounts as $sScan => $nCount ) {
25
- $aScanItemCounts[ $sScan ] = [
26
- 'count' => $nCount,
27
- 'name' => $aScanNames[ $sScan ],
 
 
 
 
 
 
28
  ];
29
  }
30
- $aAlerts[] = $this->getMod()->renderTemplate(
31
  '/components/reports/mod/hack_protect/alert_scanresults.twig',
32
  [
33
  'vars' => [
34
- 'scan_counts' => $aScanItemCounts
35
  ],
36
  'strings' => [
37
  'title' => __( 'New Scan Results', 'wp-simple-firewall' ),
@@ -46,14 +51,14 @@ class ScanAlerts extends BaseReporter {
46
  $this->markAlertsAsNotified();
47
  }
48
 
49
- return $aAlerts;
50
  }
51
 
52
  private function markAlertsAsNotified() {
53
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
54
- $oMod = $this->getMod();
55
  /** @var Scanner\Update $oUpdater */
56
- $oUpdater = $oMod->getDbHandler_ScanResults()->getQueryUpdater();
57
  $oUpdater
58
  ->setUpdateWheres( [
59
  'ignored_at' => 0,
@@ -64,35 +69,4 @@ class ScanAlerts extends BaseReporter {
64
  ] )
65
  ->query();
66
  }
67
-
68
- /**
69
- * TODO: As class that can be used by ShieldCentral also
70
- * @return int[] - key is scan slug
71
- */
72
- private function countItemsForEachScan() {
73
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
74
- $oMod = $this->getMod();
75
- /** @var HackGuard\Options $oOpts */
76
- $oOpts = $this->getOptions();
77
- /** @var Scanner\Select $oSel */
78
- $oSel = $oMod->getDbHandler_ScanResults()->getQuerySelector();
79
-
80
- $aCounts = [];
81
-
82
- $oRep = $this->getReport();
83
-
84
- foreach ( $oOpts->getScanSlugs() as $sScanSlug ) {
85
- $oSel->filterByScan( $sScanSlug )
86
- ->filterByNotNotified()
87
- ->filterByNotIgnored();
88
- if ( !is_null( $oRep->interval_start_at ) ) {
89
- $oSel->filterByCreatedAt( $oRep->interval_start_at, '>' );
90
- }
91
- if ( !is_null( $oRep->interval_end_at ) ) {
92
- $oSel->filterByCreatedAt( $oRep->interval_end_at, '<' );
93
- }
94
- $aCounts[ $sScanSlug ] = $oSel->count();
95
- }
96
- return $aCounts;
97
- }
98
  }
13
  * @inheritDoc
14
  */
15
  public function build() {
16
+ $alerts = [];
17
 
18
+ /** @var HackGuard\Strings $strings */
19
+ $strings = $this->getMod()->getStrings();
 
20
 
21
+ $rep = $this->getReport();
22
+ $scanCounts = array_filter(
23
+ ( new Query\ScanCounts( $rep->interval_start_at, $rep->interval_end_at ) )
24
+ ->setMod( $this->getMod() )
25
+ ->standard()
26
+ );
27
+
28
+ if ( !empty( $scanCounts ) ) {
29
+ foreach ( $scanCounts as $slug => $count ) {
30
+ $scanCounts[ $slug ] = [
31
+ 'count' => $count,
32
+ 'name' => $strings->getScanNames()[ $slug ],
33
  ];
34
  }
35
+ $alerts[] = $this->getMod()->renderTemplate(
36
  '/components/reports/mod/hack_protect/alert_scanresults.twig',
37
  [
38
  'vars' => [
39
+ 'scan_counts' => $scanCounts
40
  ],
41
  'strings' => [
42
  'title' => __( 'New Scan Results', 'wp-simple-firewall' ),
51
  $this->markAlertsAsNotified();
52
  }
53
 
54
+ return $alerts;
55
  }
56
 
57
  private function markAlertsAsNotified() {
58
+ /** @var HackGuard\ModCon $mod */
59
+ $mod = $this->getMod();
60
  /** @var Scanner\Update $oUpdater */
61
+ $oUpdater = $mod->getDbHandler_ScanResults()->getQueryUpdater();
62
  $oUpdater
63
  ->setUpdateWheres( [
64
  'ignored_at' => 0,
69
  ] )
70
  ->query();
71
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  }
src/lib/src/Modules/HackGuard/Lib/Snapshots/StoreAction/BaseAction.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\Snapshots\StoreAction;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\Snapshots;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Services\Core\VOs\WpPluginVo;
8
  use FernleafSystems\Wordpress\Services\Core\VOs\WpThemeVo;
@@ -40,9 +41,9 @@ class BaseAction {
40
  * @throws \Exception
41
  */
42
  protected function getNewStore() {
43
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
44
- $oMod = $this->getMod();
45
  return ( new Snapshots\Store( $this->getAsset() ) )
46
- ->setWorkingDir( $oMod->getPtgSnapsBaseDir() );
47
  }
48
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\Snapshots\StoreAction;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\Snapshots;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\ModCon;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
  use FernleafSystems\Wordpress\Services\Core\VOs\WpPluginVo;
9
  use FernleafSystems\Wordpress\Services\Core\VOs\WpThemeVo;
41
  * @throws \Exception
42
  */
43
  protected function getNewStore() {
44
+ /** @var ModCon $mod */
45
+ $mod = $this->getMod();
46
  return ( new Snapshots\Store( $this->getAsset() ) )
47
+ ->setWorkingDir( $mod->getPtgSnapsBaseDir() );
48
  }
49
  }
src/lib/src/Modules/HackGuard/Lib/Snapshots/StoreAction/Build.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\Snapshots\StoreAction;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\Snapshots;
 
6
  use FernleafSystems\Wordpress\Services\Core\VOs\WpPluginVo;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
@@ -31,10 +32,8 @@ class Build extends BaseAction {
31
  }
32
 
33
  if ( !empty( $aHashes ) ) {
34
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
35
- $oMod = $this->getMod();
36
  $oStore = ( new CreateNew() )
37
- ->setMod( $oMod )
38
  ->setAsset( $oAsset )
39
  ->run();
40
  $oStore->setSnapData( $aHashes )
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\Snapshots\StoreAction;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\Snapshots;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\ModCon;
7
  use FernleafSystems\Wordpress\Services\Core\VOs\WpPluginVo;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
32
  }
33
 
34
  if ( !empty( $aHashes ) ) {
 
 
35
  $oStore = ( new CreateNew() )
36
+ ->setMod( $this->getMod() )
37
  ->setAsset( $oAsset )
38
  ->run();
39
  $oStore->setSnapData( $aHashes )
src/lib/src/Modules/HackGuard/Lib/Snapshots/StoreAction/CleanAll.php CHANGED
@@ -2,19 +2,20 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\Snapshots\StoreAction;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Scans\Helpers\StandardDirectoryIterator;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class CleanAll extends BaseBulk {
9
 
10
  public function run() {
11
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
12
- $oMod = $this->getMod();
13
  try {
14
  $nBoundary = Services::Request()
15
  ->carbon()
16
  ->subDay()->timestamp;
17
- $oDirIt = StandardDirectoryIterator::create( $oMod->getPtgSnapsBaseDir() );
18
  foreach ( $oDirIt as $oFile ) {
19
  /** @var \SplFileInfo $oFile */
20
  if ( $nBoundary > $oFile->getMTime() ) {
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\Snapshots\StoreAction;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\ModCon;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Scans\Helpers\StandardDirectoryIterator;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class CleanAll extends BaseBulk {
10
 
11
  public function run() {
12
+ /** @var ModCon $mod */
13
+ $mod = $this->getMod();
14
  try {
15
  $nBoundary = Services::Request()
16
  ->carbon()
17
  ->subDay()->timestamp;
18
+ $oDirIt = StandardDirectoryIterator::create( $mod->getPtgSnapsBaseDir() );
19
  foreach ( $oDirIt as $oFile ) {
20
  /** @var \SplFileInfo $oFile */
21
  if ( $nBoundary > $oFile->getMTime() ) {
src/lib/src/Modules/HackGuard/Lib/Snapshots/StoreAction/DeleteAll.php CHANGED
@@ -2,13 +2,14 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\Snapshots\StoreAction;
4
 
 
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
7
  class DeleteAll extends BaseBulk {
8
 
9
  public function run() {
10
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
11
- $oMod = $this->getMod();
12
- Services::WpFs()->deleteDir( $oMod->getPtgSnapsBaseDir() );
13
  }
14
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\Snapshots\StoreAction;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\ModCon;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class DeleteAll extends BaseBulk {
9
 
10
  public function run() {
11
+ /** @var ModCon $mod */
12
+ $mod = $this->getMod();
13
+ Services::WpFs()->deleteDir( $mod->getPtgSnapsBaseDir() );
14
  }
15
  }
src/lib/src/Modules/HackGuard/ModCon.php ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
8
+ use FernleafSystems\Wordpress\Services\Services;
9
+
10
+ class ModCon extends BaseShield\ModCon {
11
+
12
+ /**
13
+ * @var Scan\ScansController
14
+ */
15
+ private $scanCon;
16
+
17
+ /**
18
+ * @var Scan\Queue\Controller
19
+ */
20
+ private $scanQueueCon;
21
+
22
+ /**
23
+ * @var Scan\Controller\Base[]
24
+ */
25
+ private $scansCons;
26
+
27
+ /**
28
+ * @var Lib\FileLocker\FileLockerController
29
+ */
30
+ private $oFileLocker;
31
+
32
+ protected function doPostConstruction() {
33
+ $this->setCustomCronSchedules();
34
+ }
35
+
36
+ public function onWpInit() {
37
+ parent::onWpInit();
38
+ $this->getScanQueueController();
39
+ }
40
+
41
+ public function getFileLocker() :Lib\FileLocker\FileLockerController {
42
+ if ( !isset( $this->oFileLocker ) ) {
43
+ $this->oFileLocker = ( new Lib\FileLocker\FileLockerController() )
44
+ ->setMod( $this );
45
+ }
46
+ return $this->oFileLocker;
47
+ }
48
+
49
+ public function getScansCon() :Scan\ScansController {
50
+ if ( !isset( $this->scanCon ) ) {
51
+ $this->scanCon = ( new Scan\ScansController() )
52
+ ->setMod( $this );
53
+ }
54
+ return $this->scanCon;
55
+ }
56
+
57
+ public function getScanQueueController() :Scan\Queue\Controller {
58
+ if ( !isset( $this->scanQueueCon ) ) {
59
+ $this->scanQueueCon = ( new Scan\Queue\Controller() )
60
+ ->setMod( $this );
61
+ }
62
+ return $this->scanQueueCon;
63
+ }
64
+
65
+ /**
66
+ * @return Scan\Controller\Base[]
67
+ * @deprecated 10.1
68
+ */
69
+ public function getAllScanCons() :array {
70
+ return $this->scansCons ?? $this->getScansCon()->getAllScanCons();
71
+ }
72
+
73
+ /**
74
+ * @param string $slug
75
+ * @return Scan\Controller\Base|mixed
76
+ * @throws \Exception
77
+ */
78
+ public function getScanCon( string $slug ) {
79
+ return empty( $this->scansCons[ $slug ] ) ?
80
+ $this->getScansCon()->getScanCon( $slug ) : $this->scansCons[ $slug ];
81
+ }
82
+
83
+ public function getMainWpData() :array {
84
+ $issues = ( new Lib\Reports\Query\ScanCounts() )->setMod( $this );
85
+ $issues->notified = null;
86
+ return array_merge( parent::getMainWpData(), [
87
+ 'scan_issues' => array_filter( $issues->all() )
88
+ ] );
89
+ }
90
+
91
+ protected function handleModAction( string $action ) {
92
+ switch ( $action ) {
93
+ case 'scan_file_download':
94
+ ( new Lib\Utility\FileDownloadHandler() )
95
+ ->setDbHandler( $this->getDbHandler_ScanResults() )
96
+ ->downloadByItemId( (int)Services::Request()->query( 'rid', 0 ) );
97
+ break;
98
+ case 'filelocker_download_original':
99
+ case 'filelocker_download_current':
100
+ $this->getFileLocker()->handleFileDownloadRequest();
101
+ break;
102
+ default:
103
+ break;
104
+ }
105
+ }
106
+
107
+ protected function preProcessOptions() {
108
+ /** @var Options $opts */
109
+ $opts = $this->getOptions();
110
+
111
+ $this->cleanFileExclusions();
112
+
113
+ if ( $opts->isOptChanged( 'scan_frequency' ) ) {
114
+ $this->getScansCon()->deleteCron();
115
+ }
116
+
117
+ if ( count( $opts->getFilesToLock() ) === 0 || !$this->getCon()
118
+ ->getModule_Plugin()
119
+ ->getShieldNetApiController()
120
+ ->canHandshake() ) {
121
+ $opts->setOpt( 'file_locker', [] );
122
+ $this->getFileLocker()->purge();
123
+ }
124
+
125
+ $lockFiles = $opts->getFilesToLock();
126
+ if ( in_array( 'root_webconfig', $lockFiles ) && !Services::Data()->isWindows() ) {
127
+ unset( $lockFiles[ array_search( 'root_webconfig', $lockFiles ) ] );
128
+ $opts->setOpt( 'file_locker', $lockFiles );
129
+ }
130
+
131
+ foreach ( $this->getScansCon()->getAllScanCons() as $con ) {
132
+ if ( !$con->isEnabled() ) {
133
+ $con->purge();
134
+ }
135
+ }
136
+ }
137
+
138
+ /**
139
+ * @return $this
140
+ */
141
+ protected function setCustomCronSchedules() {
142
+ /** @var Options $opts */
143
+ $opts = $this->getOptions();
144
+ $freq = $opts->getScanFrequency();
145
+ Services::WpCron()
146
+ ->addNewSchedule(
147
+ $this->prefix( sprintf( 'per-day-%s', $freq ) ),
148
+ [
149
+ 'interval' => DAY_IN_SECONDS/$freq,
150
+ 'display' => sprintf( __( '%s per day', 'wp-simple-firewall' ), $freq )
151
+ ]
152
+ );
153
+ return $this;
154
+ }
155
+
156
+ protected function cleanFileExclusions() {
157
+ /** @var Options $opts */
158
+ $opts = $this->getOptions();
159
+ $aExclusions = [];
160
+
161
+ $aToClean = $opts->getOpt( 'ufc_exclusions', [] );
162
+ if ( is_array( $aToClean ) ) {
163
+ foreach ( $aToClean as $nKey => $sExclusion ) {
164
+ $sExclusion = wp_normalize_path( trim( $sExclusion ) );
165
+
166
+ if ( preg_match( '/^#(.+)#$/', $sExclusion, $aMatches ) ) { // it's regex
167
+ // ignore it
168
+ }
169
+ elseif ( strpos( $sExclusion, '/' ) === false ) { // filename only
170
+ $sExclusion = trim( preg_replace( '#[^.0-9a-z_-]#i', '', $sExclusion ) );
171
+ }
172
+
173
+ if ( !empty( $sExclusion ) ) {
174
+ $aExclusions[] = $sExclusion;
175
+ }
176
+ }
177
+ }
178
+
179
+ $opts->setOpt( 'ufc_exclusions', array_unique( $aExclusions ) );
180
+ }
181
+
182
+ public function isPtgEnabled() :bool {
183
+ $opts = $this->getOptions();
184
+ return $this->isModuleEnabled() && $this->isPremium()
185
+ && $opts->isOpt( 'ptg_enable', 'enabled' )
186
+ && $opts->isOptReqsMet( 'ptg_enable' )
187
+ && $this->canCacheDirWrite();
188
+ }
189
+
190
+ /**
191
+ * @return string|false
192
+ */
193
+ public function getPtgSnapsBaseDir() {
194
+ return $this->getCon()->getPluginCachePath( 'ptguard/' );
195
+ }
196
+
197
+ public function hasWizard() :bool {
198
+ return false;
199
+ }
200
+
201
+ /**
202
+ * @return string
203
+ */
204
+ public function getTempDir() {
205
+ $sDir = $this->getCon()->getPluginCachePath( 'scans' );
206
+ return Services::WpFs()->mkdir( $sDir ) ? $sDir : false;
207
+ }
208
+
209
+ public function getDbHandler_FileLocker() :Databases\FileLocker\Handler {
210
+ return $this->getDbH( 'file_protect' );
211
+ }
212
+
213
+ public function getDbHandler_ScanQueue() :Databases\ScanQueue\Handler {
214
+ return $this->getDbH( 'scanq' );
215
+ }
216
+
217
+ public function getDbHandler_ScanResults() :Databases\Scanner\Handler {
218
+ return $this->getDbH( 'scanresults' );
219
+ }
220
+
221
+ /**
222
+ * @return bool
223
+ * @throws \Exception
224
+ */
225
+ protected function isReadyToExecute() :bool {
226
+ return ( $this->getDbHandler_ScanQueue() instanceof Databases\ScanQueue\Handler )
227
+ && $this->getDbHandler_ScanQueue()->isReady()
228
+ && ( $this->getDbHandler_ScanResults() instanceof Databases\Scanner\Handler )
229
+ && $this->getDbHandler_ScanQueue()->isReady()
230
+ && parent::isReadyToExecute();
231
+ }
232
+
233
+ public function onPluginDeactivate() {
234
+ // 1. Clean out the scanners
235
+ /** @var Options $oOpts */
236
+ $oOpts = $this->getOptions();
237
+ foreach ( $oOpts->getScanSlugs() as $slug ) {
238
+ $this->getScanCon( $slug )->purge();
239
+ }
240
+ $this->getDbHandler_ScanQueue()->tableDelete();
241
+ $this->getDbHandler_ScanResults()->tableDelete();
242
+ // 2. Clean out the file locker
243
+ $this->getFileLocker()->purge();
244
+ }
245
+
246
+ /**
247
+ * @return bool
248
+ * @deprecated 10.1
249
+ */
250
+ public function isWpvulnPluginsHighlightEnabled() :bool {
251
+ return false;
252
+ }
253
+ }
src/lib/src/Modules/HackGuard/Options.php CHANGED
@@ -2,10 +2,10 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
- class Options extends Base\ShieldOptions {
9
 
10
  public function getFilesToLock() :array {
11
  $locks = $this->getOpt( 'file_locker', [] );
@@ -19,22 +19,15 @@ class Options extends Base\ShieldOptions {
19
  /**
20
  * @return int[] - keys are the unique report hash
21
  */
22
- public function getMalFalsePositiveReports() {
23
- $aFP = $this->getOpt( 'mal_fp_reports', [] );
24
- return is_array( $aFP ) ? $aFP : [];
25
  }
26
 
27
- /**
28
- * @param string $sReportHash
29
- * @return bool
30
- */
31
- public function isMalFalsePositiveReported( $sReportHash ) :bool {
32
- return isset( $this->getMalFalsePositiveReports()[ $sReportHash ] );
33
  }
34
 
35
- /**
36
- * @return int
37
- */
38
  public function getMalConfidenceBoundary() :int {
39
  return (int)apply_filters( 'icwp_shield_fp_confidence_boundary', 50 );
40
  }
@@ -78,86 +71,62 @@ class Options extends Base\ShieldOptions {
78
  }
79
 
80
  /**
81
- * @param string $sFilename
82
- * @param string $sUrl
83
  * @return string[]
84
  */
85
- public function getMalSignatures( $sFilename, $sUrl ) {
86
- $oWpFs = Services::WpFs();
87
- $sFile = $this->getCon()->getPluginCachePath( $sFilename );
88
- if ( $oWpFs->exists( $sFile ) ) {
89
- $aSigs = explode( "\n", $oWpFs->getFileContent( $sFile, true ) );
90
  }
91
  else {
92
- $aSigs = array_filter(
93
  array_map( 'trim',
94
- explode( "\n", Services::HttpRequest()->getContent( $sUrl ) )
95
  ),
96
- function ( $sLine ) {
97
- return ( ( strpos( $sLine, '#' ) !== 0 ) && strlen( $sLine ) > 0 );
98
  }
99
  );
100
 
101
- if ( !empty( $aSigs ) ) {
102
- $oWpFs->putFileContent( $sFile, implode( "\n", $aSigs ), true );
103
  }
104
  }
105
- return $aSigs;
106
  }
107
 
108
- /**
109
- * @return bool
110
- */
111
- public function isMalAutoRepairSurgical() {
112
  return $this->isOpt( 'mal_autorepair_surgical', 'Y' );
113
  }
114
 
115
- /**
116
- * @return bool
117
- */
118
- public function isMalUseNetworkIntelligence() {
119
  return $this->getMalConfidenceBoundary() > 0;
120
  }
121
 
122
- /**
123
- * @return bool
124
- */
125
- public function isPtgReinstallLinks() {
126
  return $this->isOpt( 'ptg_reinstall_links', 'Y' ) && $this->isPremium();
127
  }
128
 
129
- /**
130
- * @return bool
131
- */
132
- public function isRepairFileAuto() {
133
  return count( $this->getRepairAreas() ) > 0;
134
  }
135
 
136
- /**
137
- * @return bool
138
- */
139
- public function isRepairFilePlugin() {
140
  return in_array( 'plugin', $this->getRepairAreas() );
141
  }
142
 
143
- /**
144
- * @return bool
145
- */
146
- public function isRepairFileTheme() {
147
  return in_array( 'theme', $this->getRepairAreas() );
148
  }
149
 
150
- /**
151
- * @return bool
152
- */
153
- public function isRepairFileWP() {
154
  return in_array( 'wp', $this->getRepairAreas() );
155
  }
156
 
157
- /**
158
- * @return bool
159
- */
160
- public function isWpvulnAutoupdatesEnabled() {
161
  return $this->isOpt( 'wpvuln_scan_autoupdate', 'Y' );
162
  }
163
 
@@ -220,9 +189,9 @@ class Options extends Base\ShieldOptions {
220
  /**
221
  * Provides an array where the key is the root dir, and the value is the specific file types.
222
  * An empty array means all files.
223
- * @return string[]
224
  */
225
- public function getUfcScanDirectories() {
226
  $aDirs = [
227
  path_join( ABSPATH, 'wp-admin' ) => [],
228
  path_join( ABSPATH, 'wp-includes' ) => []
@@ -249,10 +218,7 @@ class Options extends Base\ShieldOptions {
249
  return $this->getOpt( 'enable_unrecognised_file_cleaner_scan', 'disabled' );
250
  }
251
 
252
- /**
253
- * @return bool
254
- */
255
- public function isUfsDeleteFiles() {
256
  return $this->getUnrecognisedFileScannerOption() === 'enabled_delete_only';
257
  }
258
 
@@ -301,10 +267,7 @@ class Options extends Base\ShieldOptions {
301
  return $sPattern;
302
  }
303
 
304
- /**
305
- * @return bool
306
- */
307
- public function isScanCron() {
308
  return (bool)$this->getOpt( 'is_scan_cron' );
309
  }
310
 
@@ -328,28 +291,4 @@ class Options extends Base\ShieldOptions {
328
  }
329
  ) );
330
  }
331
-
332
- /**
333
- * @return string
334
- * @deprecated 10.0
335
- */
336
- public function getDbTable_FileLocker() {
337
- return $this->getCon()->prefixOption( $this->getDef( 'table_name_filelocker' ) );
338
- }
339
-
340
- /**
341
- * @return string
342
- * @deprecated 10.0
343
- */
344
- public function getDbTable_Scanner() {
345
- return $this->getCon()->prefixOption( $this->getDef( 'table_name_scanner' ) );
346
- }
347
-
348
- /**
349
- * @return string
350
- * @deprecated 10.0
351
- */
352
- public function getDbTable_ScanQueue() :string {
353
- return $this->getCon()->prefixOption( $this->getDef( 'table_name_scanqueue' ) );
354
- }
355
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
+ class Options extends BaseShield\Options {
9
 
10
  public function getFilesToLock() :array {
11
  $locks = $this->getOpt( 'file_locker', [] );
19
  /**
20
  * @return int[] - keys are the unique report hash
21
  */
22
+ public function getMalFalsePositiveReports() :array {
23
+ $FP = $this->getOpt( 'mal_fp_reports', [] );
24
+ return is_array( $FP ) ? $FP : [];
25
  }
26
 
27
+ public function isMalFalsePositiveReported( string $hash ) :bool {
28
+ return isset( $this->getMalFalsePositiveReports()[ $hash ] );
 
 
 
 
29
  }
30
 
 
 
 
31
  public function getMalConfidenceBoundary() :int {
32
  return (int)apply_filters( 'icwp_shield_fp_confidence_boundary', 50 );
33
  }
71
  }
72
 
73
  /**
74
+ * @param string $fileName
75
+ * @param string $url
76
  * @return string[]
77
  */
78
+ public function getMalSignatures( string $fileName, string $url ) {
79
+ $FS = Services::WpFs();
80
+ $file = $this->getCon()->getPluginCachePath( $fileName );
81
+ if ( $FS->exists( $file ) ) {
82
+ $sigs = explode( "\n", $FS->getFileContent( $file, true ) );
83
  }
84
  else {
85
+ $sigs = array_filter(
86
  array_map( 'trim',
87
+ explode( "\n", Services::HttpRequest()->getContent( $url ) )
88
  ),
89
+ function ( $line ) {
90
+ return ( ( strpos( $line, '#' ) !== 0 ) && strlen( $line ) > 0 );
91
  }
92
  );
93
 
94
+ if ( !empty( $sigs ) ) {
95
+ $FS->putFileContent( $file, implode( "\n", $sigs ), true );
96
  }
97
  }
98
+ return $sigs;
99
  }
100
 
101
+ public function isMalAutoRepairSurgical() :bool {
 
 
 
102
  return $this->isOpt( 'mal_autorepair_surgical', 'Y' );
103
  }
104
 
105
+ public function isMalUseNetworkIntelligence() :bool {
 
 
 
106
  return $this->getMalConfidenceBoundary() > 0;
107
  }
108
 
109
+ public function isPtgReinstallLinks() :bool {
 
 
 
110
  return $this->isOpt( 'ptg_reinstall_links', 'Y' ) && $this->isPremium();
111
  }
112
 
113
+ public function isRepairFileAuto() :bool {
 
 
 
114
  return count( $this->getRepairAreas() ) > 0;
115
  }
116
 
117
+ public function isRepairFilePlugin() :bool {
 
 
 
118
  return in_array( 'plugin', $this->getRepairAreas() );
119
  }
120
 
121
+ public function isRepairFileTheme() :bool {
 
 
 
122
  return in_array( 'theme', $this->getRepairAreas() );
123
  }
124
 
125
+ public function isRepairFileWP() :bool {
 
 
 
126
  return in_array( 'wp', $this->getRepairAreas() );
127
  }
128
 
129
+ public function isWpvulnAutoupdatesEnabled() :bool {
 
 
 
130
  return $this->isOpt( 'wpvuln_scan_autoupdate', 'Y' );
131
  }
132
 
189
  /**
190
  * Provides an array where the key is the root dir, and the value is the specific file types.
191
  * An empty array means all files.
192
+ * @return array[]
193
  */
194
+ public function getUfcScanDirectories() :array {
195
  $aDirs = [
196
  path_join( ABSPATH, 'wp-admin' ) => [],
197
  path_join( ABSPATH, 'wp-includes' ) => []
218
  return $this->getOpt( 'enable_unrecognised_file_cleaner_scan', 'disabled' );
219
  }
220
 
221
+ public function isUfsDeleteFiles() :bool {
 
 
 
222
  return $this->getUnrecognisedFileScannerOption() === 'enabled_delete_only';
223
  }
224
 
267
  return $sPattern;
268
  }
269
 
270
+ public function isScanCron() :bool {
 
 
 
271
  return (bool)$this->getOpt( 'is_scan_cron' );
272
  }
273
 
291
  }
292
  ) );
293
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
  }
src/lib/src/Modules/HackGuard/Processor.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+
7
+ class Processor extends BaseShield\Processor {
8
+
9
+ protected function run() {
10
+ /** @var ModCon $mod */
11
+ $mod = $this->getMod();
12
+
13
+ $mod->getScansCon()->execute();
14
+
15
+ /** @var Options $opts */
16
+ $opts = $this->getOptions();
17
+ if ( count( $opts->getFilesToLock() ) > 0 ) {
18
+ $mod->getFileLocker()->execute();
19
+ }
20
+ }
21
+
22
+ public function runHourlyCron() {
23
+ ( new Lib\Snapshots\StoreAction\TouchAll() )
24
+ ->setMod( $this->getMod() )
25
+ ->run();
26
+ }
27
+
28
+ public function runDailyCron() {
29
+ ( new Lib\Snapshots\StoreAction\CleanAll() )
30
+ ->setMod( $this->getMod() )
31
+ ->run();
32
+ }
33
+
34
+ public function onWpLoaded() {
35
+ ( new Lib\Snapshots\StoreAction\ScheduleBuildAll() )
36
+ ->setMod( $this->getMod() )
37
+ ->hookBuild();
38
+ }
39
+
40
+ public function onModuleShutdown() {
41
+ ( new Lib\Snapshots\StoreAction\ScheduleBuildAll() )
42
+ ->setMod( $this->getMod() )
43
+ ->schedule();
44
+ }
45
+ }
src/lib/src/Modules/HackGuard/Reporting.php CHANGED
@@ -2,10 +2,10 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\BaseReporting;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\Reports;
7
 
8
- class Reporting extends BaseReporting {
9
 
10
  /**
11
  * @inheritDoc
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\Reports;
7
 
8
+ class Reporting extends Base\Reporting {
9
 
10
  /**
11
  * @inheritDoc
src/lib/src/Modules/HackGuard/Scan/Controller/Base.php CHANGED
@@ -2,18 +2,22 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan\Controller;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
 
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Scans;
9
  use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseResultItem;
10
  use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseResultsSet;
11
  use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseScanActionVO;
12
  use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\Table\BaseEntryFormatter;
 
13
 
14
  abstract class Base {
15
 
16
  use ModConsumer;
 
17
 
18
  const SCAN_SLUG = '';
19
 
@@ -29,65 +33,79 @@ abstract class Base {
29
  public function __construct() {
30
  }
31
 
 
 
 
 
 
 
 
 
 
 
 
32
  public function cleanStalesResults() {
33
- $oResults = ( new HackGuard\Scan\Results\ResultsRetrieve() )
34
  ->setScanController( $this )
35
  ->retrieve();
36
- foreach ( $oResults->getItems() as $oItem ) {
37
- if ( !$this->isResultItemStale( $oItem ) ) {
38
- $oResults->removeItemByHash( $oItem->hash );
39
  }
40
  }
41
  ( new HackGuard\Scan\Results\ResultsDelete() )
42
  ->setScanController( $this )
43
- ->delete( $oResults );
44
  }
45
 
46
- /**
47
- * @param Databases\Scanner\EntryVO $oEntryVo
48
- * @return string
49
- */
50
- public function createFileDownloadLink( $oEntryVo ) {
51
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
52
- $oMod = $this->getMod();
53
- $aActionNonce = $oMod->getNonceActionData( 'scan_file_download' );
54
- $aActionNonce[ 'rid' ] = $oEntryVo->id;
55
- return add_query_arg( $aActionNonce, $oMod->getUrl_AdminPage() );
56
  }
57
 
58
- /**
59
- * @return bool
60
- */
61
- public function getScanHasProblem() {
62
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
63
- $oMod = $this->getMod();
64
- /** @var Databases\Scanner\Select $oSel */
65
- $oSel = $oMod->getDbHandler_ScanResults()->getQuerySelector();
66
- return $oSel->filterByNotIgnored()
67
- ->filterByScan( $this->getSlug() )
68
- ->count() > 0;
 
 
 
 
 
 
 
69
  }
70
 
71
  /**
72
- * @param BaseResultItem|mixed $oItem
73
  * @return bool
74
  */
75
- abstract protected function isResultItemStale( $oItem );
76
 
77
  /**
78
- * @param int|string $sItemId
79
- * @param string $sAction
80
  * @return bool
81
  * @throws \Exception
82
  */
83
- public function executeItemAction( $sItemId, $sAction ) {
84
  $bSuccess = false;
85
 
86
- if ( is_numeric( $sItemId ) ) {
87
  /** @var Databases\Scanner\EntryVO $oEntry */
88
  $oEntry = $this->getScanResultsDbHandler()
89
  ->getQuerySelector()
90
- ->byId( $sItemId );
91
  if ( empty( $oEntry ) ) {
92
  throw new \Exception( 'Item could not be found.' );
93
  }
@@ -98,7 +116,7 @@ abstract class Base {
98
 
99
  $bSuccess = $this->getItemActionHandler()
100
  ->setScanItem( $oItem )
101
- ->process( $sAction );
102
  }
103
 
104
  return $bSuccess;
@@ -108,22 +126,22 @@ abstract class Base {
108
  * @return Scans\Base\BaseResultsSet|mixed
109
  */
110
  protected function getItemsToAutoRepair() {
111
- /** @var Databases\Scanner\Select $oSel */
112
- $oSel = $this->getScanResultsDbHandler()->getQuerySelector();
113
- $oSel->filterByScan( $this->getSlug() )
114
- ->filterByNotIgnored();
115
  return ( new HackGuard\Scan\Results\ConvertBetweenTypes() )
116
  ->setScanController( $this )
117
- ->fromVOsToResultsSet( $oSel->query() );
118
  }
119
 
120
  /**
121
  * @return bool
122
  */
123
  public function updateAllAsNotified() {
124
- /** @var Databases\Scanner\Update $oUpd */
125
- $oUpd = $this->getScanResultsDbHandler()->getQueryUpdater();
126
- return $oUpd->setAllNotifiedForScan( $this->getSlug() );
127
  }
128
 
129
  /**
@@ -131,15 +149,15 @@ abstract class Base {
131
  * @return Scans\Base\BaseResultsSet|mixed
132
  */
133
  public function getAllResults( $bIncludeIgnored = false ) {
134
- /** @var Databases\Scanner\Select $oSel */
135
- $oSel = $this->getScanResultsDbHandler()->getQuerySelector();
136
- $oSel->filterByScan( $this->getSlug() );
137
  if ( !$bIncludeIgnored ) {
138
- $oSel->filterByNotIgnored();
139
  }
140
  return ( new HackGuard\Scan\Results\ConvertBetweenTypes() )
141
  ->setScanController( $this )
142
- ->fromVOsToResultsSet( $oSel->query() );
143
  }
144
 
145
  /**
@@ -172,9 +190,6 @@ abstract class Base {
172
  return $strings->getScanNames()[ static::SCAN_SLUG ];
173
  }
174
 
175
- /**
176
- * @return bool
177
- */
178
  public function isCronAutoRepair() :bool {
179
  return false;
180
  }
@@ -185,22 +200,16 @@ abstract class Base {
185
  return true;
186
  }
187
 
188
- /**
189
- * @return bool
190
- */
191
- public function isReady() {
192
  return $this->isEnabled() && $this->isScanningAvailable();
193
  }
194
 
195
- /**
196
- * @return bool
197
- */
198
- public function isScanningAvailable() {
199
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
200
- $oMod = $this->getMod();
201
- /** @var HackGuard\Options $oOpts */
202
- $oOpts = $this->getOptions();
203
- return $oMod->isModuleEnabled() && ( !$this->isPremiumOnly() || $oOpts->isPremium() );
204
  }
205
 
206
  /**
@@ -252,66 +261,64 @@ abstract class Base {
252
  return $this;
253
  }
254
 
255
- /**
256
- * @return Databases\Scanner\Handler
257
- */
258
- public function getScanResultsDbHandler() {
259
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
260
- $oMod = $this->getMod();
261
- return $oMod->getDbHandler_ScanResults();
262
  }
263
 
264
- /**
265
- * @return string
266
- */
267
- public function getSlug() {
268
  try {
269
- $sSlug = strtolower( ( new \ReflectionClass( $this ) )->getShortName() );
270
  }
271
- catch ( \ReflectionException $oRE ) {
272
- $sSlug = '';
273
  }
274
- return $sSlug;
275
  }
276
 
277
  /**
278
  * @return BaseResultItem|mixed
279
  */
280
  public function getNewResultItem() {
281
- $sClass = $this->getScanNamespace().'ResultItem';
282
- return new $sClass();
283
  }
284
 
285
  /**
286
  * @return BaseResultsSet|mixed
287
  */
288
  public function getNewResultsSet() {
289
- $sClass = $this->getScanNamespace().'ResultsSet';
290
- return new $sClass();
291
  }
292
 
293
  /**
294
  * @return BaseEntryFormatter|mixed
295
  */
296
  public function getTableEntryFormatter() {
297
- $sClass = $this->getScanNamespace().'Table\\EntryFormatter';
298
- /** @var BaseEntryFormatter $oF */
299
- $oF = new $sClass();
300
- return $oF->setScanController( $this )
301
- ->setMod( $this->getMod() )
302
- ->setScanActionVO( $this->getScanActionVO() );
303
  }
304
 
305
- /**
306
- * @return string
307
- */
308
- public function getScanNamespace() {
309
  try {
310
- $sName = ( new \ReflectionClass( $this->getScanActionVO() ) )->getNamespaceName();
311
  }
312
- catch ( \Exception $oE ) {
313
- $sName = __NAMESPACE__;
 
 
 
 
 
 
 
 
314
  }
315
- return rtrim( $sName, '\\' ).'\\';
316
  }
317
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan\Controller;
4
 
5
+ use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\ModCon;
9
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
10
  use FernleafSystems\Wordpress\Plugin\Shield\Scans;
11
  use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseResultItem;
12
  use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseResultsSet;
13
  use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\BaseScanActionVO;
14
  use FernleafSystems\Wordpress\Plugin\Shield\Scans\Base\Table\BaseEntryFormatter;
15
+ use FernleafSystems\Wordpress\Services\Services;
16
 
17
  abstract class Base {
18
 
19
  use ModConsumer;
20
+ use OneTimeExecute;
21
 
22
  const SCAN_SLUG = '';
23
 
33
  public function __construct() {
34
  }
35
 
36
+ protected function run() {
37
+ add_action(
38
+ $this->getCon()->prefix( 'ondemand_scan_'.$this->getSlug() ),
39
+ function () {
40
+ /** @var HackGuard\ModCon $mod */
41
+ $mod = $this->getMod();
42
+ $mod->getScanQueueController()->startScans( [ $this->getSlug() ] );
43
+ }
44
+ );
45
+ }
46
+
47
  public function cleanStalesResults() {
48
+ $results = ( new HackGuard\Scan\Results\ResultsRetrieve() )
49
  ->setScanController( $this )
50
  ->retrieve();
51
+ foreach ( $results->getItems() as $item ) {
52
+ if ( !$this->isResultItemStale( $item ) ) {
53
+ $results->removeItemByHash( $item->hash );
54
  }
55
  }
56
  ( new HackGuard\Scan\Results\ResultsDelete() )
57
  ->setScanController( $this )
58
+ ->delete( $results );
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 {
70
+ /** @var Databases\Events\Select $sel */
71
+ $sel = $this->getCon()
72
+ ->getModule_Events()
73
+ ->getDbHandler_Events()
74
+ ->getQuerySelector();
75
+ $entry = $sel->getLatestForEvent( $this->getSlug().'_scan_run' );
76
+ return ( $entry instanceof Databases\Events\EntryVO ) ? (int)$entry->created_at : 0;
77
+ }
78
+
79
+ public function getScanHasProblem() :bool {
80
+ /** @var ModCon $mod */
81
+ $mod = $this->getMod();
82
+ /** @var Databases\Scanner\Select $sel */
83
+ $sel = $mod->getDbHandler_ScanResults()->getQuerySelector();
84
+ return $sel->filterByNotIgnored()
85
+ ->filterByScan( $this->getSlug() )
86
+ ->count() > 0;
87
  }
88
 
89
  /**
90
+ * @param BaseResultItem|mixed $item
91
  * @return bool
92
  */
93
+ abstract protected function isResultItemStale( $item );
94
 
95
  /**
96
+ * @param int|string $itemID
97
+ * @param string $action
98
  * @return bool
99
  * @throws \Exception
100
  */
101
+ public function executeItemAction( $itemID, $action ) {
102
  $bSuccess = false;
103
 
104
+ if ( is_numeric( $itemID ) ) {
105
  /** @var Databases\Scanner\EntryVO $oEntry */
106
  $oEntry = $this->getScanResultsDbHandler()
107
  ->getQuerySelector()
108
+ ->byId( $itemID );
109
  if ( empty( $oEntry ) ) {
110
  throw new \Exception( 'Item could not be found.' );
111
  }
116
 
117
  $bSuccess = $this->getItemActionHandler()
118
  ->setScanItem( $oItem )
119
+ ->process( $action );
120
  }
121
 
122
  return $bSuccess;
126
  * @return Scans\Base\BaseResultsSet|mixed
127
  */
128
  protected function getItemsToAutoRepair() {
129
+ /** @var Databases\Scanner\Select $sel */
130
+ $sel = $this->getScanResultsDbHandler()->getQuerySelector();
131
+ $sel->filterByScan( $this->getSlug() )
132
+ ->filterByNotIgnored();
133
  return ( new HackGuard\Scan\Results\ConvertBetweenTypes() )
134
  ->setScanController( $this )
135
+ ->fromVOsToResultsSet( $sel->query() );
136
  }
137
 
138
  /**
139
  * @return bool
140
  */
141
  public function updateAllAsNotified() {
142
+ /** @var Databases\Scanner\Update $updater */
143
+ $updater = $this->getScanResultsDbHandler()->getQueryUpdater();
144
+ return $updater->setAllNotifiedForScan( $this->getSlug() );
145
  }
146
 
147
  /**
149
  * @return Scans\Base\BaseResultsSet|mixed
150
  */
151
  public function getAllResults( $bIncludeIgnored = false ) {
152
+ /** @var Databases\Scanner\Select $sel */
153
+ $sel = $this->getScanResultsDbHandler()->getQuerySelector();
154
+ $sel->filterByScan( $this->getSlug() );
155
  if ( !$bIncludeIgnored ) {
156
+ $sel->filterByNotIgnored();
157
  }
158
  return ( new HackGuard\Scan\Results\ConvertBetweenTypes() )
159
  ->setScanController( $this )
160
+ ->fromVOsToResultsSet( $sel->query() );
161
  }
162
 
163
  /**
190
  return $strings->getScanNames()[ static::SCAN_SLUG ];
191
  }
192
 
 
 
 
193
  public function isCronAutoRepair() :bool {
194
  return false;
195
  }
200
  return true;
201
  }
202
 
203
+ public function isReady() :bool {
 
 
 
204
  return $this->isEnabled() && $this->isScanningAvailable();
205
  }
206
 
207
+ public function isScanningAvailable() :bool {
208
+ /** @var ModCon $mod */
209
+ $mod = $this->getMod();
210
+ /** @var HackGuard\Options $opts */
211
+ $opts = $this->getOptions();
212
+ return $mod->isModuleEnabled() && ( !$this->isPremiumOnly() || $opts->isPremium() );
 
 
 
213
  }
214
 
215
  /**
261
  return $this;
262
  }
263
 
264
+ public function getScanResultsDbHandler() :Databases\Scanner\Handler {
265
+ /** @var ModCon $mod */
266
+ $mod = $this->getMod();
267
+ return $mod->getDbHandler_ScanResults();
 
 
 
268
  }
269
 
270
+ public function getSlug() :string {
 
 
 
271
  try {
272
+ $slug = strtolower( ( new \ReflectionClass( $this ) )->getShortName() );
273
  }
274
+ catch ( \ReflectionException $e ) {
275
+ $slug = '';
276
  }
277
+ return $slug;
278
  }
279
 
280
  /**
281
  * @return BaseResultItem|mixed
282
  */
283
  public function getNewResultItem() {
284
+ $class = $this->getScanNamespace().'ResultItem';
285
+ return new $class();
286
  }
287
 
288
  /**
289
  * @return BaseResultsSet|mixed
290
  */
291
  public function getNewResultsSet() {
292
+ $class = $this->getScanNamespace().'ResultsSet';
293
+ return new $class();
294
  }
295
 
296
  /**
297
  * @return BaseEntryFormatter|mixed
298
  */
299
  public function getTableEntryFormatter() {
300
+ $class = $this->getScanNamespace().'Table\\EntryFormatter';
301
+ /** @var BaseEntryFormatter $formatter */
302
+ $formatter = new $class();
303
+ return $formatter->setScanController( $this )
304
+ ->setMod( $this->getMod() )
305
+ ->setScanActionVO( $this->getScanActionVO() );
306
  }
307
 
308
+ public function getScanNamespace() :string {
 
 
 
309
  try {
310
+ $ns = ( new \ReflectionClass( $this->getScanActionVO() ) )->getNamespaceName();
311
  }
312
+ catch ( \Exception $e ) {
313
+ $ns = __NAMESPACE__;
314
+ }
315
+ return rtrim( $ns, '\\' ).'\\';
316
+ }
317
+
318
+ protected function scheduleOnDemandScan( int $nDelay = 3 ) {
319
+ $sHook = $this->getCon()->prefix( 'ondemand_scan_'.$this->getSlug() );
320
+ if ( !wp_next_scheduled( $sHook ) ) {
321
+ wp_schedule_single_event( Services::Request()->ts() + $nDelay, $sHook );
322
  }
 
323
  }
324
  }
src/lib/src/Modules/HackGuard/Scan/Controller/BaseForAssets.php CHANGED
@@ -8,16 +8,16 @@ use FernleafSystems\Wordpress\Services\Services;
8
  abstract class BaseForAssets extends Base {
9
 
10
  /**
11
- * @param Scans\Ptg\ResultItem|Scans\Wpv\ResultItem|Scans\Apc\ResultItem $oItem
12
  * @return bool
13
  */
14
- protected function isResultItemStale( $oItem ) {
15
- if ( $oItem->context == 'plugins' ) {
16
- $oAsset = Services::WpPlugins()->getPluginAsVo( $oItem->slug );
17
  $bAssetExists = empty( $oAsset ) || $oAsset->active;
18
  }
19
  else {
20
- $oAsset = Services::WpThemes()->getThemeAsVo( $oItem->slug );
21
  $bAssetExists = empty( $oAsset ) || ( $oAsset->active || $oAsset->is_parent );
22
  }
23
  return !$bAssetExists;
8
  abstract class BaseForAssets extends Base {
9
 
10
  /**
11
+ * @param Scans\Ptg\ResultItem|Scans\Wpv\ResultItem|Scans\Apc\ResultItem $item
12
  * @return bool
13
  */
14
+ protected function isResultItemStale( $item ) {
15
+ if ( $item->context == 'plugins' ) {
16
+ $oAsset = Services::WpPlugins()->getPluginAsVo( $item->slug );
17
  $bAssetExists = empty( $oAsset ) || $oAsset->active;
18
  }
19
  else {
20
+ $oAsset = Services::WpThemes()->getThemeAsVo( $item->slug );
21
  $bAssetExists = empty( $oAsset ) || ( $oAsset->active || $oAsset->is_parent );
22
  }
23
  return !$bAssetExists;
src/lib/src/Modules/HackGuard/Scan/Controller/Mal.php CHANGED
@@ -52,11 +52,11 @@ class Mal extends Base {
52
  }
53
 
54
  /**
55
- * @param Scans\Mal\ResultItem $oItem
56
  * @return bool
57
  */
58
- protected function isResultItemStale( $oItem ) {
59
- return !Services::WpFs()->exists( $oItem->path_full );
60
  }
61
 
62
  /**
52
  }
53
 
54
  /**
55
+ * @param Scans\Mal\ResultItem $item
56
  * @return bool
57
  */
58
+ protected function isResultItemStale( $item ) {
59
+ return !Services::WpFs()->exists( $item->path_full );
60
  }
61
 
62
  /**
src/lib/src/Modules/HackGuard/Scan/Controller/Ptg.php CHANGED
@@ -11,6 +11,13 @@ class Ptg extends BaseForAssets {
11
 
12
  const SCAN_SLUG = 'ptg';
13
 
 
 
 
 
 
 
 
14
  /**
15
  * @return Scans\Ptg\ResultsSet
16
  */
@@ -18,19 +25,19 @@ class Ptg extends BaseForAssets {
18
  /** @var HackGuard\Options $opts */
19
  $opts = $this->getOptions();
20
 
21
- /** @var Scans\Ptg\ResultsSet $oRes */
22
- $oRes = parent::getItemsToAutoRepair();
23
 
24
  if ( !$opts->isRepairFilePlugin() || !$opts->isRepairFileTheme() ) {
25
  if ( $opts->isRepairFileTheme() ) {
26
- $oRes = $oRes->getResultsForThemesContext();
27
  }
28
  elseif ( $opts->isRepairFilePlugin() ) {
29
- $oRes = $oRes->getResultsForPluginsContext();
30
  }
31
  }
32
 
33
- return $oRes;
34
  }
35
 
36
  public function isCronAutoRepair() :bool {
@@ -40,14 +47,14 @@ class Ptg extends BaseForAssets {
40
  }
41
 
42
  /**
43
- * @param Scans\Mal\ResultItem $oItem
44
  * @return bool
45
  */
46
- protected function isResultItemStale( $oItem ) {
47
  $bStale = false;
48
- $oAsset = ( new WpOrg\Plugin\Files() )->findPluginFromFile( $oItem->path_full );
49
  if ( empty( $oAsset ) ) {
50
- $oAsset = ( new WpOrg\Theme\Files() )->findThemeFromFile( $oItem->path_full );
51
  $bStale = empty( $oAsset );
52
  }
53
  return $bStale;
@@ -60,39 +67,29 @@ class Ptg extends BaseForAssets {
60
  return new Scans\Ptg\Utilities\ItemActionHandler();
61
  }
62
 
63
- /**
64
- * @param string $sBaseFile
65
- * @return bool
66
- */
67
- public function actionPluginReinstall( $sBaseFile ) {
68
- $bSuccess = false;
69
- $oWpPs = Services::WpPlugins();
70
- $oPl = $oWpPs->getPluginAsVo( $sBaseFile );
71
- if ( $oPl->isWpOrg() && $oWpPs->reinstall( $oPl->file ) ) {
72
  try {
73
  ( new HackGuard\Lib\Snapshots\StoreAction\Build() )
74
  ->setMod( $this->getMod() )
75
- ->setAsset( $oPl )
76
  ->run();
77
- $bSuccess = true;
78
  }
79
  catch ( \Exception $e ) {
80
  }
81
  }
82
- return $bSuccess;
83
  }
84
 
85
- /**
86
- * @return bool
87
- */
88
  public function isEnabled() :bool {
89
  return $this->getOptions()->isOpt( 'ptg_enable', 'Y' ) && $this->getOptions()->isOptReqsMet( 'ptg_enable' );
90
  }
91
 
92
- /**
93
- * @return bool
94
- */
95
- public function isScanningAvailable() {
96
  return parent::isScanningAvailable()
97
  && $this->getOptions()->isOptReqsMet( 'ptg_enable' )
98
  && $this->getMod()->canCacheDirWrite();
11
 
12
  const SCAN_SLUG = 'ptg';
13
 
14
+ protected function run() {
15
+ parent::run();
16
+ ( new HackGuard\Scan\Utilities\PtgAddReinstallLinks() )
17
+ ->setScanController( $this )
18
+ ->execute();
19
+ }
20
+
21
  /**
22
  * @return Scans\Ptg\ResultsSet
23
  */
25
  /** @var HackGuard\Options $opts */
26
  $opts = $this->getOptions();
27
 
28
+ /** @var Scans\Ptg\ResultsSet $results */
29
+ $results = parent::getItemsToAutoRepair();
30
 
31
  if ( !$opts->isRepairFilePlugin() || !$opts->isRepairFileTheme() ) {
32
  if ( $opts->isRepairFileTheme() ) {
33
+ $results = $results->getResultsForThemesContext();
34
  }
35
  elseif ( $opts->isRepairFilePlugin() ) {
36
+ $results = $results->getResultsForPluginsContext();
37
  }
38
  }
39
 
40
+ return $results;
41
  }
42
 
43
  public function isCronAutoRepair() :bool {
47
  }
48
 
49
  /**
50
+ * @param Scans\Mal\ResultItem $item
51
  * @return bool
52
  */
53
+ protected function isResultItemStale( $item ) {
54
  $bStale = false;
55
+ $oAsset = ( new WpOrg\Plugin\Files() )->findPluginFromFile( $item->path_full );
56
  if ( empty( $oAsset ) ) {
57
+ $oAsset = ( new WpOrg\Theme\Files() )->findThemeFromFile( $item->path_full );
58
  $bStale = empty( $oAsset );
59
  }
60
  return $bStale;
67
  return new Scans\Ptg\Utilities\ItemActionHandler();
68
  }
69
 
70
+ public function actionPluginReinstall( string $file ) :bool {
71
+ $success = false;
72
+ $WPP = Services::WpPlugins();
73
+ $plugin = $WPP->getPluginAsVo( $file );
74
+ if ( $plugin->isWpOrg() && $WPP->reinstall( $plugin->file ) ) {
 
 
 
 
75
  try {
76
  ( new HackGuard\Lib\Snapshots\StoreAction\Build() )
77
  ->setMod( $this->getMod() )
78
+ ->setAsset( $plugin )
79
  ->run();
80
+ $success = true;
81
  }
82
  catch ( \Exception $e ) {
83
  }
84
  }
85
+ return $success;
86
  }
87
 
 
 
 
88
  public function isEnabled() :bool {
89
  return $this->getOptions()->isOpt( 'ptg_enable', 'Y' ) && $this->getOptions()->isOptReqsMet( 'ptg_enable' );
90
  }
91
 
92
+ public function isScanningAvailable() :bool {
 
 
 
93
  return parent::isScanningAvailable()
94
  && $this->getOptions()->isOptReqsMet( 'ptg_enable' )
95
  && $this->getMod()->canCacheDirWrite();
src/lib/src/Modules/HackGuard/Scan/Controller/Ufc.php CHANGED
@@ -18,11 +18,11 @@ class Ufc extends Base {
18
  }
19
 
20
  /**
21
- * @param Scans\Mal\ResultItem $oItem
22
  * @return bool
23
  */
24
- protected function isResultItemStale( $oItem ) {
25
- return !Services::WpFs()->exists( $oItem->path_full );
26
  }
27
 
28
  public function isCronAutoRepair() :bool {
18
  }
19
 
20
  /**
21
+ * @param Scans\Mal\ResultItem $item
22
  * @return bool
23
  */
24
+ protected function isResultItemStale( $item ) {
25
+ return !Services::WpFs()->exists( $item->path_full );
26
  }
27
 
28
  public function isCronAutoRepair() :bool {
src/lib/src/Modules/HackGuard/Scan/Controller/Wcf.php CHANGED
@@ -18,13 +18,13 @@ class Wcf extends Base {
18
  }
19
 
20
  /**
21
- * @param Scans\Wcf\ResultItem $oItem
22
  * @return bool
23
  */
24
- protected function isResultItemStale( $oItem ) {
25
  $oCFH = Services::CoreFileHashes();
26
- return !$oCFH->isCoreFile( $oItem->path_full )
27
- || Services::CoreFileHashes()->isCoreFileHashValid( $oItem->path_full );
28
  }
29
 
30
  public function isCronAutoRepair() :bool {
18
  }
19
 
20
  /**
21
+ * @param Scans\Wcf\ResultItem $item
22
  * @return bool
23
  */
24
+ protected function isResultItemStale( $item ) {
25
  $oCFH = Services::CoreFileHashes();
26
+ return !$oCFH->isCoreFile( $item->path_full )
27
+ || Services::CoreFileHashes()->isCoreFileHashValid( $item->path_full );
28
  }
29
 
30
  public function isCronAutoRepair() :bool {
src/lib/src/Modules/HackGuard/Scan/Controller/Wpv.php CHANGED
@@ -4,11 +4,57 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan\Control
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Scans;
 
7
 
8
  class Wpv extends BaseForAssets {
9
 
10
  const SCAN_SLUG = 'wpv';
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  /**
13
  * @return Scans\Wpv\Utilities\ItemActionHandler
14
  */
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Scans;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class Wpv extends BaseForAssets {
10
 
11
  const SCAN_SLUG = 'wpv';
12
 
13
+ protected function run() {
14
+ parent::run();
15
+ /** @var HackGuard\Options $opts */
16
+ $opts = $this->getOptions();
17
+
18
+ add_action( 'upgrader_process_complete', function () {
19
+ $this->scheduleOnDemandScan();
20
+ }, 10, 0 );
21
+ add_action( 'deleted_plugin', function () {
22
+ $this->scheduleOnDemandScan();
23
+ }, 10, 0 );
24
+ add_action( 'load-plugins.php', function () {
25
+ ( new HackGuard\Scan\Utilities\WpvAddPluginRows() )
26
+ ->setScanController( $this )
27
+ ->execute();
28
+ }, 10, 2 );
29
+
30
+ if ( $opts->isWpvulnAutoupdatesEnabled() ) {
31
+ add_filter( 'auto_update_plugin', [ $this, 'autoupdateVulnerablePlugins' ], PHP_INT_MAX, 2 );
32
+ }
33
+ }
34
+
35
+ /**
36
+ * @param bool $bDoAutoUpdate
37
+ * @param \stdClass|string $mItem
38
+ * @return bool
39
+ */
40
+ public function autoupdateVulnerablePlugins( $bDoAutoUpdate, $mItem ) {
41
+ $itemFile = Services::WpGeneral()->getFileFromAutomaticUpdateItem( $mItem );
42
+ return $bDoAutoUpdate || count( $this->getPluginVulnerabilities( $itemFile ) ) > 0;
43
+ }
44
+
45
+ /**
46
+ * @param string $file
47
+ * @return Scans\Wpv\WpVulnDb\WpVulnVO[]
48
+ */
49
+ public function getPluginVulnerabilities( $file ) {
50
+ return array_map(
51
+ function ( $item ) {
52
+ return $item->getWpVulnVo();
53
+ },
54
+ $this->getAllResults()->getItemsForSlug( $file )
55
+ );
56
+ }
57
+
58
  /**
59
  * @return Scans\Wpv\Utilities\ItemActionHandler
60
  */
src/lib/src/Modules/HackGuard/Scan/Queue/BuildScanAction.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan\Queue;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
 
6
 
7
  /**
8
  * Class BuildScanAction
@@ -13,22 +14,22 @@ class BuildScanAction {
13
  use Shield\Modules\ModConsumer;
14
 
15
  /**
16
- * @param string $sSlug
17
  * @return Shield\Scans\Base\BaseScanActionVO|mixed
18
  * @throws \Exception
19
  */
20
- public function build( $sSlug ) {
21
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
22
- $oMod = $this->getMod();
23
 
24
- $oAction = $oMod->getScanCon( $sSlug )->getScanActionVO();
25
 
26
  // Build the action definition:
27
 
28
  $sClass = $oAction->getScanNamespace().'BuildScanAction';
29
  /** @var Shield\Scans\Base\BaseBuildScanAction $oBuilder */
30
  $oBuilder = new $sClass();
31
- $oBuilder->setMod( $oMod )
32
  ->setScanActionVO( $oAction )
33
  ->build();
34
  return $oAction;
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan\Queue;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\ModCon;
7
 
8
  /**
9
  * Class BuildScanAction
14
  use Shield\Modules\ModConsumer;
15
 
16
  /**
17
+ * @param string $slug
18
  * @return Shield\Scans\Base\BaseScanActionVO|mixed
19
  * @throws \Exception
20
  */
21
+ public function build( $slug ) {
22
+ /** @var ModCon $mod */
23
+ $mod = $this->getMod();
24
 
25
+ $oAction = $mod->getScanCon( $slug )->getScanActionVO();
26
 
27
  // Build the action definition:
28
 
29
  $sClass = $oAction->getScanNamespace().'BuildScanAction';
30
  /** @var Shield\Scans\Base\BaseBuildScanAction $oBuilder */
31
  $oBuilder = new $sClass();
32
+ $oBuilder->setMod( $mod )
33
  ->setScanActionVO( $oAction )
34
  ->build();
35
  return $oAction;
src/lib/src/Modules/HackGuard/Scan/Queue/CompleteQueue.php CHANGED
@@ -4,6 +4,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan\Queue;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
 
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Scans;
9
  use FernleafSystems\Wordpress\Services\Services;
@@ -21,7 +22,7 @@ class CompleteQueue {
21
  * Take care here not to confuse the 2x DB Handlers
22
  */
23
  public function complete() {
24
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $mod */
25
  $mod = $this->getMod();
26
  $con = $this->getCon();
27
  /** @var Databases\ScanQueue\Handler $oDbH */
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\ModCon;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
9
  use FernleafSystems\Wordpress\Plugin\Shield\Scans;
10
  use FernleafSystems\Wordpress\Services\Services;
22
  * Take care here not to confuse the 2x DB Handlers
23
  */
24
  public function complete() {
25
+ /** @var ModCon $mod */
26
  $mod = $this->getMod();
27
  $con = $this->getCon();
28
  /** @var Databases\ScanQueue\Handler $oDbH */
src/lib/src/Modules/HackGuard/Scan/Queue/Controller.php CHANGED
@@ -38,18 +38,18 @@ class Controller {
38
  * @return bool[]
39
  */
40
  public function getScansRunningStates() {
41
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
42
- $oMod = $this->getMod();
43
- /** @var HackGuard\Options $oOpts */
44
- $oOpts = $this->getOptions();
45
- /** @var ScanQueue\Select $oSel */
46
- $oSel = $oMod->getDbHandler_ScanQueue()->getQuerySelector();
47
 
48
  // First clean the queue:
49
  $this->cleanExpiredFromQueue();
50
 
51
- $aScans = array_fill_keys( $oOpts->getScanSlugs(), false );
52
- foreach ( $oSel->getInitiatedScans() as $sInitScan ) {
53
  $aScans[ $sInitScan ] = true;
54
  }
55
  return $aScans;
@@ -59,15 +59,15 @@ class Controller {
59
  * @return bool
60
  */
61
  protected function cleanExpiredFromQueue() {
62
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
63
- $oMod = $this->getMod();
64
- /** @var HackGuard\Options $oOpts */
65
- $oOpts = $this->getOptions();
66
  $nExpiredBoundary = Services::Request()
67
  ->carbon()
68
- ->subSeconds( $oOpts->getMalQueueExpirationInterval() )->timestamp;
69
  /** @var ScanQueue\Delete $oDel */
70
- $oDel = $oMod->getDbHandler_ScanQueue()->getQueryDeleter();
71
  return $oDel->addWhereOlderThan( $nExpiredBoundary )
72
  ->query();
73
  }
@@ -83,18 +83,17 @@ class Controller {
83
  * @return float
84
  */
85
  public function getScanJobProgress() {
86
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
87
- $oMod = $this->getMod();
88
- $oDbH = $oMod->getDbHandler_ScanQueue();
89
- /** @var ScanQueue\Select $oSel */
90
- $oSel = $oDbH->getQuerySelector();
91
 
92
- $aUnfinished = $oSel->getUnfinishedScans();
93
  $nProgress = 1;
94
- if ( $oSel->getUnfinishedScans() > 0 ) {
95
- $nInitiated = count( $oSel->getInitiatedScans() );
96
  if ( $nInitiated > 0 ) {
97
- $nProgress = 1 - ( count( $aUnfinished )/count( $oSel->getInitiatedScans() ) );
98
  }
99
  }
100
  else {
38
  * @return bool[]
39
  */
40
  public function getScansRunningStates() {
41
+ /** @var HackGuard\ModCon $mod */
42
+ $mod = $this->getMod();
43
+ /** @var HackGuard\Options $opts */
44
+ $opts = $this->getOptions();
45
+ /** @var ScanQueue\Select $sel */
46
+ $sel = $mod->getDbHandler_ScanQueue()->getQuerySelector();
47
 
48
  // First clean the queue:
49
  $this->cleanExpiredFromQueue();
50
 
51
+ $aScans = array_fill_keys( $opts->getScanSlugs(), false );
52
+ foreach ( $sel->getInitiatedScans() as $sInitScan ) {
53
  $aScans[ $sInitScan ] = true;
54
  }
55
  return $aScans;
59
  * @return bool
60
  */
61
  protected function cleanExpiredFromQueue() {
62
+ /** @var HackGuard\ModCon $mod */
63
+ $mod = $this->getMod();
64
+ /** @var HackGuard\Options $opts */
65
+ $opts = $this->getOptions();
66
  $nExpiredBoundary = Services::Request()
67
  ->carbon()
68
+ ->subSeconds( $opts->getMalQueueExpirationInterval() )->timestamp;
69
  /** @var ScanQueue\Delete $oDel */
70
+ $oDel = $mod->getDbHandler_ScanQueue()->getQueryDeleter();
71
  return $oDel->addWhereOlderThan( $nExpiredBoundary )
72
  ->query();
73
  }
83
  * @return float
84
  */
85
  public function getScanJobProgress() {
86
+ /** @var HackGuard\ModCon $mod */
87
+ $mod = $this->getMod();
88
+ /** @var ScanQueue\Select $sel */
89
+ $sel = $mod->getDbHandler_ScanQueue()->getQuerySelector();
 
90
 
91
+ $aUnfinished = $sel->getUnfinishedScans();
92
  $nProgress = 1;
93
+ if ( $sel->getUnfinishedScans() > 0 ) {
94
+ $nInitiated = count( $sel->getInitiatedScans() );
95
  if ( $nInitiated > 0 ) {
96
+ $nProgress = 1 - ( count( $aUnfinished )/count( $sel->getInitiatedScans() ) );
97
  }
98
  }
99
  else {
src/lib/src/Modules/HackGuard/Scan/Queue/QueueProcessor.php CHANGED
@@ -4,6 +4,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan\Queue;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\ScanQueue;
 
7
  use FernleafSystems\Wordpress\Services\Services;
8
  use FernleafSystems\Wordpress\Services\Utilities;
9
 
@@ -151,8 +152,8 @@ class QueueProcessor extends Utilities\BackgroundProcessing\BackgroundProcess {
151
  * @return ScanQueue\Handler
152
  */
153
  public function getDbHandler() {
154
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
155
- $oMod = $this->getMod();
156
- return $oMod->getDbHandler_ScanQueue();
157
  }
158
  }
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\ScanQueue;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\ModCon;
8
  use FernleafSystems\Wordpress\Services\Services;
9
  use FernleafSystems\Wordpress\Services\Utilities;
10
 
152
  * @return ScanQueue\Handler
153
  */
154
  public function getDbHandler() {
155
+ /** @var ModCon $mod */
156
+ $mod = $this->getMod();
157
+ return $mod->getDbHandler_ScanQueue();
158
  }
159
  }
src/lib/src/Modules/HackGuard/Scan/Queue/ScanExecute.php CHANGED
@@ -19,16 +19,16 @@ class ScanExecute {
19
  * @throws \Exception
20
  */
21
  public function execute( $oEntry ) {
22
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
23
- $oMod = $this->getMod();
24
- $oDbH = $oMod->getDbHandler_ScanQueue();
25
  $oTypeConverter = ( new ConvertBetweenTypes() )->setDbHandler( $oDbH );
26
 
27
  $oAction = $oTypeConverter->fromDbEntryToAction( $oEntry );
28
 
29
  $this->getScanner( $oAction )
30
  ->setScanActionVO( $oAction )
31
- ->setMod( $oMod )
32
  ->run();
33
 
34
  if ( $oAction->usleep > 0 ) {
19
  * @throws \Exception
20
  */
21
  public function execute( $oEntry ) {
22
+ /** @var Shield\Modules\HackGuard\ModCon $mod */
23
+ $mod = $this->getMod();
24
+ $oDbH = $mod->getDbHandler_ScanQueue();
25
  $oTypeConverter = ( new ConvertBetweenTypes() )->setDbHandler( $oDbH );
26
 
27
  $oAction = $oTypeConverter->fromDbEntryToAction( $oEntry );
28
 
29
  $this->getScanner( $oAction )
30
  ->setScanActionVO( $oAction )
31
+ ->setMod( $mod )
32
  ->run();
33
 
34
  if ( $oAction->usleep > 0 ) {
src/lib/src/Modules/HackGuard/Scan/Queue/ScanInitiate.php CHANGED
@@ -2,6 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan\Queue;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Scans;
7
 
@@ -16,24 +17,24 @@ class ScanInitiate {
16
 
17
  /**
18
  * Build and Enqueue.
19
- * @param string $sSlug
20
  * @throws \Exception
21
  */
22
- public function init( $sSlug ) {
23
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
24
- $oMod = $this->getMod();
25
- $oDbH = $oMod->getDbHandler_ScanQueue();
26
 
27
- if ( ( new IsScanEnqueued() )->setDbHandler( $oMod->getDbHandler_ScanQueue() )->check( $sSlug ) ) {
28
  throw new \Exception( 'Scan is already running' );
29
  }
30
 
31
  $oAction = ( new BuildScanAction() )
32
- ->setMod( $oMod )
33
- ->build( $sSlug );
34
 
35
  ( new ScanEnqueue() )
36
- ->setDbHandler( $oDbH )
37
  ->setQueueProcessor( $this->getQueueProcessor() )
38
  ->setScanActionVO( $oAction )
39
  ->enqueue();
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan\Queue;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\ModCon;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Scans;
8
 
17
 
18
  /**
19
  * Build and Enqueue.
20
+ * @param string $slug
21
  * @throws \Exception
22
  */
23
+ public function init( $slug ) {
24
+ /** @var ModCon $mod */
25
+ $mod = $this->getMod();
26
+ $dbh = $mod->getDbHandler_ScanQueue();
27
 
28
+ if ( ( new IsScanEnqueued() )->setDbHandler( $dbh )->check( $slug ) ) {
29
  throw new \Exception( 'Scan is already running' );
30
  }
31
 
32
  $oAction = ( new BuildScanAction() )
33
+ ->setMod( $mod )
34
+ ->build( $slug );
35
 
36
  ( new ScanEnqueue() )
37
+ ->setDbHandler( $dbh )
38
  ->setQueueProcessor( $this->getQueueProcessor() )
39
  ->setScanActionVO( $oAction )
40
  ->enqueue();
src/lib/src/Modules/HackGuard/Scan/ScanActionFromSlug.php CHANGED
@@ -1,42 +1,35 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield;
 
6
 
7
- /**
8
- * Class ScanActionFromSlug
9
- * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan
10
- */
11
  class ScanActionFromSlug {
12
 
13
- /**
14
- * @param string $sScanSlug
15
- * @return Shield\Scans\Base\BaseScanActionVO|mixed
16
- */
17
- public static function GetAction( $sScanSlug ) {
18
- $oVO = null;
19
- switch ( $sScanSlug ) {
20
- case 'apc':
21
- $oVO = new Shield\Scans\Apc\ScanActionVO();
22
  break;
23
- case 'mal':
24
- $oVO = new Shield\Scans\Mal\ScanActionVO();
25
  break;
26
- case 'ptg':
27
- $oVO = new Shield\Scans\Ptg\ScanActionVO();
28
  break;
29
- case 'ufc':
30
- $oVO = new Shield\Scans\Ufc\ScanActionVO();
31
  break;
32
- case 'wcf':
33
- $oVO = new Shield\Scans\Wcf\ScanActionVO();
34
  break;
35
- case 'wpv':
36
- $oVO = new Shield\Scans\Wpv\ScanActionVO();
37
  break;
38
  }
39
- $oVO->scan = $sScanSlug;
40
- return $oVO;
41
  }
42
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan\Controller;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Scans;
7
 
 
 
 
 
8
  class ScanActionFromSlug {
9
 
10
+ public static function GetAction( string $slug ) :Scans\Base\BaseScanActionVO {
11
+ $VO = null;
12
+ switch ( $slug ) {
13
+ case Controller\Apc::SCAN_SLUG:
14
+ $VO = new Scans\Apc\ScanActionVO();
 
 
 
 
15
  break;
16
+ case Controller\Mal::SCAN_SLUG:
17
+ $VO = new Scans\Mal\ScanActionVO();
18
  break;
19
+ case Controller\Ptg::SCAN_SLUG:
20
+ $VO = new Scans\Ptg\ScanActionVO();
21
  break;
22
+ case Controller\Ufc::SCAN_SLUG:
23
+ $VO = new Scans\Ufc\ScanActionVO();
24
  break;
25
+ case Controller\Wcf::SCAN_SLUG:
26
+ $VO = new Scans\Wcf\ScanActionVO();
27
  break;
28
+ case Controller\Wpv::SCAN_SLUG:
29
+ $VO = new Scans\Wpv\ScanActionVO();
30
  break;
31
  }
32
+ $VO->scan = $slug;
33
+ return $VO;
34
  }
35
  }
src/lib/src/Modules/HackGuard/Scan/ScansController.php ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan;
4
+
5
+ use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Crons\StandardCron;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
9
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Options;
10
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
11
+ use FernleafSystems\Wordpress\Services\Services;
12
+
13
+ class ScansController {
14
+
15
+ use ModConsumer;
16
+ use OneTimeExecute;
17
+ use StandardCron;
18
+
19
+ private $scanCons;
20
+
21
+ public function __construct() {
22
+ $this->scanCons = [];
23
+ }
24
+
25
+ protected function run() {
26
+ /** @var HackGuard\ModCon $mod */
27
+ $mod = $this->getMod();
28
+ foreach ( $this->getAllScanCons() as $scanCon ) {
29
+ $scanCon->execute();
30
+ }
31
+ $this->setupCron();
32
+ $this->handlePostScanCron();
33
+ }
34
+
35
+ /**
36
+ * @return Controller\Base[]
37
+ */
38
+ public function getAllScanCons() :array {
39
+ /** @var Options $opts */
40
+ $opts = $this->getOptions();
41
+ foreach ( $opts->getScanSlugs() as $slug ) {
42
+ try {
43
+ $this->getScanCon( $slug );
44
+ }
45
+ catch ( \Exception $e ) {
46
+ }
47
+ }
48
+ return $this->scanCons;
49
+ }
50
+
51
+ /**
52
+ * @return int[] - key is scan slug
53
+ */
54
+ public function getLastScansAt() :array {
55
+ /** @var Options $opts */
56
+ $opts = $this->getOptions();
57
+ /** @var Databases\Events\Select $oSel */
58
+ $oSel = $this->getCon()
59
+ ->getModule_Events()
60
+ ->getDbHandler_Events()
61
+ ->getQuerySelector();
62
+ $aEvents = $oSel->getLatestForAllEvents();
63
+
64
+ $aLatest = [];
65
+ foreach ( $opts->getScanSlugs() as $slug ) {
66
+ $event = $slug.'_scan_run';
67
+ $aLatest[ $slug ] = isset( $aEvents[ $event ] ) ? (int)$aEvents[ $event ]->created_at : 0;
68
+ }
69
+ return $aLatest;
70
+ }
71
+
72
+ /**
73
+ * @param string $slug
74
+ * @return Controller\Base|mixed
75
+ * @throws \Exception
76
+ */
77
+ public function getScanCon( string $slug ) {
78
+ if ( !isset( $this->scanCons[ $slug ] ) ) {
79
+ $class = __NAMESPACE__.'\\Controller\\'.ucwords( $slug );
80
+ if ( @class_exists( $class ) ) {
81
+ /** @var Controller\Base $obj */
82
+ $obj = new $class();
83
+ $this->scanCons[ $slug ] = $obj->setMod( $this->getMod() );
84
+ }
85
+ else {
86
+ throw new \Exception( 'Scan slug does not have a class: '.$slug );
87
+ }
88
+ }
89
+ return $this->scanCons[ $slug ];
90
+ }
91
+
92
+ private function handlePostScanCron() {
93
+ add_action( $this->getCon()->prefix( 'post_scan' ), function () {
94
+ $this->runAutoRepair();
95
+ } );
96
+ }
97
+
98
+ private function runAutoRepair() {
99
+ /** @var HackGuard\ModCon $mod */
100
+ $mod = $this->getMod();
101
+ /** @var HackGuard\Options $opts */
102
+ $opts = $this->getOptions();
103
+ foreach ( $opts->getScanSlugs() as $sSlug ) {
104
+ $oScanCon = $mod->getScanCon( $sSlug );
105
+ if ( $oScanCon->isCronAutoRepair() ) {
106
+ $oScanCon->runCronAutoRepair();
107
+ }
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Cron callback
113
+ */
114
+ public function runCron() {
115
+ Services::WpGeneral()->getIfAutoUpdatesInstalled() ? $this->resetCron() : $this->cronScan();
116
+ }
117
+
118
+ private function cronScan() {
119
+ /** @var HackGuard\ModCon $mod */
120
+ $mod = $this->getMod();
121
+ /** @var HackGuard\Options $opts */
122
+ $opts = $this->getOptions();
123
+
124
+ if ( $this->getCanScansExecute() ) {
125
+ $aScans = [];
126
+ foreach ( $opts->getScanSlugs() as $sScanSlug ) {
127
+ $oScanCon = $mod->getScanCon( $sScanSlug );
128
+ if ( $oScanCon->isScanningAvailable() && $oScanCon->isEnabled() ) {
129
+ $aScans[] = $sScanSlug;
130
+ }
131
+ }
132
+
133
+ $opts->setIsScanCron( true );
134
+ $mod->saveModOptions()
135
+ ->getScanQueueController()
136
+ ->startScans( $aScans );
137
+ }
138
+ else {
139
+ error_log( 'Shield scans cannot execute.' );
140
+ }
141
+ }
142
+
143
+ /**
144
+ * @return string[]
145
+ */
146
+ public function getReasonsScansCantExecute() :array {
147
+ return array_keys( array_filter( [
148
+ 'reason_not_call_self' => !$this->getCon()->getModule_Plugin()->getCanSiteCallToItself()
149
+ ] ) );
150
+ }
151
+
152
+ public function getCanScansExecute() :bool {
153
+ return count( $this->getReasonsScansCantExecute() ) === 0;
154
+ }
155
+
156
+ protected function getCronFrequency() {
157
+ /** @var HackGuard\Options $opts */
158
+ $opts = $this->getOptions();
159
+ return $opts->getScanFrequency();
160
+ }
161
+
162
+ public function getFirstRunTimestamp() :int {
163
+ $c = Services::Request()->carbon( true );
164
+ $c->addHours( $c->minute < 40 ? 0 : 1 )
165
+ ->minute( $c->minute < 40 ? 45 : 15 )
166
+ ->second( 0 );
167
+
168
+ if ( $this->getCronFrequency() === 1 ) { // If it's a daily scan only, set to 3am by default
169
+ $hour = (int)apply_filters( $this->getCon()->prefix( 'daily_scan_cron_hour' ), 3 );
170
+ if ( $hour < 0 || $hour > 23 ) {
171
+ $hour = 3;
172
+ }
173
+ if ( $c->hour >= $hour ) {
174
+ $c->addDays( 1 );
175
+ }
176
+ $c->hour( $hour );
177
+ }
178
+
179
+ return $c->timestamp;
180
+ }
181
+
182
+ protected function getCronName() :string {
183
+ return $this->getCon()->prefix( $this->getOptions()->getDef( 'cron_all_scans' ) );
184
+ }
185
+ }
src/lib/src/Modules/HackGuard/Scan/Utilities/PtgAddReinstallLinks.php ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
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;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan\Controller;
8
+ use FernleafSystems\Wordpress\Services\Core\VOs\WpPluginVo;
9
+ use FernleafSystems\Wordpress\Services\Services;
10
+
11
+ class PtgAddReinstallLinks {
12
+
13
+ use Controller\ScanControllerConsumer;
14
+ use OneTimeExecute;
15
+
16
+ /**
17
+ * @var int
18
+ */
19
+ private $nColumnsCount;
20
+
21
+ /**
22
+ * @var int
23
+ */
24
+ private $vulnCount;
25
+
26
+ protected function run() {
27
+ add_action( 'plugin_action_links', function ( $links, $file ) {
28
+ if ( is_array( $links ) && is_string( $file ) ) {
29
+ $links = $this->addActionLinkRefresh( $links, $file );
30
+ }
31
+ return $links;
32
+ }, 50, 2 );
33
+ add_action( 'admin_footer', function () {
34
+ $this->printPluginReinstallDialogs();
35
+ } );
36
+ add_action( 'admin_enqueue_scripts', function ( $hook ) {
37
+ if ( $hook === 'plugins.php' ) {
38
+ $this->insertCustomJsVars();
39
+ }
40
+ }, 9 ); // >5 && <10
41
+ }
42
+
43
+ protected function canRun() {
44
+ $scanCon = $this->getScanController();
45
+ /** @var HackGuard\Options $opts */
46
+ $opts = $scanCon->getOptions();
47
+ return $scanCon->isReady() && $opts->isPtgReinstallLinks();
48
+ }
49
+
50
+ private function addActionLinkRefresh( array $links, string $file ) :array {
51
+ $WPP = Services::WpPlugins();
52
+
53
+ $plugin = $WPP->getPluginAsVo( $file );
54
+ if ( $plugin instanceof WpPluginVo && $plugin->isWpOrg() && !$WPP->isUpdateAvailable( $file ) ) {
55
+ $template = '<a href="javascript:void(0)">%s</a>';
56
+ $links[ 'icwp-reinstall' ] = sprintf( $template, __( 'Re-Install', 'wp-simple-firewall' ) );
57
+ }
58
+
59
+ return $links;
60
+ }
61
+
62
+ private function insertCustomJsVars() {
63
+ $scanCon = $this->getScanController();
64
+ wp_localize_script(
65
+ $scanCon->getCon()->prefix( 'global-plugin' ),
66
+ 'icwp_wpsf_vars_hp',
67
+ [
68
+ 'ajax_plugin_reinstall' => $scanCon->getMod()->getAjaxActionData( 'plugin_reinstall' ),
69
+ 'reinstallable' => Services::WpPlugins()->getInstalledWpOrgPluginFiles(),
70
+ 'strings' => [
71
+ 'reinstall_first' => __( 'Re-install First', 'wp-simple-firewall' )
72
+ .'. '.__( 'Then Activate', 'wp-simple-firewall' ),
73
+ 'okay_reinstall' => sprintf( '%s, %s',
74
+ __( 'Yes', 'wp-simple-firewall' ), __( 'Re-Install It', 'wp-simple-firewall' ) ),
75
+ 'activate_only' => __( 'Activate Only', 'wp-simple-firewall' ),
76
+ 'cancel' => __( 'Cancel', 'wp-simple-firewall' ),
77
+ ]
78
+ ]
79
+ );
80
+ wp_enqueue_script( 'jquery-ui-dialog' ); // jquery and jquery-ui should be dependencies, didn't check though...
81
+ wp_enqueue_style( 'wp-jquery-ui-dialog' );
82
+ }
83
+
84
+ private function printPluginReinstallDialogs() {
85
+ $scanCon = $this->getScanController();
86
+ echo $scanCon->getMod()
87
+ ->renderTemplate(
88
+ 'snippets/dialog_plugins_reinstall.twig',
89
+ [
90
+ 'strings' => [
91
+ 'are_you_sure' => __( 'Are you sure?', 'wp-simple-firewll' ),
92
+ 'really_reinstall' => __( 'Really Re-Install Plugin', 'wp-simple-firewll' ),
93
+ 'wp_reinstall' => __( 'WordPress will now download and install the latest available version of this plugin.', 'wp-simple-firewll' ),
94
+ 'in_case' => sprintf( '%s: %s',
95
+ __( 'Note', 'wp-simple-firewall' ),
96
+ __( 'In case of possible failure, it may be better to do this while the plugin is inactive.', 'wp-simple-firewll' )
97
+ ),
98
+ 'reinstall_first' => __( 'Re-install first?', 'wp-simple-firewall' ),
99
+ 'corrupted' => __( "This ensures files for this plugin haven't been corrupted in any way.", 'wp-simple-firewall' ),
100
+ 'choose' => __( "You can choose to 'Activate Only' (not recommended), or close this message to cancel activation.", 'wp-simple-firewall' ),
101
+ 'editing_restricted' => __( 'Editing this option is currently restricted.', 'wp-simple-firewall' ),
102
+ 'download' => sprintf(
103
+ __( 'For best security practices, %s will download and re-install the latest available version of this plugin.', 'wp-simple-firewall' ),
104
+ $scanCon->getCon()->getHumanName()
105
+ )
106
+ ],
107
+ 'js_snippets' => []
108
+ ],
109
+ true
110
+ );
111
+ }
112
+ }
src/lib/src/Modules/HackGuard/Scan/Utilities/WpvAddPluginRows.php ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
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;
9
+
10
+ class WpvAddPluginRows {
11
+
12
+ use Controller\ScanControllerConsumer;
13
+ use OneTimeExecute;
14
+
15
+ /**
16
+ * @var int
17
+ */
18
+ private $nColumnsCount;
19
+
20
+ /**
21
+ * @var int
22
+ */
23
+ private $vulnCount;
24
+
25
+ protected function run() {
26
+ $this->addPluginVulnerabilityRows();
27
+ }
28
+
29
+ protected function canRun() {
30
+ return $this->isWpvulnPluginsHighlightEnabled() && $this->countVulnerablePlugins() > 0;
31
+ }
32
+
33
+ private function isWpvulnPluginsHighlightEnabled() :bool {
34
+ $scanCon = $this->getScanController();
35
+ if ( $scanCon->isEnabled() ) {
36
+ $opt = apply_filters( 'icwp_shield_wpvuln_scan_display', 'securityadmin' );
37
+ }
38
+ else {
39
+ $opt = 'disabled';
40
+ }
41
+ return ( $opt != 'disabled' ) && Services::WpUsers()->isUserAdmin()
42
+ && ( ( $opt != 'securityadmin' ) || $scanCon->getCon()->isPluginAdmin() );
43
+ }
44
+
45
+ private function addPluginVulnerabilityRows() {
46
+ // These 3 add the 'Vulnerable' plugin status view.
47
+ // BUG: when vulnerable is active, only 1 plugin is available to "All" status. don't know fix.
48
+ add_action( 'pre_current_active_plugins', [ $this, 'addVulnerablePluginStatusView' ], 1000 );
49
+ add_filter( 'all_plugins', [ $this, 'filterPluginsToView' ], 1000 );
50
+ add_filter( 'views_plugins', [ $this, 'addPluginsStatusViewLink' ], 1000 );
51
+ add_filter( 'manage_plugins_columns', [ $this, 'fCountColumns' ], 1000 );
52
+ foreach ( Services::WpPlugins()->getInstalledPluginFiles() as $file ) {
53
+ add_action( "after_plugin_row_$file", [ $this, 'attachVulnerabilityWarning' ], 100, 2 );
54
+ }
55
+ }
56
+
57
+ public function addVulnerablePluginStatusView() {
58
+ if ( Services::Request()->query( 'plugin_status' ) == 'vulnerable' ) {
59
+ global $status;
60
+ $status = 'vulnerable';
61
+ }
62
+ add_filter( 'views_plugins', [ $this, 'addPluginsStatusViewLink' ], 1000 );
63
+ }
64
+
65
+ /**
66
+ * FILTER
67
+ * @param array $views
68
+ * @return array
69
+ */
70
+ public function addPluginsStatusViewLink( $views ) {
71
+ global $status;
72
+
73
+ $views[ 'vulnerable' ] = sprintf( "<a href='%s' %s>%s</a>",
74
+ add_query_arg( 'plugin_status', 'vulnerable', 'plugins.php' ),
75
+ ( 'vulnerable' === $status ) ? ' class="current"' : '',
76
+ sprintf( '%s <span class="count">(%s)</span>',
77
+ __( 'Vulnerable', 'wp-simple-firewall' ),
78
+ number_format_i18n( $this->countVulnerablePlugins() )
79
+ )
80
+ );
81
+ return $views;
82
+ }
83
+
84
+ /**
85
+ * FILTER
86
+ * @param array $plugins
87
+ * @return array
88
+ */
89
+ public function filterPluginsToView( $plugins ) {
90
+ if ( Services::Request()->query( 'plugin_status' ) == 'vulnerable' ) {
91
+ /** @var Wpv\ResultsSet $oVulnerableRes */
92
+ $oVulnerableRes = $this->getScanController()->getAllResults();
93
+ global $status;
94
+ $status = 'vulnerable';
95
+ $plugins = array_intersect_key(
96
+ $plugins,
97
+ array_flip( $oVulnerableRes->getUniqueSlugs() )
98
+ );
99
+ }
100
+ return $plugins;
101
+ }
102
+
103
+ /**
104
+ * @param string $pluginFile
105
+ * @param array $pData
106
+ */
107
+ public function attachVulnerabilityWarning( $pluginFile, $pData ) {
108
+ $scanCon = $this->getScanController();
109
+
110
+ $vulns = $scanCon->getPluginVulnerabilities( $pluginFile );
111
+ if ( count( $vulns ) > 0 ) {
112
+ $sOurName = $scanCon->getCon()->getHumanName();
113
+ echo $scanCon->getMod()
114
+ ->renderTemplate(
115
+ 'snippets/plugin-vulnerability.php',
116
+ [
117
+ 'strings' => [
118
+ 'known_vuln' => sprintf( __( '%s has discovered that the currently installed version of the %s plugin has known security vulnerabilities.', 'wp-simple-firewall' ),
119
+ $sOurName, '<strong>'.$pData[ 'Name' ].'</strong>' ),
120
+ 'name' => __( 'Vulnerability Name', 'wp-simple-firewall' ),
121
+ 'type' => __( 'Vulnerability Type', 'wp-simple-firewall' ),
122
+ 'fixed_versions' => __( 'Fixed Versions', 'wp-simple-firewall' ),
123
+ 'more_info' => __( 'More Info', 'wp-simple-firewall' ),
124
+ ],
125
+ 'vulns' => $vulns,
126
+ 'nColspan' => $this->nColumnsCount
127
+ ]
128
+ );
129
+ }
130
+ }
131
+
132
+ /**
133
+ * @param array $cols
134
+ * @return array
135
+ */
136
+ public function fCountColumns( $cols ) {
137
+ if ( !isset( $this->nColumnsCount ) ) {
138
+ $this->nColumnsCount = count( $cols );
139
+ }
140
+ return $cols;
141
+ }
142
+
143
+ private function countVulnerablePlugins() :int {
144
+ if ( !isset( $this->vulnCount ) ) {
145
+ $this->vulnCount = $this->getScanController()
146
+ ->getAllResults()
147
+ ->countUniqueSlugsForPluginsContext();
148
+ }
149
+ return $this->vulnCount;
150
+ }
151
+ }
src/lib/src/Modules/HackGuard/Strings.php CHANGED
@@ -8,13 +8,8 @@ use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class Strings extends Base\Strings {
10
 
11
- /**
12
- * @param string $sSlug
13
- * @return string|null
14
- */
15
- public function getScanName( $sSlug ) {
16
- $aN = $this->getScanNames();
17
- return isset( $aN[ $sSlug ] ) ? $aN[ $sSlug ] : null;
18
  }
19
 
20
  /**
@@ -175,62 +170,62 @@ class Strings extends Base\Strings {
175
  * @throws \Exception
176
  */
177
  public function getOptionStrings( string $key ) :array {
178
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $mod */
179
  $mod = $this->getMod();
180
- $sModName = $mod->getMainFeatureName();
181
 
182
  switch ( $key ) {
183
 
184
  case 'enable_hack_protect' :
185
- $sName = sprintf( __( 'Enable %s Module', 'wp-simple-firewall' ), $sModName );
186
- $sSummary = sprintf( __( 'Enable (or Disable) The %s Module', 'wp-simple-firewall' ), $sModName );
187
- $sDescription = sprintf( __( 'Un-Checking this option will completely disable the %s module.', 'wp-simple-firewall' ), $sModName );
188
  break;
189
 
190
  case 'scan_frequency' :
191
- $sName = __( 'Daily Scan Frequency', 'wp-simple-firewall' );
192
- $sSummary = __( 'Number Of Times To Run All Scans Each Day', 'wp-simple-firewall' );
193
- $sDescription = [
194
  sprintf( '%s: %s', __( 'Default', 'wp-simple-firewall' ), __( 'Once every 24hrs.', 'wp-simple-firewall' ) ),
195
  __( 'To improve security, increase the number of scans per day.', 'wp-simple-firewall' )
196
  ];
197
  break;
198
 
199
  case 'enable_plugin_vulnerabilities_scan' :
200
- $sName = __( 'Vulnerabilities Scanner', 'wp-simple-firewall' );
201
- $sSummary = sprintf( __( 'Daily Cron - %s', 'wp-simple-firewall' ), __( 'Scans Plugins For Known Vulnerabilities', 'wp-simple-firewall' ) );
202
- $sDescription = __( 'Runs a scan of all your plugins against a database of known WordPress plugin vulnerabilities.', 'wp-simple-firewall' );
203
  break;
204
 
205
  case 'enable_wpvuln_scan' :
206
- $sName = __( 'Vulnerability Scanner', 'wp-simple-firewall' );
207
- $sSummary = __( 'Enable The Vulnerability Scanner', 'wp-simple-firewall' );
208
- $sDescription = __( 'Runs a scan of all your plugins against a database of known WordPress vulnerabilities.', 'wp-simple-firewall' );
209
  break;
210
 
211
  case 'wpvuln_scan_autoupdate' :
212
- $sName = __( 'Automatic Updates', 'wp-simple-firewall' );
213
- $sSummary = __( 'Apply Updates Automatically To Vulnerable Plugins', 'wp-simple-firewall' );
214
- $sDescription = __( 'When an update becomes available, automatically apply updates to items with known vulnerabilities.', 'wp-simple-firewall' );
215
  break;
216
 
217
  case 'enable_core_file_integrity_scan' :
218
- $sName = sprintf( __( '%s Core Files', 'wp-simple-firewall' ),
219
  Services::WpGeneral()->isClassicPress() ? 'ClassicPress' : 'WordPress'
220
  );
221
- $sSummary = sprintf( __( 'Scan And Monitor %s Core Files For Changes', 'wp-simple-firewall' ),
222
  Services::WpGeneral()->isClassicPress() ? 'ClassicPress' : 'WordPress'
223
  );
224
- $sDescription = [
225
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Regularly scan your WordPress core files for changes compared to official WordPress files.', 'wp-simple-firewall' ) ),
226
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), __( 'Keep this feature turned on, at all times.', 'wp-simple-firewall' ) )
227
  ];
228
  break;
229
 
230
  case 'mal_scan_enable' :
231
- $sName = __( 'Malware', 'wp-simple-firewall' );
232
- $sSummary = __( 'Scan And Monitor Files For Malware Infections', 'wp-simple-firewall' );
233
- $sDescription = [
234
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Monitor and detect presence of Malware signatures.', 'wp-simple-firewall' ) ),
235
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), __( 'Keep this feature turned on, at all times.', 'wp-simple-firewall' ) ),
236
  sprintf( '%s - %s', __( 'Note', 'wp-simple-firewall' ), __( 'Currently files of the following types are supported:', 'wp-simple-firewall' ) )
@@ -239,24 +234,24 @@ class Strings extends Base\Strings {
239
  break;
240
 
241
  case 'ptg_enable' :
242
- $sName = __( 'Plugins & Themes', 'wp-simple-firewall' );
243
- $sSummary = __( 'Scan And Monitor Plugin & Theme Files For Changes', 'wp-simple-firewall' );
244
 
245
- $sDescription = [
246
  __( "Looks for new files added to plugins or themes, and also for changes to existing files.", 'wp-simple-firewall' ),
247
  sprintf( '%s - %s', __( 'Important', 'wp-simple-firewall' ), __( "Doesn't currently detect missing files.", 'wp-simple-firewall' ) ),
248
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), __( 'Keep this feature turned on, at all times.', 'wp-simple-firewall' ) )
249
  ];
250
  if ( !$mod->canCacheDirWrite() ) {
251
- $sDescription[] = sprintf( __( 'Sorry, this feature is not available because we cannot write to disk at this location: %s', 'wp-simple-firewall' ),
252
  '<code>'.$mod->getPtgSnapsBaseDir().'</code>' );
253
  }
254
  break;
255
 
256
  case 'file_repair_areas' :
257
- $sName = __( 'Automatic File Repair', 'wp-simple-firewall' );
258
- $sSummary = __( 'Automatically Repair Files That Have Changes Or Malware Infection', 'wp-simple-firewall' );
259
- $sDescription = [
260
  __( 'Will attempt to automatically repair files that have been changed or infected with malware.', 'wp-simple-firewall' ),
261
  '- '.__( 'In the case of WordPress, original files will be downloaded from WordPress.org to repair any broken files.', 'wp-simple-firewall' ),
262
  '- '.__( 'In the case of plugins & themes, only those installed from WordPress.org may be repaired.', 'wp-simple-firewall' ),
@@ -266,9 +261,9 @@ class Strings extends Base\Strings {
266
  break;
267
 
268
  case 'file_locker' :
269
- $sName = __( 'File Locker', 'wp-simple-firewall' );
270
- $sSummary = __( 'Lock Files Against Tampering And Changes', 'wp-simple-firewall' );
271
- $sDescription = [
272
  __( 'Detects changes to the files, then lets you examine contents and revert as required.', 'wp-simple-firewall' ),
273
  sprintf( '%s: %s', __( 'Note', 'wp-simple-firewall' ), __( 'Web.Config is only available for Windows/IIS.', 'wp-simple-firewall' ) )
274
  ];
@@ -277,118 +272,118 @@ class Strings extends Base\Strings {
277
  ->setMod( $this->getMod() )
278
  ->loadLocks();
279
  if ( !empty( $aLocks ) ) {
280
- $sDescription[] = __( 'Locked Files', 'wp-simple-firewall' ).':';
281
  foreach ( $aLocks as $oLock ) {
282
- $sDescription[] = sprintf( '<code>%s</code>', $oLock->file );
283
  }
284
  }
285
  break;
286
 
287
  case 'enable_unrecognised_file_cleaner_scan' :
288
- $sName = __( 'Unrecognised Files Scanner', 'wp-simple-firewall' );
289
- $sSummary = __( 'Automatically Scans For Unrecognised Files In Core Directories', 'wp-simple-firewall' );
290
- $sDescription = __( 'Scans for, and automatically deletes, any files in your core WordPress folders that are not part of your WordPress installation.', 'wp-simple-firewall' );
291
  break;
292
 
293
  case 'ufc_scan_uploads' :
294
- $sName = __( 'Scan Uploads', 'wp-simple-firewall' );
295
- $sSummary = __( 'Scan Uploads Folder For PHP and Javascript', 'wp-simple-firewall' );
296
- $sDescription = sprintf( '%s - %s', __( 'Warning', 'wp-simple-firewall' ), __( 'Take care when turning on this option - if you are unsure, leave it disabled.', 'wp-simple-firewall' ) )
297
  .'<br />'.__( 'The Uploads folder is primarily for media, but could be used to store nefarious files.', 'wp-simple-firewall' );
298
  break;
299
 
300
  case 'ufc_exclusions' :
301
- $sName = __( 'File Exclusions', 'wp-simple-firewall' );
302
- $sSummary = __( 'Provide A List Of Files To Be Excluded From The Scan', 'wp-simple-firewall' );
303
  $sDefaults = implode( ', ', $this->getOptions()->getOptDefault( 'ufc_exclusions' ) );
304
- $sDescription = __( 'Take a new line for each file you wish to exclude from the scan.', 'wp-simple-firewall' )
305
  .'<br/><strong>'.__( 'No commas are necessary.', 'wp-simple-firewall' ).'</strong>'
306
  .'<br/>'.sprintf( '%s: %s', __( 'Default', 'wp-simple-firewall' ), $sDefaults );
307
  break;
308
 
309
  case 'ic_enabled' :
310
- $sName = __( 'Enable Integrity Scan', 'wp-simple-firewall' );
311
- $sSummary = __( 'Scans For Critical Changes Made To Your WordPress Site', 'wp-simple-firewall' );
312
- $sDescription = __( 'Detects changes made to your WordPress site outside of WordPress.', 'wp-simple-firewall' );
313
  break;
314
 
315
  case 'ic_users' :
316
- $sName = __( 'Monitor User Accounts', 'wp-simple-firewall' );
317
- $sSummary = __( 'Scans For Critical Changes Made To User Accounts', 'wp-simple-firewall' );
318
- $sDescription = 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=' )
319
  .'<br />'.__( 'An example of this might be some form of SQL Injection attack.', 'wp-simple-firewall' )
320
  .'<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' ) )
321
  .'<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' ) );
322
  break;
323
 
324
  case 'ptg_depth' : /* DELETED */
325
- $sName = __( 'Guard/Scan Depth', 'wp-simple-firewall' );
326
- $sSummary = __( 'How Deep Into The Plugin Directories To Scan And Guard', 'wp-simple-firewall' );
327
- $sDescription = __( 'The Guard normally scans only the top level of a folder. Increasing depth will increase scan times.', 'wp-simple-firewall' )
328
  .'<br/>'.sprintf( __( 'Setting it to %s will remove this limit and all sub-folders will be scanned - not recommended', 'wp-simple-firewall' ), 0 );
329
  break;
330
 
331
  case 'ptg_reinstall_links' :
332
- $sName = __( 'Show Re-Install Links', 'wp-simple-firewall' );
333
- $sSummary = __( 'Show Re-Install Links For Plugins', 'wp-simple-firewall' );
334
- $sDescription = __( "Show links to re-install plugins and offer re-install when activating plugins.", 'wp-simple-firewall' );
335
  break;
336
 
337
  case 'enabled_scan_apc' :
338
- $sName = __( 'Abandoned Plugin Scanner', 'wp-simple-firewall' );
339
- $sSummary = __( 'Enable The Abandoned Plugin Scanner', 'wp-simple-firewall' );
340
- $sDescription = __( "Scan your WordPress.org assets for whether they've been abandoned.", 'wp-simple-firewall' );
341
  break;
342
 
343
  case 'display_apc' :
344
- $sName = __( 'Highlight Plugins', 'wp-simple-firewall' );
345
- $sSummary = __( 'Highlight Abandoned Plugins', 'wp-simple-firewall' );
346
- $sDescription = __( "Abandoned plugins will be highlighted on the main plugins page.", 'wp-simple-firewall' );
347
  break;
348
 
349
  case 'mal_autorepair_core' :
350
- $sName = __( 'Auto-Repair WP Core', 'wp-simple-firewall' );
351
- $sSummary = __( 'Automatically Repair WordPress Core Files', 'wp-simple-firewall' );
352
- $sDescription = __( "Automatically reinstall any core files found to have potential malware.", 'wp-simple-firewall' );
353
  break;
354
 
355
  case 'mal_autorepair_surgical' :
356
- $sName = __( 'Surgical Auto-Repair', 'wp-simple-firewall' );
357
- $sSummary = __( 'Automatically Attempt To Surgically Remove Malware Code', 'wp-simple-firewall' );
358
- $sDescription = __( "Attempts to automatically remove code from infected files.", 'wp-simple-firewall' )
359
  .'<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' ) )
360
  .'<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' ) );
361
  break;
362
 
363
  // REMOVED:
364
  case 'mal_autorepair_plugins' :
365
- $sName = __( 'Auto-Repair WP Plugins', 'wp-simple-firewall' );
366
- $sSummary = __( 'Automatically Repair WordPress.org Plugins', 'wp-simple-firewall' );
367
- $sDescription = __( "Automatically repair any plugin files found to have potential malware.", 'wp-simple-firewall' )
368
  .'<br />'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( 'Only compatible with plugins installed from WordPress.org.', 'wp-simple-firewall' ) )
369
  .'<br />'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( "Also deletes suspected files if they weren't originally distributed with the plugin.", 'wp-simple-firewall' ) );
370
  break;
371
  case 'autorepair_themes' :
372
- $sName = __( 'Auto-Repair WP Themes', 'wp-simple-firewall' );
373
- $sSummary = __( 'Automatically Repair WordPress.org Themes', 'wp-simple-firewall' );
374
- $sDescription = __( "Automatically repair any theme files found to have potential malware.", 'wp-simple-firewall' )
375
  .'<br />'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( 'Only compatible with themes installed from WordPress.org.', 'wp-simple-firewall' ) )
376
  .'<br />'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( "Also deletes suspected files if they weren't originally distributed with the theme.", 'wp-simple-firewall' ) );
377
  break;
378
  case 'wpvuln_scan_display' :
379
- $sName = __( 'Highlight Plugins', 'wp-simple-firewall' );
380
- $sSummary = __( 'Highlight Vulnerable Plugins Upon Display', 'wp-simple-firewall' );
381
- $sDescription = __( 'Vulnerable plugins will be highlighted on the main plugins page.', 'wp-simple-firewall' );
382
  break;
383
  case 'email_files_list' :
384
- $sName = __( 'Email Files List', 'wp-simple-firewall' );
385
- $sSummary = __( 'Scan Notification Emails Should Include Full Listing Of Files', 'wp-simple-firewall' );
386
- $sDescription = __( 'Scanner notification emails will include a summary list of all affected files.', 'wp-simple-firewall' );
387
  break;
388
  case 'mal_fp_confidence' :
389
- $sName = __( 'Ignore False Positives Threshold', 'wp-simple-firewall' );
390
- $sSummary = __( 'Ignore False Positives In Scan Results Automatically', 'wp-simple-firewall' );
391
- $sDescription = __( "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' )
392
  .'<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' )
393
  .' ('.__( "A false positive is similar to when an anti-virus alerts to a file that doesnt have a virus.", 'wp-simple-firewall' ).')'
394
  .'<br />'.__( "The higher the confidence level, the more likely a result is a false positive.", 'wp-simple-firewall' )
@@ -402,16 +397,16 @@ class Strings extends Base\Strings {
402
  .'<br />'.__( 'The more sites that share this information, the stronger and smarter the network becomes.', 'wp-simple-firewall' );
403
  break;
404
  case 'notification_interval' :
405
- $sName = __( 'Repeat Notifications', 'wp-simple-firewall' );
406
- $sSummary = __( 'Item Repeat Notifications Suppression Interval', 'wp-simple-firewall' );
407
- $sDescription = __( 'How long the automated scans should wait before repeating a notification about an item.', 'wp-simple-firewall' )
408
  .'<br/>'.__( 'Specify the number of days to suppress repeat notifications.', 'wp-simple-firewall' )
409
  .'<br/>'.sprintf( '%s: %s', __( 'Note', 'wp-simple-firewall' ), __( 'This is per discovered item or file, not per scan.', 'wp-simple-firewall' ) );
410
  break;
411
  case 'ptg_extensions' :
412
- $sName = __( 'Included File Types', 'wp-simple-firewall' );
413
- $sSummary = __( 'The File Types (by File Extension) Included In The Scan', 'wp-simple-firewall' );
414
- $sDescription = __( 'Take a new line for each file extension.', 'wp-simple-firewall' )
415
  .'<br/>'.__( 'No commas(,) or periods(.) necessary.', 'wp-simple-firewall' )
416
  .'<br/>'.__( 'Remove all extensions to scan all file type (not recommended).', 'wp-simple-firewall' );
417
  break;
@@ -420,9 +415,9 @@ class Strings extends Base\Strings {
420
  }
421
 
422
  return [
423
- 'name' => $sName,
424
- 'summary' => $sSummary,
425
- 'description' => $sDescription,
426
  ];
427
  }
428
 
8
 
9
  class Strings extends Base\Strings {
10
 
11
+ public function getScanName( string $slug ) :string {
12
+ return $this->getScanNames()[ $slug ];
 
 
 
 
 
13
  }
14
 
15
  /**
170
  * @throws \Exception
171
  */
172
  public function getOptionStrings( string $key ) :array {
173
+ /** @var ModCon $mod */
174
  $mod = $this->getMod();
175
+ $modName = $mod->getMainFeatureName();
176
 
177
  switch ( $key ) {
178
 
179
  case 'enable_hack_protect' :
180
+ $name = sprintf( __( 'Enable %s Module', 'wp-simple-firewall' ), $modName );
181
+ $summary = sprintf( __( 'Enable (or Disable) The %s Module', 'wp-simple-firewall' ), $modName );
182
+ $desc = sprintf( __( 'Un-Checking this option will completely disable the %s module.', 'wp-simple-firewall' ), $modName );
183
  break;
184
 
185
  case 'scan_frequency' :
186
+ $name = __( 'Daily Scan Frequency', 'wp-simple-firewall' );
187
+ $summary = __( 'Number Of Times To Run All Scans Each Day', 'wp-simple-firewall' );
188
+ $desc = [
189
  sprintf( '%s: %s', __( 'Default', 'wp-simple-firewall' ), __( 'Once every 24hrs.', 'wp-simple-firewall' ) ),
190
  __( 'To improve security, increase the number of scans per day.', 'wp-simple-firewall' )
191
  ];
192
  break;
193
 
194
  case 'enable_plugin_vulnerabilities_scan' :
195
+ $name = __( 'Vulnerabilities Scanner', 'wp-simple-firewall' );
196
+ $summary = sprintf( __( 'Daily Cron - %s', 'wp-simple-firewall' ), __( 'Scans Plugins For Known Vulnerabilities', 'wp-simple-firewall' ) );
197
+ $desc = __( 'Runs a scan of all your plugins against a database of known WordPress plugin vulnerabilities.', 'wp-simple-firewall' );
198
  break;
199
 
200
  case 'enable_wpvuln_scan' :
201
+ $name = __( 'Vulnerability Scanner', 'wp-simple-firewall' );
202
+ $summary = __( 'Enable The Vulnerability Scanner', 'wp-simple-firewall' );
203
+ $desc = __( 'Runs a scan of all your plugins against a database of known WordPress vulnerabilities.', 'wp-simple-firewall' );
204
  break;
205
 
206
  case 'wpvuln_scan_autoupdate' :
207
+ $name = __( 'Automatic Updates', 'wp-simple-firewall' );
208
+ $summary = __( 'Apply Updates Automatically To Vulnerable Plugins', 'wp-simple-firewall' );
209
+ $desc = __( 'When an update becomes available, automatically apply updates to items with known vulnerabilities.', 'wp-simple-firewall' );
210
  break;
211
 
212
  case 'enable_core_file_integrity_scan' :
213
+ $name = sprintf( __( '%s Core Files', 'wp-simple-firewall' ),
214
  Services::WpGeneral()->isClassicPress() ? 'ClassicPress' : 'WordPress'
215
  );
216
+ $summary = sprintf( __( 'Scan And Monitor %s Core Files For Changes', 'wp-simple-firewall' ),
217
  Services::WpGeneral()->isClassicPress() ? 'ClassicPress' : 'WordPress'
218
  );
219
+ $desc = [
220
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Regularly scan your WordPress core files for changes compared to official WordPress files.', 'wp-simple-firewall' ) ),
221
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), __( 'Keep this feature turned on, at all times.', 'wp-simple-firewall' ) )
222
  ];
223
  break;
224
 
225
  case 'mal_scan_enable' :
226
+ $name = __( 'Malware', 'wp-simple-firewall' );
227
+ $summary = __( 'Scan And Monitor Files For Malware Infections', 'wp-simple-firewall' );
228
+ $desc = [
229
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Monitor and detect presence of Malware signatures.', 'wp-simple-firewall' ) ),
230
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), __( 'Keep this feature turned on, at all times.', 'wp-simple-firewall' ) ),
231
  sprintf( '%s - %s', __( 'Note', 'wp-simple-firewall' ), __( 'Currently files of the following types are supported:', 'wp-simple-firewall' ) )
234
  break;
235
 
236
  case 'ptg_enable' :
237
+ $name = __( 'Plugins & Themes', 'wp-simple-firewall' );
238
+ $summary = __( 'Scan And Monitor Plugin & Theme Files For Changes', 'wp-simple-firewall' );
239
 
240
+ $desc = [
241
  __( "Looks for new files added to plugins or themes, and also for changes to existing files.", 'wp-simple-firewall' ),
242
  sprintf( '%s - %s', __( 'Important', 'wp-simple-firewall' ), __( "Doesn't currently detect missing files.", 'wp-simple-firewall' ) ),
243
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), __( 'Keep this feature turned on, at all times.', 'wp-simple-firewall' ) )
244
  ];
245
  if ( !$mod->canCacheDirWrite() ) {
246
+ $desc[] = sprintf( __( 'Sorry, this feature is not available because we cannot write to disk at this location: %s', 'wp-simple-firewall' ),
247
  '<code>'.$mod->getPtgSnapsBaseDir().'</code>' );
248
  }
249
  break;
250
 
251
  case 'file_repair_areas' :
252
+ $name = __( 'Automatic File Repair', 'wp-simple-firewall' );
253
+ $summary = __( 'Automatically Repair Files That Have Changes Or Malware Infection', 'wp-simple-firewall' );
254
+ $desc = [
255
  __( 'Will attempt to automatically repair files that have been changed or infected with malware.', 'wp-simple-firewall' ),
256
  '- '.__( 'In the case of WordPress, original files will be downloaded from WordPress.org to repair any broken files.', 'wp-simple-firewall' ),
257
  '- '.__( 'In the case of plugins & themes, only those installed from WordPress.org may be repaired.', 'wp-simple-firewall' ),
261
  break;
262
 
263
  case 'file_locker' :
264
+ $name = __( 'File Locker', 'wp-simple-firewall' );
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
  ];
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;
281
 
282
  case 'enable_unrecognised_file_cleaner_scan' :
283
+ $name = __( 'Unrecognised Files Scanner', 'wp-simple-firewall' );
284
+ $summary = __( 'Automatically Scans For Unrecognised Files In Core Directories', 'wp-simple-firewall' );
285
+ $desc = __( 'Scans for, and automatically deletes, any files in your core WordPress folders that are not part of your WordPress installation.', 'wp-simple-firewall' );
286
  break;
287
 
288
  case 'ufc_scan_uploads' :
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' :
296
+ $name = __( 'File Exclusions', 'wp-simple-firewall' );
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' :
305
+ $name = __( 'Enable Integrity Scan', 'wp-simple-firewall' );
306
+ $summary = __( 'Scans For Critical Changes Made To Your WordPress Site', 'wp-simple-firewall' );
307
+ $desc = __( 'Detects changes made to your WordPress site outside of WordPress.', 'wp-simple-firewall' );
308
  break;
309
 
310
  case 'ic_users' :
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' :
327
+ $name = __( 'Show Re-Install Links', 'wp-simple-firewall' );
328
+ $summary = __( 'Show Re-Install Links For Plugins', 'wp-simple-firewall' );
329
+ $desc = __( "Show links to re-install plugins and offer re-install when activating plugins.", 'wp-simple-firewall' );
330
  break;
331
 
332
  case 'enabled_scan_apc' :
333
+ $name = __( 'Abandoned Plugin Scanner', 'wp-simple-firewall' );
334
+ $summary = __( 'Enable The Abandoned Plugin Scanner', 'wp-simple-firewall' );
335
+ $desc = __( "Scan your WordPress.org assets for whether they've been abandoned.", 'wp-simple-firewall' );
336
  break;
337
 
338
  case 'display_apc' :
339
+ $name = __( 'Highlight Plugins', 'wp-simple-firewall' );
340
+ $summary = __( 'Highlight Abandoned Plugins', 'wp-simple-firewall' );
341
+ $desc = __( "Abandoned plugins will be highlighted on the main plugins page.", 'wp-simple-firewall' );
342
  break;
343
 
344
  case 'mal_autorepair_core' :
345
+ $name = __( 'Auto-Repair WP Core', 'wp-simple-firewall' );
346
+ $summary = __( 'Automatically Repair WordPress Core Files', 'wp-simple-firewall' );
347
+ $desc = __( "Automatically reinstall any core files found to have potential malware.", 'wp-simple-firewall' );
348
  break;
349
 
350
  case 'mal_autorepair_surgical' :
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:
359
  case 'mal_autorepair_plugins' :
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' );
375
+ $summary = __( 'Highlight Vulnerable Plugins Upon Display', 'wp-simple-firewall' );
376
+ $desc = __( 'Vulnerable plugins will be highlighted on the main plugins page.', 'wp-simple-firewall' );
377
  break;
378
  case 'email_files_list' :
379
+ $name = __( 'Email Files List', 'wp-simple-firewall' );
380
+ $summary = __( 'Scan Notification Emails Should Include Full Listing Of Files', 'wp-simple-firewall' );
381
+ $desc = __( 'Scanner notification emails will include a summary list of all affected files.', 'wp-simple-firewall' );
382
  break;
383
  case 'mal_fp_confidence' :
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' )
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;
415
  }
416
 
417
  return [
418
+ 'name' => $name,
419
+ 'summary' => $summary,
420
+ 'description' => $desc,
421
  ];
422
  }
423
 
src/lib/src/Modules/HackGuard/UI.php CHANGED
@@ -3,47 +3,28 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Lib\FileLocker\Ops\LoadFileLocks;
8
- use FernleafSystems\Wordpress\Services\Core\VOs\WpPluginVo;
9
  use FernleafSystems\Wordpress\Services\Services;
10
 
11
- class UI extends Base\ShieldUI {
12
 
13
- /**
14
- * @return array
15
- */
16
- public function buildInsightsVars() {
17
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $mod */
18
  $mod = $this->getMod();
19
  /** @var Options $opts */
20
  $opts = $this->getOptions();
21
 
22
- $aLatestScans = array_map(
23
- function ( $nTime ) {
24
- return sprintf(
25
- __( 'Last Scan: %s', 'wp-simple-firewall' ),
26
- ( $nTime > 0 ) ?
27
- Services::Request()->carbon()->setTimestamp( $nTime )->diffForHumans()
28
- : __( 'Never', 'wp-simple-firewall' )
29
- );
30
- },
31
- $mod->getLastScansAt()
32
- );
33
-
34
  $aUiTrack = $mod->getUiTrack();
35
  if ( empty( $aUiTrack[ 'selected_scans' ] ) ) {
36
  $aUiTrack[ 'selected_scans' ] = $opts->getScanSlugs();
37
  }
38
 
39
  // Can Scan Checks:
40
- $aReasonCantScan = $mod->getProcessor()
41
- ->getSubProScanner()
42
- ->getReasonsScansCantExecute();
43
 
44
  /** @var \FernleafSystems\Wordpress\Plugin\Shield\Databases\Scanner\Select $oSelector */
45
  $oSelector = $mod->getDbHandler_ScanResults()->getQuerySelector();
46
- $aData = [
47
  'ajax' => [
48
  'scans_start' => $mod->getAjaxActionData( 'scans_start', true ),
49
  'scans_check' => $mod->getAjaxActionData( 'scans_check', true ),
@@ -87,7 +68,13 @@ class UI extends Base\ShieldUI {
87
  ],
88
  'vars' => [
89
  'initial_check' => $mod->getScanQueueController()->hasRunningScans(),
90
- 'cannot_scan_reasons' => $aReasonCantScan
 
 
 
 
 
 
91
  ],
92
  'scan_results' => [
93
  ],
@@ -169,24 +156,37 @@ class UI extends Base\ShieldUI {
169
  ],
170
  ];
171
 
172
- /** @var Strings $oStrings */
173
- $oStrings = $mod->getStrings();
174
- $aScanNames = $oStrings->getScanNames();
175
- foreach ( $aData[ 'scans' ] as $sSlug => &$aScanData ) {
176
- $oScanCon = $mod->getScanCon( $sSlug );
177
- $aScanData[ 'flags' ][ 'is_available' ] = $oScanCon->isScanningAvailable();
178
- $aScanData[ 'flags' ][ 'is_restricted' ] = !$oScanCon->isScanningAvailable();
179
- $aScanData[ 'flags' ][ 'is_enabled' ] = $oScanCon->isEnabled();
180
- $aScanData[ 'flags' ][ 'is_selected' ] = $oScanCon->isScanningAvailable() && in_array( $sSlug, $aUiTrack[ 'selected_scans' ] );
181
- $aScanData[ 'flags' ][ 'has_last_scan' ] = $mod->getLastScanAt( $sSlug ) > 0;
182
- $aScanData[ 'vars' ][ 'last_scan_at' ] = $aLatestScans[ $sSlug ];
183
- $aScanData[ 'strings' ][ 'title' ] = $aScanNames[ $sSlug ];
184
- $aScanData[ 'hrefs' ][ 'options' ] = $mod->getUrl_DirectLinkToSection( 'section_scan_'.$sSlug );
185
- $aScanData[ 'hrefs' ][ 'please_enable' ] = $mod->getUrl_DirectLinkToSection( 'section_scan_'.$sSlug );
186
- $aScanData[ 'count' ] = $oSelector->countForScan( $sSlug );
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  }
188
 
189
- return $aData;
190
  }
191
 
192
  /**
@@ -205,11 +205,11 @@ class UI extends Base\ShieldUI {
205
  * @return array
206
  */
207
  protected function getFileLockerVars() {
208
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $mod */
209
  $mod = $this->getMod();
210
 
211
  $oLockCon = $mod->getFileLocker();
212
- $oLockLoader = ( new LoadFileLocks() )->setMod( $mod );
213
  $aProblemLocks = $oLockLoader->withProblems();
214
  $aGoodLocks = $oLockLoader->withoutProblems();
215
 
@@ -245,127 +245,26 @@ class UI extends Base\ShieldUI {
245
  * @return array
246
  */
247
  private function getInsightVarsScan_Ptg() {
248
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
249
- $oMod = $this->getMod();
250
- $oReq = Services::Request();
251
 
252
- /** @var \ICWP_WPSF_Processor_HackProtect $oPro */
253
- $oPro = $oMod->getProcessor();
254
- $oProPtg = $oPro->getSubProScanner()->getSubProcessorPtg();
255
  /** @var \FernleafSystems\Wordpress\Plugin\Shield\Databases\Scanner\Select $oSelector */
256
- $oSelector = $oMod->getDbHandler_ScanResults()->getQuerySelector();
257
 
258
  /** @var \FernleafSystems\Wordpress\Plugin\Shield\Databases\Scanner\EntryVO[] $aPtgResults */
259
  $aPtgResults = $oSelector->filterByNotIgnored()
260
  ->filterByScan( 'ptg' )
261
  ->query();
262
- /** @var Shield\Scans\Ptg\ResultsSet $oFullResults */
263
- $oFullResults = ( new Shield\Modules\HackGuard\Scan\Results\ConvertBetweenTypes() )
264
- ->setScanController( $oMod->getScanCon( 'ptg' ) )
265
- ->fromVOsToResultsSet( $aPtgResults );
266
-
267
- // Process Plugins
268
- $aPlugins = $oFullResults->getAllResultsSetsForPluginsContext();
269
- $oWpPlugins = Services::WpPlugins();
270
- foreach ( $aPlugins as $sSlug => $oItemRS ) {
271
- $aItems = $oItemRS->getAllItems();
272
- /** @var \FernleafSystems\Wordpress\Plugin\Shield\Scans\Ptg\ResultItem $oIT */
273
- $oIT = array_pop( $aItems );
274
- $aMeta = $oProPtg->getSnapshotItemMeta( $oIT->slug );
275
- if ( !empty( $aMeta[ 'ts' ] ) ) {
276
- $aMeta[ 'ts' ] = $oReq->carbon()->setTimestamp( $aMeta[ 'ts' ] )->diffForHumans();
277
- }
278
- else {
279
- $aMeta[ 'ts' ] = __( 'unknown', 'wp-simple-firewall' );
280
- }
281
-
282
- $bInstalled = $oWpPlugins->isInstalled( $oIT->slug );
283
- $oPlgn = $oWpPlugins->getPluginAsVo( $oIT->slug );
284
- $bIsWpOrg = $bInstalled && $oPlgn instanceof WpPluginVo && $oPlgn->isWpOrg();
285
- $bHasUpdate = $bIsWpOrg && $oPlgn->hasUpdate();
286
- $aProfile = [
287
- 'id' => $oSelector->filterByHash( $oIT->hash )->first()->id,
288
- 'name' => __( 'unknown', 'wp-simple-firewall' ),
289
- 'version' => __( 'unknown', 'wp-simple-firewall' ),
290
- 'root_dir' => $oWpPlugins->getInstallationDir( $oIT->slug ),
291
- 'slug' => $sSlug,
292
- 'is_wporg' => $bIsWpOrg,
293
- 'can_reinstall' => $bIsWpOrg,
294
- 'can_deactivate' => $bInstalled && ( $sSlug !== $this->getCon()->getPluginBaseFile() ),
295
- 'has_update' => $bHasUpdate,
296
- 'count_files' => $oItemRS->countItems(),
297
- 'date_snapshot' => $aMeta[ 'ts' ],
298
- ];
299
-
300
- if ( $bInstalled ) {
301
- $oP = $oWpPlugins->getPluginAsVo( $oIT->slug );
302
- $aProfile[ 'name' ] = $oP->Name;
303
- $aProfile[ 'version' ] = $oP->Version;
304
- }
305
- else {
306
- // MISSING!
307
- if ( is_array( $aMeta ) ) {
308
- $aProfile[ 'name' ] = isset( $aMeta[ 'name' ] ) ? $aMeta[ 'name' ] : __( 'unknown', 'wp-simple-firewall' );
309
- $aProfile[ 'version' ] = isset( $aMeta[ 'version' ] ) ? $aMeta[ 'version' ] : __( 'unknown', 'wp-simple-firewall' );
310
- }
311
- }
312
- $aProfile[ 'name' ] = sprintf( '%s: %s', __( 'Plugin' ), $aProfile[ 'name' ] );
313
-
314
- $aPlugins[ $sSlug ] = $aProfile;
315
- }
316
-
317
- // Process Themes
318
- $aThemes = $oFullResults->getAllResultsSetsForThemesContext();
319
- $oWpThemes = Services::WpThemes();
320
- foreach ( $aThemes as $sSlug => $oItemRS ) {
321
- $aItems = $oItemRS->getAllItems();
322
- /** @var \FernleafSystems\Wordpress\Plugin\Shield\Scans\Ptg\ResultItem $oIT */
323
- $oIT = array_pop( $aItems );
324
- $aMeta = $oProPtg->getSnapshotItemMeta( $oIT->slug );
325
- if ( !empty( $aMeta[ 'ts' ] ) ) {
326
- $aMeta[ 'ts' ] = $oReq->carbon()->setTimestamp( $aMeta[ 'ts' ] )->diffForHumans();
327
- }
328
- else {
329
- $aMeta[ 'ts' ] = __( 'unknown', 'wp-simple-firewall' );
330
- }
331
-
332
- $bInstalled = $oWpThemes->isInstalled( $oIT->slug );
333
- $bIsWpOrg = $bInstalled && $oWpThemes->isWpOrg( $sSlug );
334
- $bHasUpdate = $bIsWpOrg && $oWpThemes->isUpdateAvailable( $sSlug );
335
- $aProfile = [
336
- 'id' => $oSelector->filterByHash( $oIT->hash )->first()->id,
337
- 'name' => __( 'unknown', 'wp-simple-firewall' ),
338
- 'version' => __( 'unknown', 'wp-simple-firewall' ),
339
- 'root_dir' => __( 'unknown', 'wp-simple-firewall' ),
340
- 'slug' => $sSlug,
341
- 'is_wporg' => $bIsWpOrg,
342
- 'can_reinstall' => $bIsWpOrg,
343
- 'can_deactivate' => false,
344
- 'has_update' => $bHasUpdate,
345
- 'count_files' => $oItemRS->countItems(),
346
- 'date_snapshot' => $aMeta[ 'ts' ],
347
- ];
348
- if ( $bInstalled ) {
349
- $oT = $oWpThemes->getTheme( $oIT->slug );
350
- $aProfile[ 'name' ] = $oT->get( 'Name' );
351
- $aProfile[ 'version' ] = $oT->get( 'Version' );
352
- $aProfile[ 'root_dir' ] = $oWpThemes->getInstallationDir( $oIT->slug );
353
- }
354
- $aProfile[ 'name' ] = sprintf( '%s: %s', __( 'Theme' ), $aProfile[ 'name' ] );
355
-
356
- $aThemes[ $sSlug ] = $aProfile;
357
- }
358
 
359
  return [
360
  'flags' => [
361
- 'has_items' => $oMod->isPtgEnabled() ? $oFullResults->hasItems() : false,
362
  'has_plugins' => !empty( $aPlugins ),
363
  'has_themes' => !empty( $aThemes ),
364
  'show_table' => false,
365
  ],
366
  'hrefs' => [],
367
  'vars' => [],
368
- 'assets' => $oMod->isPtgEnabled() ? array_merge( $aPlugins, $aThemes ) : [],
369
  'strings' => [
370
  'subtitle' => __( "Detects unauthorized changes to plugins/themes", 'wp-simple-firewall' ),
371
  'files_with_problems' => __( 'Files with problems', 'wp-simple-firewall' ),
@@ -403,4 +302,14 @@ class UI extends Base\ShieldUI {
403
 
404
  return $aWarnings;
405
  }
 
 
 
 
 
 
 
 
 
 
406
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
 
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
+ class UI extends BaseShield\UI {
10
 
11
+ public function buildInsightsVars() :array {
12
+ /** @var ModCon $mod */
 
 
 
13
  $mod = $this->getMod();
14
  /** @var Options $opts */
15
  $opts = $this->getOptions();
16
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  $aUiTrack = $mod->getUiTrack();
18
  if ( empty( $aUiTrack[ 'selected_scans' ] ) ) {
19
  $aUiTrack[ 'selected_scans' ] = $opts->getScanSlugs();
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();
27
+ $data = [
28
  'ajax' => [
29
  'scans_start' => $mod->getAjaxActionData( 'scans_start', true ),
30
  'scans_check' => $mod->getAjaxActionData( 'scans_check', true ),
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(),
75
+ 'title' => __( 'Scan Settings', 'wp-simple-firewall' ),
76
+ ],
77
+ ]
78
  ],
79
  'scan_results' => [
80
  ],
156
  ],
157
  ];
158
 
159
+ /** @var Strings $strings */
160
+ $strings = $mod->getStrings();
161
+ $name = $strings->getScanNames();
162
+ foreach ( $data[ 'scans' ] as $slug => &$scData ) {
163
+ try {
164
+ $scon = $mod->getScanCon( $slug );
165
+ }
166
+ catch ( \Exception $e ) {
167
+ continue;
168
+ }
169
+ $lastScanAt = $scon->getLastScanAt();
170
+
171
+ $scData[ 'flags' ][ 'is_available' ] = $scon->isScanningAvailable();
172
+ $scData[ 'flags' ][ 'is_restricted' ] = !$scon->isScanningAvailable();
173
+ $scData[ 'flags' ][ 'is_enabled' ] = $scon->isEnabled();
174
+ $scData[ 'flags' ][ 'is_selected' ] = $scon->isScanningAvailable() && in_array( $slug, $aUiTrack[ 'selected_scans' ] );
175
+ $scData[ 'vars' ][ 'last_scan_at_ts' ] = $lastScanAt;
176
+ $scData[ 'flags' ][ 'has_last_scan' ] = $lastScanAt > 0;
177
+ $scData[ 'vars' ][ 'last_scan_at' ] = sprintf(
178
+ __( 'Last Scan: %s', 'wp-simple-firewall' ),
179
+ ( $lastScanAt > 0 ) ?
180
+ Services::Request()->carbon()->setTimestamp( $lastScanAt )->diffForHumans()
181
+ : __( 'Never', 'wp-simple-firewall' )
182
+ );
183
+ $scData[ 'strings' ][ 'title' ] = $name[ $slug ];
184
+ $scData[ 'hrefs' ][ 'options' ] = $mod->getUrl_DirectLinkToSection( 'section_scan_'.$slug );
185
+ $scData[ 'hrefs' ][ 'please_enable' ] = $mod->getUrl_DirectLinkToSection( 'section_scan_'.$slug );
186
+ $scData[ 'count' ] = $oSelector->countForScan( $slug );
187
  }
188
 
189
+ return $data;
190
  }
191
 
192
  /**
205
  * @return array
206
  */
207
  protected function getFileLockerVars() {
208
+ /** @var ModCon $mod */
209
  $mod = $this->getMod();
210
 
211
  $oLockCon = $mod->getFileLocker();
212
+ $oLockLoader = ( new Lib\FileLocker\Ops\LoadFileLocks() )->setMod( $mod );
213
  $aProblemLocks = $oLockLoader->withProblems();
214
  $aGoodLocks = $oLockLoader->withoutProblems();
215
 
245
  * @return array
246
  */
247
  private function getInsightVarsScan_Ptg() {
248
+ /** @var ModCon $mod */
249
+ $mod = $this->getMod();
 
250
 
 
 
 
251
  /** @var \FernleafSystems\Wordpress\Plugin\Shield\Databases\Scanner\Select $oSelector */
252
+ $oSelector = $mod->getDbHandler_ScanResults()->getQuerySelector();
253
 
254
  /** @var \FernleafSystems\Wordpress\Plugin\Shield\Databases\Scanner\EntryVO[] $aPtgResults */
255
  $aPtgResults = $oSelector->filterByNotIgnored()
256
  ->filterByScan( 'ptg' )
257
  ->query();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
  return [
260
  'flags' => [
261
+ 'has_items' => $mod->isPtgEnabled() ? !empty( $aPtgResults ) : false,
262
  'has_plugins' => !empty( $aPlugins ),
263
  'has_themes' => !empty( $aThemes ),
264
  'show_table' => false,
265
  ],
266
  'hrefs' => [],
267
  'vars' => [],
 
268
  'strings' => [
269
  'subtitle' => __( "Detects unauthorized changes to plugins/themes", 'wp-simple-firewall' ),
270
  'files_with_problems' => __( 'Files with problems', 'wp-simple-firewall' ),
302
 
303
  return $aWarnings;
304
  }
305
+
306
+ protected function getSettingsRelatedLinks() :array {
307
+ $modInsights = $this->getCon()->getModule_Insights();
308
+ return [
309
+ [
310
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'scans' ),
311
+ 'title' => __( 'Run Scans', 'wp-simple-firewall' ),
312
+ ]
313
+ ];
314
+ }
315
  }
src/lib/src/Modules/Headers/Insights/OverviewCards.php CHANGED
@@ -8,7 +8,7 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Headers\Options;
8
  class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
9
 
10
  public function build() :array {
11
- /** @var \ICWP_WPSF_FeatureHandler_Headers $mod */
12
  $mod = $this->getMod();
13
  /** @var Options $opts */
14
  $opts = $this->getOptions();
8
  class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
9
 
10
  public function build() :array {
11
+ /** @var Shield\Modules\Headers\ModCon $mod */
12
  $mod = $this->getMod();
13
  /** @var Options $opts */
14
  $opts = $this->getOptions();
src/lib/src/Modules/Headers/ModCon.php ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
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
+
15
+ private function cleanCustomRules() {
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
+ }
src/lib/src/Modules/Headers/Options.php CHANGED
@@ -2,15 +2,13 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Headers;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
 
7
- class Options extends Base\ShieldOptions {
8
 
9
- /**
10
- * @return array
11
- */
12
- public function getCspCustomRules() {
13
- return $this->isPremium() ? $this->getOpt( 'xcsp_custom' ) : [];
14
  }
15
 
16
  /**
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Headers;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
 
7
+ class Options extends BaseShield\Options {
8
 
9
+ public function getCspCustomRules() :array {
10
+ $csp = $this->getOpt( 'xcsp_custom' );
11
+ return $this->isPremium() && is_array( $csp ) ? $csp : [];
 
 
12
  }
13
 
14
  /**
src/lib/src/Modules/Headers/Processor.php ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Headers;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+
7
+ class Processor extends BaseShield\Processor {
8
+
9
+ /**
10
+ * @var bool
11
+ */
12
+ private $bHeadersPushed;
13
+
14
+ /**
15
+ * @var array
16
+ */
17
+ private $headers;
18
+
19
+ protected function run() {
20
+ if ( $this->getPushHeadersEarly() ) {
21
+ $this->sendHeaders();
22
+ }
23
+ else {
24
+ add_filter( 'wp_headers', [ $this, 'addToHeaders' ], PHP_INT_MAX );
25
+ add_action( 'send_headers', [ $this, 'sendHeaders' ], PHP_INT_MAX, 0 );
26
+ }
27
+ }
28
+
29
+ protected function getPushHeadersEarly() :bool {
30
+ return defined( 'WPCACHEHOME' ); //WP Super Cache
31
+ }
32
+
33
+ /**
34
+ * Tries to ensure duplicate headers are not sent. Previously sent/supplied headers take priority.
35
+ * @param array $wpHeaders
36
+ * @return array
37
+ */
38
+ public function addToHeaders( $wpHeaders ) {
39
+
40
+ if ( !$this->isHeadersPushed() ) {
41
+
42
+ if ( !is_array( $wpHeaders ) ) {
43
+ $wpHeaders = [];
44
+ }
45
+
46
+ $alreadySent = array_map(
47
+ function ( $header ) {
48
+ return strtolower( trim( $header ) );
49
+ },
50
+ array_keys( $wpHeaders )
51
+ );
52
+ foreach ( $this->gatherSecurityHeaders() as $header => $value ) {
53
+ if ( !in_array( strtolower( $header ), $alreadySent ) ) {
54
+ $wpHeaders[ $header ] = $value;
55
+ }
56
+ }
57
+ $this->setHeadersPushed( true );
58
+ }
59
+ return $wpHeaders;
60
+ }
61
+
62
+ /**
63
+ * Tries to ensure duplicate headers are not sent.
64
+ */
65
+ public function sendHeaders() {
66
+ if ( !$this->isHeadersPushed() ) {
67
+ $aAlreadySent = array_map( 'strtolower', array_keys( $this->getAlreadySentHeaders() ) );
68
+ foreach ( $this->gatherSecurityHeaders() as $sName => $sValue ) {
69
+ if ( !in_array( strtolower( $sName ), $aAlreadySent ) ) {
70
+ @header( sprintf( '%s: %s', $sName, $sValue ) );
71
+ }
72
+ }
73
+ $this->setHeadersPushed( true );
74
+ }
75
+ }
76
+
77
+ /**
78
+ * @return string[] - array of all previously sent headers. Keys are header names, values are header values.
79
+ */
80
+ private function getAlreadySentHeaders() {
81
+ $headers = [];
82
+
83
+ if ( function_exists( 'headers_list' ) ) {
84
+ $sent = headers_list();
85
+ if ( is_array( $sent ) ) {
86
+ foreach ( $sent as $header ) {
87
+ if ( strpos( $header, ':' ) ) {
88
+ list( $key, $value ) = array_map( 'trim', explode( ':', $header, 2 ) );
89
+ $headers[ $key ] = $value;
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ return $headers;
96
+ }
97
+
98
+ private function getXFrameHeader() :array {
99
+ switch ( $this->getOptions()->getOpt( 'x_frame' ) ) {
100
+ case 'on_sameorigin':
101
+ $xFrame = 'SAMEORIGIN';
102
+ break;
103
+ case 'on_deny':
104
+ $xFrame = 'DENY';
105
+ break;
106
+ default:
107
+ $xFrame = '';
108
+ break;
109
+ }
110
+ return empty( $xFrame ) ? [] : [ 'x-frame-options' => $xFrame ];
111
+ }
112
+
113
+ private function getXssProtectionHeader() :array {
114
+ return [ 'X-XSS-Protection' => '1; mode=block' ];
115
+ }
116
+
117
+ private function getContentTypeOptionHeader() :array {
118
+ return [ 'X-Content-Type-Options' => 'nosniff' ];
119
+ }
120
+
121
+ private function getReferrerPolicyHeader() :array {
122
+ /** @var Options $opts */
123
+ $opts = $this->getOptions();
124
+ return [ 'Referrer-Policy' => $opts->getReferrerPolicyValue() ];
125
+ }
126
+
127
+ private function setContentSecurityPolicyHeader() :array {
128
+ /** @var Options $opts */
129
+ $opts = $this->getOptions();
130
+
131
+ $aDefaultSrcDirectives = [];
132
+
133
+ if ( $opts->isOpt( 'xcsp_self', 'Y' ) ) {
134
+ $aDefaultSrcDirectives[] = "'self'";
135
+ }
136
+ if ( $opts->isOpt( 'xcsp_data', 'Y' ) ) {
137
+ $aDefaultSrcDirectives[] = "data:";
138
+ }
139
+ if ( $opts->isOpt( 'xcsp_inline', 'Y' ) ) {
140
+ $aDefaultSrcDirectives[] = "'unsafe-inline'";
141
+ }
142
+ if ( $opts->isOpt( 'xcsp_eval', 'Y' ) ) {
143
+ $aDefaultSrcDirectives[] = "'unsafe-eval'";
144
+ }
145
+ if ( $opts->isOpt( 'xcsp_https', 'Y' ) ) {
146
+ $aDefaultSrcDirectives[] = "https:";
147
+ }
148
+
149
+ $aDefaultSrcDirectives[] = implode( " ", $opts->getOpt( 'xcsp_hosts', [] ) );
150
+
151
+ $rules = $opts->getCspCustomRules();
152
+ array_unshift( $rules, sprintf( 'default-src %s;', implode( " ", $aDefaultSrcDirectives ) ) );
153
+ return [ 'Content-Security-Policy' => implode( ' ', $rules ) ];
154
+ }
155
+
156
+ private function gatherSecurityHeaders() :array {
157
+ /** @var Options $opts */
158
+ $opts = $this->getOptions();
159
+
160
+ if ( $opts->isReferrerPolicyEnabled() ) {
161
+ $this->addHeader( $this->getReferrerPolicyHeader() );
162
+ }
163
+ if ( $opts->isEnabledXFrame() ) {
164
+ $this->addHeader( $this->getXFrameHeader() );
165
+ }
166
+ if ( $opts->isEnabledXssProtection() ) {
167
+ $this->addHeader( $this->getXssProtectionHeader() );
168
+ }
169
+ if ( $opts->isEnabledContentTypeHeader() ) {
170
+ $this->addHeader( $this->getContentTypeOptionHeader() );
171
+ }
172
+ if ( $opts->isEnabledContentSecurityPolicy() ) {
173
+ $this->addHeader( $this->setContentSecurityPolicyHeader() );
174
+ }
175
+ return $this->getHeaders();
176
+ }
177
+
178
+ private function getHeaders() :array {
179
+ if ( !isset( $this->headers ) || !is_array( $this->headers ) ) {
180
+ $this->headers = [];
181
+ }
182
+ return array_unique( $this->headers );
183
+ }
184
+
185
+ private function addHeader( array $header ) {
186
+ if ( !empty( $header ) && is_array( $header ) ) {
187
+ $this->headers = array_merge( $this->getHeaders(), $header );
188
+ }
189
+ }
190
+
191
+ private function isHeadersPushed() :bool {
192
+ return (bool)$this->bHeadersPushed;
193
+ }
194
+
195
+ private function setHeadersPushed( bool $pushed ) :self {
196
+ $this->bHeadersPushed = $pushed;
197
+ return $this;
198
+ }
199
+ }
src/lib/src/Modules/Headers/UI.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Headers;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
 
7
- class UI extends Base\ShieldUI {
8
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Headers;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
 
7
+ class UI extends BaseShield\UI {
8
  }
src/lib/src/Modules/IPs/AdminNotices.php CHANGED
@@ -3,33 +3,30 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
 
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class AdminNotices extends Shield\Modules\Base\AdminNotices {
9
 
10
  /**
11
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
12
- * @throws \Exception
13
  */
14
- protected function processNotice( $oNotice ) {
15
 
16
- switch ( $oNotice->id ) {
17
 
18
  case 'visitor-whitelisted':
19
- $this->buildNotice_VisitorWhitelisted( $oNotice );
20
  break;
21
 
22
  default:
23
- parent::processNotice( $oNotice );
24
  break;
25
  }
26
  }
27
 
28
- /**
29
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
30
- */
31
- private function buildNotice_VisitorWhitelisted( $oNotice ) {
32
- $oNotice->render_data = [
33
  'notice_attributes' => [],
34
  'strings' => [
35
  'title' => sprintf(
@@ -46,24 +43,20 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
46
  ];
47
  }
48
 
49
- /**
50
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
51
- * @return bool
52
- */
53
- protected function isDisplayNeeded( $oNotice ) {
54
- /** @var \ICWP_WPSF_FeatureHandler_Ips $oMod */
55
- $oMod = $this->getMod();
56
 
57
- switch ( $oNotice->id ) {
58
 
59
  case 'visitor-whitelisted':
60
- $bNeeded = $oMod->isVisitorWhitelisted();
61
  break;
62
 
63
  default:
64
- $bNeeded = parent::isDisplayNeeded( $oNotice );
65
  break;
66
  }
67
- return $bNeeded;
68
  }
69
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\AdminNotices\NoticeVO;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class AdminNotices extends Shield\Modules\Base\AdminNotices {
10
 
11
  /**
12
+ * @inheritDoc
 
13
  */
14
+ protected function processNotice( NoticeVO $notice ) {
15
 
16
+ switch ( $notice->id ) {
17
 
18
  case 'visitor-whitelisted':
19
+ $this->buildNotice_VisitorWhitelisted( $notice );
20
  break;
21
 
22
  default:
23
+ parent::processNotice( $notice );
24
  break;
25
  }
26
  }
27
 
28
+ private function buildNotice_VisitorWhitelisted( NoticeVO $notice ) {
29
+ $notice->render_data = [
 
 
 
30
  'notice_attributes' => [],
31
  'strings' => [
32
  'title' => sprintf(
43
  ];
44
  }
45
 
46
+ protected function isDisplayNeeded( NoticeVO $notice ) :bool {
47
+ /** @var ModCon $mod */
48
+ $mod = $this->getMod();
 
 
 
 
49
 
50
+ switch ( $notice->id ) {
51
 
52
  case 'visitor-whitelisted':
53
+ $needed = $mod->isVisitorWhitelisted();
54
  break;
55
 
56
  default:
57
+ $needed = parent::isDisplayNeeded( $notice );
58
  break;
59
  }
60
+ return $needed;
61
  }
62
  }
src/lib/src/Modules/IPs/AjaxHandler.php CHANGED
@@ -3,38 +3,44 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
 
6
  use FernleafSystems\Wordpress\Services\Services;
 
7
 
8
- class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
9
 
10
  protected function processAjaxAction( string $action ) :array {
11
 
12
  switch ( $action ) {
13
  case 'ip_insert':
14
- $aResponse = $this->ajaxExec_AddIp();
15
  break;
16
 
17
  case 'ip_delete':
18
- $aResponse = $this->ajaxExec_IpDelete();
19
  break;
20
 
21
  case 'render_table_ip':
22
- $aResponse = $this->ajaxExec_BuildTableIps();
23
  break;
24
 
25
  case 'build_ip_analyse':
26
- $aResponse = $this->ajaxExec_BuildIpAnalyse();
 
 
 
 
27
  break;
28
 
29
  default:
30
- $aResponse = parent::processAjaxAction( $action );
31
  }
32
 
33
- return $aResponse;
34
  }
35
 
36
  private function ajaxExec_AddIp() :array {
37
- /** @var \ICWP_WPSF_FeatureHandler_Ips $mod */
38
  $mod = $this->getMod();
39
  $oIpServ = Services::IP();
40
 
@@ -43,17 +49,17 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
43
  $bSuccess = false;
44
  $sMessage = __( "IP address wasn't added to the list", 'wp-simple-firewall' );
45
 
46
- $sIp = preg_replace( '#[^/:.a-f\d]#i', '', ( isset( $aFormParams[ 'ip' ] ) ? $aFormParams[ 'ip' ] : '' ) );
47
  $sList = isset( $aFormParams[ 'list' ] ) ? $aFormParams[ 'list' ] : '';
48
 
49
- $bAcceptableIp = $oIpServ->isValidIp( $sIp )
50
- || $oIpServ->isValidIp4Range( $sIp )
51
- || $oIpServ->isValidIp6Range( $sIp );
52
 
53
  $bIsBlackList = $sList != $mod::LIST_MANUAL_WHITE;
54
 
55
  // TODO: Bring this IP verification out of here and make it more accessible
56
- if ( empty( $sIp ) ) {
57
  $sMessage = __( "IP address not provided", 'wp-simple-firewall' );
58
  }
59
  elseif ( empty( $sList ) ) {
@@ -65,22 +71,22 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
65
  elseif ( $bIsBlackList && !$mod->isPremium() ) {
66
  $sMessage = __( "Please upgrade to Pro if you'd like to add IPs to the black list manually.", 'wp-simple-firewall' );
67
  }
68
- elseif ( $bIsBlackList && $oIpServ->checkIp( $oIpServ->getRequestIp(), $sIp ) ) {
69
  $sMessage = __( "Manually black listing your current IP address is not supported.", 'wp-simple-firewall' );
70
  }
71
- elseif ( $bIsBlackList && in_array( $sIp, Services::IP()->getServerPublicIPs() ) ) {
72
  $sMessage = __( "This IP is reserved and can't be blacklisted.", 'wp-simple-firewall' );
73
  }
74
  else {
75
- $sLabel = isset( $aFormParams[ 'label' ] ) ? $aFormParams[ 'label' ] : '';
76
  $oIP = null;
77
  switch ( $sList ) {
78
  case $mod::LIST_MANUAL_WHITE:
79
  try {
80
  $oIP = ( new Shield\Modules\IPs\Lib\Ops\AddIp() )
81
  ->setMod( $mod )
82
- ->setIP( $sIp )
83
- ->toManualWhitelist( $sLabel );
84
  }
85
  catch ( \Exception $oE ) {
86
  }
@@ -90,8 +96,8 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
90
  try {
91
  $oIP = ( new Shield\Modules\IPs\Lib\Ops\AddIp() )
92
  ->setMod( $mod )
93
- ->setIP( $sIp )
94
- ->toManualBlacklist( $sLabel );
95
  }
96
  catch ( \Exception $oE ) {
97
  }
@@ -114,15 +120,15 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
114
  }
115
 
116
  private function ajaxExec_IpDelete() :array {
117
- /** @var \ICWP_WPSF_FeatureHandler_Ips $oMod */
118
- $oMod = $this->getMod();
119
  $bSuccess = false;
120
  $nId = Services::Request()->post( 'rid', -1 );
121
 
122
  if ( !is_numeric( $nId ) || $nId < 0 ) {
123
  $sMessage = __( 'Invalid entry selected', 'wp-simple-firewall' );
124
  }
125
- elseif ( $oMod->getDbHandler_IPs()->getQueryDeleter()->deleteById( $nId ) ) {
126
  $sMessage = __( 'IP address deleted', 'wp-simple-firewall' );
127
  $bSuccess = true;
128
  }
@@ -137,21 +143,106 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
137
  }
138
 
139
  private function ajaxExec_BuildTableIps() :array {
140
- /** @var \ICWP_WPSF_FeatureHandler_Ips $mod */
141
  $mod = $this->getMod();
142
 
143
- $oDbH = $mod->getDbHandler_IPs();
144
- $oDbH->autoCleanDb();
145
 
146
  return [
147
  'success' => true,
148
  'html' => ( new Shield\Tables\Build\Ip() )
149
  ->setMod( $mod )
150
- ->setDbHandler( $oDbH )
151
  ->render()
152
  ];
153
  }
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  private function ajaxExec_BuildIpAnalyse() :array {
156
  try {
157
  $ip = Services::Request()->post( 'fIp', '' );
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
 
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
 
12
  protected function processAjaxAction( string $action ) :array {
13
 
14
  switch ( $action ) {
15
  case 'ip_insert':
16
+ $response = $this->ajaxExec_AddIp();
17
  break;
18
 
19
  case 'ip_delete':
20
+ $response = $this->ajaxExec_IpDelete();
21
  break;
22
 
23
  case 'render_table_ip':
24
+ $response = $this->ajaxExec_BuildTableIps();
25
  break;
26
 
27
  case 'build_ip_analyse':
28
+ $response = $this->ajaxExec_BuildIpAnalyse();
29
+ break;
30
+
31
+ case 'ip_analyse_action':
32
+ $response = $this->ajaxExec_IpAnalyseAction();
33
  break;
34
 
35
  default:
36
+ $response = parent::processAjaxAction( $action );
37
  }
38
 
39
+ return $response;
40
  }
41
 
42
  private function ajaxExec_AddIp() :array {
43
+ /** @var ModCon $mod */
44
  $mod = $this->getMod();
45
  $oIpServ = Services::IP();
46
 
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' ] : '';
54
 
55
+ $bAcceptableIp = $oIpServ->isValidIp( $ip )
56
+ || $oIpServ->isValidIp4Range( $ip )
57
+ || $oIpServ->isValidIp6Range( $ip );
58
 
59
  $bIsBlackList = $sList != $mod::LIST_MANUAL_WHITE;
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 ) ) {
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' ] ?? '';
82
  $oIP = null;
83
  switch ( $sList ) {
84
  case $mod::LIST_MANUAL_WHITE:
85
  try {
86
  $oIP = ( new Shield\Modules\IPs\Lib\Ops\AddIp() )
87
  ->setMod( $mod )
88
+ ->setIP( $ip )
89
+ ->toManualWhitelist( (string)$label );
90
  }
91
  catch ( \Exception $oE ) {
92
  }
96
  try {
97
  $oIP = ( new Shield\Modules\IPs\Lib\Ops\AddIp() )
98
  ->setMod( $mod )
99
+ ->setIP( $ip )
100
+ ->toManualBlacklist( (string)$label );
101
  }
102
  catch ( \Exception $oE ) {
103
  }
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
  }
143
  }
144
 
145
  private function ajaxExec_BuildTableIps() :array {
146
+ /** @var ModCon $mod */
147
  $mod = $this->getMod();
148
 
149
+ $dbh = $mod->getDbHandler_IPs();
150
+ $dbh->autoCleanDb();
151
 
152
  return [
153
  'success' => true,
154
  'html' => ( new Shield\Tables\Build\Ip() )
155
  ->setMod( $mod )
156
+ ->setDbHandler( $dbh )
157
  ->render()
158
  ];
159
  }
160
 
161
+ private function ajaxExec_IpAnalyseAction() :array {
162
+ $req = Services::Request();
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':
190
+ try {
191
+ $success = ( new Ops\AddIp() )
192
+ ->setMod( $this->getMod() )
193
+ ->setIP( $ip )
194
+ ->toManualBlacklist() instanceof Shield\Databases\IPs\EntryVO;
195
+ }
196
+ catch ( \Exception $e ) {
197
+ }
198
+ $msg = $success ? __( 'IP address blocked.', 'wp-simple-firewall' )
199
+ : __( "IP address couldn't be blocked at this time.", 'wp-simple-firewall' );
200
+ break;
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' )
208
+ : __( "IP address couldn't be unblocked at this time.", 'wp-simple-firewall' );
209
+ break;
210
+
211
+ case 'bypass':
212
+ try {
213
+ $success = ( new Ops\AddIp() )
214
+ ->setMod( $this->getMod() )
215
+ ->setIP( $ip )
216
+ ->toManualWhitelist() instanceof Shield\Databases\IPs\EntryVO;
217
+ }
218
+ catch ( \Exception $e ) {
219
+ }
220
+ $msg = $success ? __( 'IP address added to Bypass list.', 'wp-simple-firewall' )
221
+ : __( "IP address couldn't be added to Bypass list at this time.", 'wp-simple-firewall' );
222
+ break;
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;
236
+ }
237
+ }
238
+
239
+ return [
240
+ 'success' => $success,
241
+ 'message' => $msg,
242
+ 'page_reload' => true,
243
+ ];
244
+ }
245
+
246
  private function ajaxExec_BuildIpAnalyse() :array {
247
  try {
248
  $ip = Services::Request()->post( 'fIp', '' );
src/lib/src/Modules/IPs/Components/ImportIpsFromFile.php CHANGED
@@ -11,33 +11,30 @@ class ImportIpsFromFile {
11
  use Shield\Modules\ModConsumer;
12
 
13
  public function run() {
14
- foreach ( [ 'black', 'white' ] as $sType ) {
15
- $this->runFileImport( $sType );
16
  }
17
  }
18
 
19
- /**
20
- * @param string $sType
21
- */
22
- private function runFileImport( $sType ) {
23
- $oFS = Services::WpFs();
24
 
25
- $sImportFile = $oFS->findFileInDir( 'ip_import_'.$sType, $this->getCon()->getPath_Flags() );
26
- if ( $oFS->isFile( $sImportFile ) ) {
27
- $sContent = $oFS->getFileContent( $sImportFile );
28
- if ( !empty( $sContent ) ) {
29
  $oAdd = ( new IPs\Lib\Ops\AddIp() )->setMod( $this->getMod() );
30
- foreach ( array_map( 'trim', explode( "\n", $sContent ) ) as $sIP ) {
31
  $oAdd->setIP( $sIP );
32
  try {
33
- $sType == 'white' ? $oAdd->toManualWhitelist( 'file import' )
34
  : $oAdd->toManualBlacklist( 'file import' );
35
  }
36
- catch ( \Exception $oE ) {
37
  }
38
  }
39
  }
40
- $oFS->deleteFile( $sImportFile );
41
  }
42
  }
43
  }
11
  use Shield\Modules\ModConsumer;
12
 
13
  public function run() {
14
+ foreach ( [ 'black', 'white' ] as $type ) {
15
+ $this->runFileImport( $type );
16
  }
17
  }
18
 
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 ) ) {
26
  $oAdd = ( new IPs\Lib\Ops\AddIp() )->setMod( $this->getMod() );
27
+ foreach ( array_map( 'trim', explode( "\n", $content ) ) as $sIP ) {
28
  $oAdd->setIP( $sIP );
29
  try {
30
+ $type == 'white' ? $oAdd->toManualWhitelist( 'file import' )
31
  : $oAdd->toManualBlacklist( 'file import' );
32
  }
33
+ catch ( \Exception $e ) {
34
  }
35
  }
36
  }
37
+ $FS->deleteFile( $fileImport );
38
  }
39
  }
40
  }
src/lib/src/Modules/IPs/Components/ProcessOffense.php CHANGED
@@ -17,9 +17,9 @@ class ProcessOffense {
17
  use IpAddressConsumer;
18
 
19
  public function run() {
20
- /** @var \ICWP_WPSF_FeatureHandler_Ips $mod */
21
- $mod = $this->getMod();
22
  $con = $this->getCon();
 
 
23
  /** @var IPs\Options $opts */
24
  $opts = $this->getOptions();
25
 
@@ -29,7 +29,7 @@ class ProcessOffense {
29
  ->setIP( $this->getIP() )
30
  ->toAutoBlacklist();
31
  }
32
- catch ( \Exception $oE ) {
33
  $oIP = null;
34
  }
35
 
17
  use IpAddressConsumer;
18
 
19
  public function run() {
 
 
20
  $con = $this->getCon();
21
+ /** @var IPs\ModCon $mod */
22
+ $mod = $this->getMod();
23
  /** @var IPs\Options $opts */
24
  $opts = $this->getOptions();
25
 
29
  ->setIP( $this->getIP() )
30
  ->toAutoBlacklist();
31
  }
32
+ catch ( \Exception $e ) {
33
  $oIP = null;
34
  }
35
 
src/lib/src/Modules/IPs/Components/QueryIpBlock.php CHANGED
@@ -32,10 +32,10 @@ class QueryIpBlock {
32
 
33
  $bIpBlocked = true;
34
 
35
- /** @var \ICWP_WPSF_FeatureHandler_Ips $oMod */
36
- $oMod = $this->getMod();
37
  /** @var Databases\IPs\Update $oUp */
38
- $oUp = $oMod->getDbHandler_IPs()->getQueryUpdater();
39
  $oUp->updateLastAccessAt( $oIP );
40
  }
41
  return $bIpBlocked;
@@ -47,10 +47,10 @@ class QueryIpBlock {
47
  private function getBlockedIpRecord() {
48
  $oBlockIP = null;
49
 
50
- /** @var \ICWP_WPSF_FeatureHandler_Ips $oMod */
51
- $oMod = $this->getMod();
52
  $oIP = ( new IPs\Lib\Ops\LookupIpOnList() )
53
- ->setDbHandler( $oMod->getDbHandler_IPs() )
54
  ->setIP( $this->getIP() )
55
  ->setListTypeBlack()
56
  ->setIsIpBlocked( true )
@@ -61,11 +61,11 @@ class QueryIpBlock {
61
  $oOpts = $this->getOptions();
62
 
63
  // Clean out old IPs as we go so they don't show up in future queries.
64
- if ( $oIP->list == $oMod::LIST_AUTO_BLACK
65
  && $oIP->last_access_at < Services::Request()->ts() - $oOpts->getAutoExpireTime() ) {
66
 
67
  ( new IPs\Lib\Ops\DeleteIp() )
68
- ->setDbHandler( $oMod->getDbHandler_IPs() )
69
  ->setIP( Services::IP()->getRequestIp() )
70
  ->fromBlacklist();
71
  }
32
 
33
  $bIpBlocked = true;
34
 
35
+ /** @var IPs\ModCon $mod */
36
+ $mod = $this->getMod();
37
  /** @var Databases\IPs\Update $oUp */
38
+ $oUp = $mod->getDbHandler_IPs()->getQueryUpdater();
39
  $oUp->updateLastAccessAt( $oIP );
40
  }
41
  return $bIpBlocked;
47
  private function getBlockedIpRecord() {
48
  $oBlockIP = null;
49
 
50
+ /** @var IPs\ModCon $mod */
51
+ $mod = $this->getMod();
52
  $oIP = ( new IPs\Lib\Ops\LookupIpOnList() )
53
+ ->setDbHandler( $mod->getDbHandler_IPs() )
54
  ->setIP( $this->getIP() )
55
  ->setListTypeBlack()
56
  ->setIsIpBlocked( true )
61
  $oOpts = $this->getOptions();
62
 
63
  // Clean out old IPs as we go so they don't show up in future queries.
64
+ if ( $oIP->list == $mod::LIST_AUTO_BLACK
65
  && $oIP->last_access_at < Services::Request()->ts() - $oOpts->getAutoExpireTime() ) {
66
 
67
  ( new IPs\Lib\Ops\DeleteIp() )
68
+ ->setDbHandler( $mod->getDbHandler_IPs() )
69
  ->setIP( Services::IP()->getRequestIp() )
70
  ->fromBlacklist();
71
  }
src/lib/src/Modules/IPs/Components/QueryRemainingOffenses.php CHANGED
@@ -3,9 +3,8 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Components;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\IPs;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops;
8
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Options;
9
 
10
  /**
11
  * Class QueryRemainingOffenses
@@ -20,20 +19,20 @@ class QueryRemainingOffenses {
20
  * @return int
21
  */
22
  public function run() {
23
- /** @var \ICWP_WPSF_FeatureHandler_Ips $oMod */
24
- $oMod = $this->getMod();
25
- $oBlackIp = ( new Ops\LookupIpOnList() )
26
- ->setDbHandler( $oMod->getDbHandler_IPs() )
27
  ->setListTypeBlack()
28
  ->setIP( $this->getIP() )
29
  ->lookup( false );
30
 
31
  $nOffenses = 0;
32
- if ( $oBlackIp instanceof IPs\EntryVO ) {
33
  $nOffenses = (int)$oBlackIp->transgressions;
34
  }
35
 
36
- /** @var Options $oOpts */
37
  $oOpts = $this->getOptions();
38
  return $oOpts->getOffenseLimit() - $nOffenses - 1;
39
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Components;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
 
8
 
9
  /**
10
  * Class QueryRemainingOffenses
19
  * @return int
20
  */
21
  public function run() {
22
+ /** @var IPs\ModCon $mod */
23
+ $mod = $this->getMod();
24
+ $oBlackIp = ( new IPs\Lib\Ops\LookupIpOnList() )
25
+ ->setDbHandler( $mod->getDbHandler_IPs() )
26
  ->setListTypeBlack()
27
  ->setIP( $this->getIP() )
28
  ->lookup( false );
29
 
30
  $nOffenses = 0;
31
+ if ( $oBlackIp instanceof Databases\IPs\EntryVO ) {
32
  $nOffenses = (int)$oBlackIp->transgressions;
33
  }
34
 
35
+ /** @var IPs\Options $oOpts */
36
  $oOpts = $this->getOptions();
37
  return $oOpts->getOffenseLimit() - $nOffenses - 1;
38
  }
src/lib/src/Modules/IPs/Components/UnblockIpByFlag.php CHANGED
@@ -11,19 +11,19 @@ class UnblockIpByFlag {
11
  use Shield\Modules\ModConsumer;
12
 
13
  public function run() {
14
- /** @var \ICWP_WPSF_FeatureHandler_Ips $oMod */
15
- $oMod = $this->getMod();
16
- $oFS = Services::WpFs();
17
 
18
- $sPathUnblockFlag = $oFS->findFileInDir( 'unblock', $this->getCon()->getPath_Flags() );
19
- if ( $oFS->isFile( $sPathUnblockFlag ) ) {
20
- $sContent = $oFS->getFileContent( $sPathUnblockFlag );
21
  if ( !empty( $sContent ) ) {
22
 
23
  $aLines = array_map( 'trim', explode( "\n", $sContent ) );
24
  foreach ( $aLines as $sIp ) {
25
  $bRemoved = ( new IPs\Lib\Ops\DeleteIp() )
26
- ->setDbHandler( $oMod->getDbHandler_IPs() )
27
  ->setIP( $sIp )
28
  ->fromBlacklist();
29
  if ( $bRemoved ) {
@@ -31,7 +31,7 @@ class UnblockIpByFlag {
31
  }
32
  }
33
  }
34
- $oFS->deleteFile( $sPathUnblockFlag );
35
  }
36
  }
37
  }
11
  use Shield\Modules\ModConsumer;
12
 
13
  public function run() {
14
+ /** @var IPs\ModCon $mod */
15
+ $mod = $this->getMod();
16
+ $FS = Services::WpFs();
17
 
18
+ $sPathUnblockFlag = $FS->findFileInDir( 'unblock', $this->getCon()->getPath_Flags() );
19
+ if ( $FS->isFile( $sPathUnblockFlag ) ) {
20
+ $sContent = $FS->getFileContent( $sPathUnblockFlag );
21
  if ( !empty( $sContent ) ) {
22
 
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 ) {
31
  }
32
  }
33
  }
34
+ $FS->deleteFile( $sPathUnblockFlag );
35
  }
36
  }
37
  }
src/lib/src/Modules/IPs/Lib/AutoUnblock.php CHANGED
@@ -3,6 +3,7 @@
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
 
@@ -36,7 +37,7 @@ class AutoUnblock {
36
  * @throws \Exception
37
  */
38
  private function processAutoUnblockRequest() :bool {
39
- /** @var \ICWP_WPSF_FeatureHandler_Ips $mod */
40
  $mod = $this->getMod();
41
  /** @var IPs\Options $opts */
42
  $opts = $this->getOptions();
@@ -96,7 +97,7 @@ class AutoUnblock {
96
  * @throws \Exception
97
  */
98
  private function processUserMagicLink() :bool {
99
- /** @var \ICWP_WPSF_FeatureHandler_Ips $mod */
100
  $mod = $this->getMod();
101
  /** @var IPs\Options $opts */
102
  $opts = $this->getOptions();
@@ -165,7 +166,7 @@ class AutoUnblock {
165
  * @throws \Exception
166
  */
167
  private function sendMagicLinkEmail() {
168
- /** @var \ICWP_WPSF_FeatureHandler_Ips $mod */
169
  $mod = $this->getMod();
170
  $user = Services::WpUsers()->getCurrentWpUser();
171
 
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
 
37
  * @throws \Exception
38
  */
39
  private function processAutoUnblockRequest() :bool {
40
+ /** @var IPs\ModCon $mod */
41
  $mod = $this->getMod();
42
  /** @var IPs\Options $opts */
43
  $opts = $this->getOptions();
97
  * @throws \Exception
98
  */
99
  private function processUserMagicLink() :bool {
100
+ /** @var IPs\ModCon $mod */
101
  $mod = $this->getMod();
102
  /** @var IPs\Options $opts */
103
  $opts = $this->getOptions();
166
  * @throws \Exception
167
  */
168
  private function sendMagicLinkEmail() {
169
+ /** @var IPs\ModCon $mod */
170
  $mod = $this->getMod();
171
  $user = Services::WpUsers()->getCurrentWpUser();
172
 
src/lib/src/Modules/IPs/Lib/BlacklistHandler.php CHANGED
@@ -13,7 +13,7 @@ class BlacklistHandler {
13
  use OneTimeExecute;
14
 
15
  protected function run() {
16
- /** @var \ICWP_WPSF_FeatureHandler_Ips $mod */
17
  $mod = $this->getMod();
18
  /** @var IPs\Options $oOpts */
19
  $oOpts = $this->getOptions();
@@ -47,7 +47,7 @@ class BlacklistHandler {
47
  }
48
 
49
  public function loadBotDetectors() {
50
- /** @var \ICWP_WPSF_FeatureHandler_Ips $mod */
51
  $mod = $this->getMod();
52
  /** @var IPs\Options $opts */
53
  $opts = $this->getOptions();
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();
47
  }
48
 
49
  public function loadBotDetectors() {
50
+ /** @var IPs\ModCon $mod */
51
  $mod = $this->getMod();
52
  /** @var IPs\Options $opts */
53
  $opts = $this->getOptions();
src/lib/src/Modules/IPs/Lib/BlockRequest.php CHANGED
@@ -46,7 +46,7 @@ class BlockRequest {
46
  }
47
 
48
  private function renderKillPage() {
49
- /** @var \ICWP_WPSF_FeatureHandler_Ips $mod */
50
  $mod = $this->getMod();
51
  /** @var IPs\Options $opts */
52
  $opts = $this->getOptions();
@@ -65,11 +65,18 @@ class BlockRequest {
65
  && $opts->getCanRequestAutoUnblockEmailLink( $user );
66
  $bCanAutoRecover = $bCanUauGasp || $bCanUauMagic;
67
 
68
- $aData = [
 
 
 
 
 
 
 
69
  'strings' => [
70
  'title' => sprintf( __( "You've been blocked by the %s plugin", 'wp-simple-firewall' ),
71
  sprintf( '<a href="%s" target="_blank">%s</a>',
72
- $con->getPluginSpec()[ 'meta' ][ 'url_repo_home' ],
73
  $con->getHumanName()
74
  )
75
  ),
@@ -116,7 +123,7 @@ class BlockRequest {
116
  ];
117
  Services::WpGeneral()
118
  ->wpDie(
119
- $mod->renderTemplate( '/pages/block/blocklist_die.twig', $aData, true )
120
  );
121
  }
122
 
@@ -124,7 +131,7 @@ class BlockRequest {
124
  * @return string
125
  */
126
  private function renderEmailMagicLinkContent() {
127
- /** @var \ICWP_WPSF_FeatureHandler_Ips $mod */
128
  $mod = $this->getMod();
129
  /** @var IPs\Options $opts */
130
  $opts = $this->getOptions();
46
  }
47
 
48
  private function renderKillPage() {
49
+ /** @var IPs\ModCon $mod */
50
  $mod = $this->getMod();
51
  /** @var IPs\Options $opts */
52
  $opts = $this->getOptions();
65
  && $opts->getCanRequestAutoUnblockEmailLink( $user );
66
  $bCanAutoRecover = $bCanUauGasp || $bCanUauMagic;
67
 
68
+ if ( !empty( $con->getLabels()[ 'PluginURI' ] ) ) {
69
+ $homeURL = $con->getLabels()[ 'PluginURI' ];
70
+ }
71
+ else {
72
+ $homeURL = $con->getPluginSpec()[ 'meta' ][ 'url_repo_home' ];
73
+ }
74
+
75
+ $data = [
76
  'strings' => [
77
  'title' => sprintf( __( "You've been blocked by the %s plugin", 'wp-simple-firewall' ),
78
  sprintf( '<a href="%s" target="_blank">%s</a>',
79
+ $homeURL,
80
  $con->getHumanName()
81
  )
82
  ),
123
  ];
124
  Services::WpGeneral()
125
  ->wpDie(
126
+ $mod->renderTemplate( '/pages/block/blocklist_die.twig', $data, true )
127
  );
128
  }
129
 
131
  * @return string
132
  */
133
  private function renderEmailMagicLinkContent() {
134
+ /** @var IPs\ModCon $mod */
135
  $mod = $this->getMod();
136
  /** @var IPs\Options $opts */
137
  $opts = $this->getOptions();
src/lib/src/Modules/IPs/Lib/IpAnalyse/BuildDisplay.php CHANGED
@@ -6,8 +6,10 @@ 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\ModConsumer;
10
  use FernleafSystems\Wordpress\Services\Services;
 
11
 
12
  class BuildDisplay {
13
 
@@ -19,7 +21,7 @@ class BuildDisplay {
19
  * @throws \Exception
20
  */
21
  public function run() :string {
22
- /** @var \ICWP_WPSF_FeatureHandler_Ips $mod */
23
  $mod = $this->getMod();
24
 
25
  $ip = $this->getIP();
@@ -77,6 +79,28 @@ class BuildDisplay {
77
 
78
  $sRDNS = gethostbyaddr( $ip );
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  return $this->getMod()->renderTemplate(
81
  '/wpadmin_pages/insights/ips/ip_analyse/ip_general.twig',
82
  [
@@ -84,6 +108,11 @@ class BuildDisplay {
84
  'title_general' => __( 'Identifying Info', 'wp-simple-firewall' ),
85
  'title_status' => __( 'IP Status', 'wp-simple-firewall' ),
86
 
 
 
 
 
 
87
  'status' => [
88
  'is_you' => __( 'Is It You?', 'wp-simple-firewall' ),
89
  'offenses' => __( 'Number of offenses', 'wp-simple-firewall' ),
@@ -117,9 +146,7 @@ class BuildDisplay {
117
  'is_bypass' => $oBypassIP instanceof Databases\IPs\EntryVO,
118
  ],
119
  'identity' => [
120
- 'who_is_it' => Services::IP()
121
- ->getIpDetector()
122
- ->getIPIdentity(),
123
  'rdns' => $sRDNS === $ip ? __( 'Unavailable', 'wp-simple-firewall' ) : $sRDNS,
124
  'country_name' => $validGeo ? $geo->getCountryName() : __( 'Unknown', 'wp-simple-firewall' ),
125
  'timezone' => $validGeo ? $geo->getTimezone() : __( 'Unknown', 'wp-simple-firewall' ),
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
 
21
  * @throws \Exception
22
  */
23
  public function run() :string {
24
+ /** @var ModCon $mod */
25
  $mod = $this->getMod();
26
 
27
  $ip = $this->getIP();
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
  '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' ),
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' ),
src/lib/src/Modules/IPs/Lib/OffenseTracker.php CHANGED
@@ -17,13 +17,13 @@ class OffenseTracker extends EventsListener {
17
  private $nOffenseCount = 0;
18
 
19
  /**
20
- * @param string $sEvent
21
  * @param array $aMeta
22
  */
23
- protected function captureEvent( $sEvent, $aMeta = [] ) {
24
  $aDef = $this->getCon()
25
  ->loadEventsService()
26
- ->getEventDef( $sEvent );
27
 
28
  if ( !empty( $aDef ) && !empty( $aDef[ 'offense' ] ) && empty( $aMeta[ 'suppress_offense' ] ) ) {
29
  $this->incrementCount( isset( $aMeta[ 'offense_count' ] ) ? $aMeta[ 'offense_count' ] : 1 );
17
  private $nOffenseCount = 0;
18
 
19
  /**
20
+ * @param string $evt
21
  * @param array $aMeta
22
  */
23
+ protected function captureEvent( $evt, $aMeta = [] ) {
24
  $aDef = $this->getCon()
25
  ->loadEventsService()
26
+ ->getEventDef( $evt );
27
 
28
  if ( !empty( $aDef ) && !empty( $aDef[ 'offense' ] ) && empty( $aMeta[ 'suppress_offense' ] ) ) {
29
  $this->incrementCount( isset( $aMeta[ 'offense_count' ] ) ? $aMeta[ 'offense_count' ] : 1 );
src/lib/src/Modules/IPs/Lib/Ops/AddIp.php CHANGED
@@ -4,6 +4,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
 
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  /**
@@ -20,7 +21,7 @@ class AddIp {
20
  * @throws \Exception
21
  */
22
  public function toAutoBlacklist() {
23
- /** @var \ICWP_WPSF_FeatureHandler_Ips $mod */
24
  $mod = $this->getMod();
25
  $oReq = Services::Request();
26
 
@@ -64,8 +65,8 @@ class AddIp {
64
  * @throws \Exception
65
  */
66
  public function toManualBlacklist( $sLabel = '' ) {
67
- /** @var \ICWP_WPSF_FeatureHandler_Ips $oMod */
68
- $oMod = $this->getMod();
69
  $oIpServ = Services::IP();
70
 
71
  $sIP = $this->getIP();
@@ -78,27 +79,27 @@ class AddIp {
78
 
79
  if ( $oIpServ->isValidIpRange( $sIP ) ) {
80
  ( new DeleteIp() )
81
- ->setDbHandler( $oMod->getDbHandler_IPs() )
82
  ->setIP( $sIP )
83
  ->fromBlacklist();
84
  }
85
 
86
  $oIP = ( new LookupIpOnList() )
87
- ->setDbHandler( $oMod->getDbHandler_IPs() )
88
  ->setListTypeBlack()
89
  ->setIP( $sIP )
90
  ->lookup( false );
91
 
92
  if ( !$oIP instanceof Databases\IPs\EntryVO ) {
93
- $oIP = $this->add( $oMod::LIST_MANUAL_BLACK, $sLabel );
94
  }
95
 
96
  $aUpdateData = [
97
  'last_access_at' => Services::Request()->ts()
98
  ];
99
 
100
- if ( $oIP->list != $oMod::LIST_MANUAL_BLACK ) {
101
- $aUpdateData[ 'list' ] = $oMod::LIST_MANUAL_BLACK;
102
  }
103
  if ( $oIP->label != $sLabel ) {
104
  $aUpdateData[ 'label' ] = $sLabel;
@@ -107,7 +108,7 @@ class AddIp {
107
  $aUpdateData[ 'blocked_at' ] = Services::Request()->ts();
108
  }
109
 
110
- $oMod->getDbHandler_IPs()
111
  ->getQueryUpdater()
112
  ->updateEntry( $oIP, $aUpdateData );
113
  }
@@ -116,41 +117,41 @@ class AddIp {
116
  }
117
 
118
  /**
119
- * @param string $sLabel
120
  * @return Databases\IPs\EntryVO|null
121
  * @throws \Exception
122
  */
123
- public function toManualWhitelist( $sLabel = '' ) {
124
- /** @var \ICWP_WPSF_FeatureHandler_Ips $oMod */
125
- $oMod = $this->getMod();
126
  $oIpServ = Services::IP();
127
 
128
- $sIP = $this->getIP();
129
- if ( !$oIpServ->isValidIp( $sIP ) && !$oIpServ->isValidIpRange( $sIP ) ) {
130
  throw new \Exception( "IP address isn't valid." );
131
  }
132
 
133
- if ( $oIpServ->isValidIpRange( $sIP ) ) {
134
  ( new DeleteIp() )
135
- ->setDbHandler( $oMod->getDbHandler_IPs() )
136
- ->setIP( $sIP )
137
  ->fromWhiteList();
138
  }
139
 
140
  $oIP = ( new LookupIpOnList() )
141
- ->setDbHandler( $oMod->getDbHandler_IPs() )
142
  ->setIP( $this->getIP() )
143
  ->lookup( false );
144
  if ( !$oIP instanceof Databases\IPs\EntryVO ) {
145
- $oIP = $this->add( $oMod::LIST_MANUAL_WHITE, $sLabel );
146
  }
147
 
148
  $aUpdateData = [];
149
- if ( $oIP->list != $oMod::LIST_MANUAL_WHITE ) {
150
- $aUpdateData[ 'list' ] = $oMod::LIST_MANUAL_WHITE;
151
  }
152
- if ( !empty( $sLabel ) && $oIP->label != $sLabel ) {
153
- $aUpdateData[ 'label' ] = $sLabel;
154
  }
155
  if ( $oIP->blocked_at > 0 ) {
156
  $aUpdateData[ 'blocked_at' ] = 0;
@@ -160,7 +161,7 @@ class AddIp {
160
  }
161
 
162
  if ( !empty( $aUpdateData ) ) {
163
- $oMod->getDbHandler_IPs()
164
  ->getQueryUpdater()
165
  ->updateEntry( $oIP, $aUpdateData );
166
  }
@@ -178,11 +179,11 @@ class AddIp {
178
  private function add( $sList, $sLabel = '', $nLastAccessAt = null ) {
179
  $oIP = null;
180
 
181
- /** @var \ICWP_WPSF_FeatureHandler_Ips $oMod */
182
- $oMod = $this->getMod();
183
 
184
  // Never add a reserved IP to any black list
185
- $oDbh = $oMod->getDbHandler_IPs();
186
 
187
  /** @var Databases\IPs\EntryVO $oTempIp */
188
  $oTempIp = $oDbh->getVo();
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\ModCon;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
10
  /**
21
  * @throws \Exception
22
  */
23
  public function toAutoBlacklist() {
24
+ /** @var ModCon $mod */
25
  $mod = $this->getMod();
26
  $oReq = Services::Request();
27
 
65
  * @throws \Exception
66
  */
67
  public function toManualBlacklist( $sLabel = '' ) {
68
+ /** @var ModCon $mod */
69
+ $mod = $this->getMod();
70
  $oIpServ = Services::IP();
71
 
72
  $sIP = $this->getIP();
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;
108
  $aUpdateData[ 'blocked_at' ] = Services::Request()->ts();
109
  }
110
 
111
+ $mod->getDbHandler_IPs()
112
  ->getQueryUpdater()
113
  ->updateEntry( $oIP, $aUpdateData );
114
  }
117
  }
118
 
119
  /**
120
+ * @param string $label
121
  * @return Databases\IPs\EntryVO|null
122
  * @throws \Exception
123
  */
124
+ public function toManualWhitelist( $label = '' ) {
125
+ /** @var ModCon $mod */
126
+ $mod = $this->getMod();
127
  $oIpServ = Services::IP();
128
 
129
+ $ip = $this->getIP();
130
+ if ( !$oIpServ->isValidIp( $ip ) && !$oIpServ->isValidIpRange( $ip ) ) {
131
  throw new \Exception( "IP address isn't valid." );
132
  }
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;
161
  }
162
 
163
  if ( !empty( $aUpdateData ) ) {
164
+ $mod->getDbHandler_IPs()
165
  ->getQueryUpdater()
166
  ->updateEntry( $oIP, $aUpdateData );
167
  }
179
  private function add( $sList, $sLabel = '', $nLastAccessAt = null ) {
180
  $oIP = null;
181
 
182
+ /** @var ModCon $mod */
183
+ $mod = $this->getMod();
184
 
185
  // Never add a reserved IP to any black list
186
+ $oDbh = $mod->getDbHandler_IPs();
187
 
188
  /** @var Databases\IPs\EntryVO $oTempIp */
189
  $oTempIp = $oDbh->getVo();
src/lib/src/Modules/IPs/Lib/Ops/DeleteIp.php CHANGED
@@ -15,28 +15,19 @@ class DeleteIp {
15
  use Databases\Base\HandlerConsumer;
16
  use IPs\Components\IpAddressConsumer;
17
 
18
- /**
19
- * @return bool
20
- */
21
- public function fromBlacklist() {
22
- return $this->getDeleter()
23
- ->filterByBlacklist()
24
- ->query();
25
  }
26
 
27
- /**
28
- * @return bool
29
- */
30
- public function fromWhiteList() {
31
- return $this->getDeleter()
32
- ->filterByWhitelist()
33
- ->query();
34
  }
35
 
36
- /**
37
- * @return Databases\IPs\Delete
38
- */
39
- private function getDeleter() {
40
  /** @var Databases\IPs\Delete $oDel */
41
  $oDel = $this->getDbHandler()->getQueryDeleter();
42
  return $oDel->filterByIp( $this->getIP() )
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();
 
 
 
22
  }
23
 
24
+ public function fromWhiteList() :bool {
25
+ return (bool)$this->getDeleter()
26
+ ->filterByWhitelist()
27
+ ->query();
 
 
 
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() )
src/lib/src/Modules/IPs/Lib/ProcessOffenses.php CHANGED
@@ -11,7 +11,7 @@ class ProcessOffenses {
11
  use ModConsumer;
12
 
13
  public function run() {
14
- /** @var \ICWP_WPSF_FeatureHandler_Ips $mod */
15
  $mod = $this->getMod();
16
 
17
  $mod->loadOffenseTracker()->setIfCommit( true );
@@ -25,7 +25,7 @@ class ProcessOffenses {
25
  }
26
 
27
  private function processOffense() {
28
- /** @var \ICWP_WPSF_FeatureHandler_Ips $mod */
29
  $mod = $this->getMod();
30
 
31
  $oTracker = $mod->loadOffenseTracker();
11
  use ModConsumer;
12
 
13
  public function run() {
14
+ /** @var IPs\ModCon $mod */
15
  $mod = $this->getMod();
16
 
17
  $mod->loadOffenseTracker()->setIfCommit( true );
25
  }
26
 
27
  private function processOffense() {
28
+ /** @var IPs\ModCon $mod */
29
  $mod = $this->getMod();
30
 
31
  $oTracker = $mod->loadOffenseTracker();
src/lib/src/Modules/IPs/ModCon.php ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ 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 {
10
+
11
+ const LIST_MANUAL_WHITE = 'MW';
12
+ const LIST_MANUAL_BLACK = 'MB';
13
+ const LIST_AUTO_BLACK = 'AB';
14
+
15
+ /**
16
+ * @var Lib\OffenseTracker
17
+ */
18
+ private $oOffenseTracker;
19
+
20
+ /**
21
+ * @var Lib\BlacklistHandler
22
+ */
23
+ private $oBlacklistHandler;
24
+
25
+ public function getBlacklistHandler() :Lib\BlacklistHandler {
26
+ if ( !isset( $this->oBlacklistHandler ) ) {
27
+ $this->oBlacklistHandler = ( new Lib\BlacklistHandler() )->setMod( $this );
28
+ }
29
+ return $this->oBlacklistHandler;
30
+ }
31
+
32
+ public function getDbHandler_IPs() :Shield\Databases\IPs\Handler {
33
+ return $this->getDbH( 'ips' );
34
+ }
35
+
36
+ /**
37
+ * @return bool
38
+ * @throws \Exception
39
+ */
40
+ protected function isReadyToExecute() :bool {
41
+ $oIp = Services::IP();
42
+ return $oIp->isValidIp_PublicRange( $oIp->getRequestIp() )
43
+ && ( $this->getDbHandler_IPs() instanceof Shield\Databases\IPs\Handler )
44
+ && $this->getDbHandler_IPs()->isReady()
45
+ && parent::isReadyToExecute();
46
+ }
47
+
48
+ protected function preProcessOptions() {
49
+ /** @var Options $opts */
50
+ $opts = $this->getOptions();
51
+ if ( !defined( strtoupper( $opts->getOpt( 'auto_expire' ).'_IN_SECONDS' ) ) ) {
52
+ $opts->resetOptToDefault( 'auto_expire' );
53
+ }
54
+
55
+ $nLimit = $opts->getOffenseLimit();
56
+ if ( !is_int( $nLimit ) || $nLimit < 0 ) {
57
+ $opts->resetOptToDefault( 'transgression_limit' );
58
+ }
59
+
60
+ $this->cleanPathWhitelist();
61
+ }
62
+
63
+ private function cleanPathWhitelist() {
64
+ /** @var Options $opts */
65
+ $opts = $this->getOptions();
66
+ $opts->setOpt( 'request_whitelist', array_unique( array_filter( array_map(
67
+ function ( $rule ) {
68
+ $rule = strtolower( trim( $rule ) );
69
+ if ( !empty( $rule ) ) {
70
+ $toCheck = array_unique( [
71
+ (string)parse_url( Services::WpGeneral()->getHomeUrl(), PHP_URL_PATH ),
72
+ (string)parse_url( Services::WpGeneral()->getWpUrl(), PHP_URL_PATH ),
73
+ ] );
74
+ $regEx = sprintf( '#^%s$#i', str_replace( 'STAR', '.*', preg_quote( str_replace( '*', 'STAR', $rule ), '#' ) ) );
75
+ foreach ( $toCheck as $path ) {
76
+ $slashPath = rtrim( $path, '/' ).'/';
77
+ if ( preg_match( $regEx, $path ) || preg_match( $regEx, $slashPath ) ) {
78
+ $rule = false;
79
+ break;
80
+ }
81
+ }
82
+ }
83
+ return $rule;
84
+ },
85
+ $opts->getOpt( 'request_whitelist', [] ) // do not use Options getter as it formats into regex
86
+ ) ) ) );
87
+ }
88
+
89
+ public function loadOffenseTracker() :Lib\OffenseTracker {
90
+ if ( !isset( $this->oOffenseTracker ) ) {
91
+ $this->oOffenseTracker = new Lib\OffenseTracker( $this->getCon() );
92
+ }
93
+ return $this->oOffenseTracker;
94
+ }
95
+
96
+ public function getTextOptDefault( string $key ) :string {
97
+
98
+ switch ( $key ) {
99
+
100
+ case 'text_loginfailed':
101
+ $text = sprintf( '%s: %s',
102
+ __( 'Warning', 'wp-simple-firewall' ),
103
+ __( 'Repeated login attempts that fail will result in a complete ban of your IP Address.', 'wp-simple-firewall' )
104
+ );
105
+ break;
106
+
107
+ case 'text_remainingtrans':
108
+ $text = sprintf( '%s: %s',
109
+ __( 'Warning', 'wp-simple-firewall' ),
110
+ __( 'You have %s remaining offenses(s) against this site and then your IP address will be completely blocked.', 'wp-simple-firewall' )
111
+ .'<br/><strong>'.__( 'Seriously, stop repeating what you are doing or you will be locked out.', 'wp-simple-firewall' ).'</strong>'
112
+ );
113
+ break;
114
+
115
+ default:
116
+ $text = parent::getTextOptDefault( $key );
117
+ break;
118
+ }
119
+ return $text;
120
+ }
121
+ }
src/lib/src/Modules/IPs/Options.php CHANGED
@@ -2,10 +2,10 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
- class Options extends Base\ShieldOptions {
9
 
10
  /**
11
  * @return int
@@ -163,12 +163,4 @@ class Options extends Base\ShieldOptions {
163
  protected function isSelectOptionEnabled( $key ) {
164
  return !$this->isOpt( $key, 'disabled' );
165
  }
166
-
167
- /**
168
- * @return string
169
- * @deprecated 10.0
170
- */
171
- public function getDbTable_IPs() {
172
- return $this->getCon()->prefixOption( $this->getDef( 'ip_lists_table_name' ) );
173
- }
174
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
+ class Options extends BaseShield\Options {
9
 
10
  /**
11
  * @return int
163
  protected function isSelectOptionEnabled( $key ) {
164
  return !$this->isOpt( $key, 'disabled' );
165
  }
 
 
 
 
 
 
 
 
166
  }
src/lib/src/Modules/IPs/Processor.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+
7
+ class Processor extends BaseShield\Processor {
8
+
9
+ protected function run() {
10
+ /** @var ModCon $mod */
11
+ $mod = $this->getMod();
12
+ $mod->getBlacklistHandler()->execute();
13
+ }
14
+ }
src/lib/src/Modules/IPs/Strings.php CHANGED
@@ -13,10 +13,10 @@ class Strings extends Base\Strings {
13
  * @throws \Exception
14
  */
15
  public function getSectionStrings( string $section ) :array {
16
- /** @var \ICWP_WPSF_FeatureHandler_Ips $oMod */
17
- $oMod = $this->getMod();
18
  $sPlugName = $this->getCon()->getHumanName();
19
- $sModName = $oMod->getMainFeatureName();
20
 
21
  switch ( $section ) {
22
 
@@ -201,7 +201,7 @@ class Strings extends Base\Strings {
201
 
202
  case 'track_loginfailed' :
203
  $sName = __( 'Failed Login', 'wp-simple-firewall' );
204
- $sSummary = __( 'Detect Failed Login Attempts Using Valid Usernames', '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
 
13
  * @throws \Exception
14
  */
15
  public function getSectionStrings( string $section ) :array {
16
+ /** @var ModCon $mod */
17
+ $mod = $this->getMod();
18
  $sPlugName = $this->getCon()->getHumanName();
19
+ $sModName = $mod->getMainFeatureName();
20
 
21
  switch ( $section ) {
22
 
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
 
src/lib/src/Modules/IPs/UI.php CHANGED
@@ -2,16 +2,16 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\IpAnalyse\FindAllPluginIps;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops\RetrieveIpsForLists;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
10
- class UI extends Base\ShieldUI {
11
 
12
  public function buildInsightsVars() :array {
13
  $con = $this->getCon();
14
- /** @var \ICWP_WPSF_FeatureHandler_Ips $mod */
15
  $mod = $this->getMod();
16
  /** @var Options $opts */
17
  $opts = $this->getOptions();
@@ -61,6 +61,12 @@ class UI extends Base\ShieldUI {
61
  'tab_ip_analysis' => __( 'IP Analysis', 'wp-simple-firewall' ),
62
  ],
63
  'vars' => [
 
 
 
 
 
 
64
  'unique_ips_black' => ( new RetrieveIpsForLists() )
65
  ->setDbHandler( $mod->getDbHandler_IPs() )
66
  ->black(),
@@ -107,7 +113,8 @@ class UI extends Base\ShieldUI {
107
  '/wpadmin_pages/insights/ips/ip_analyse/index.twig',
108
  [
109
  'ajax' => [
110
- 'build_ip_analyse' => $mod->getAjaxActionData( 'build_ip_analyse', true ),
 
111
  ],
112
  'strings' => [
113
  'select_ip' => __( 'Select IP To Analyse', 'wp-simple-firewall' ),
@@ -124,4 +131,14 @@ class UI extends Base\ShieldUI {
124
  true
125
  );
126
  }
 
 
 
 
 
 
 
 
 
 
127
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\IpAnalyse\FindAllPluginIps;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops\RetrieveIpsForLists;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
10
+ class UI extends BaseShield\UI {
11
 
12
  public function buildInsightsVars() :array {
13
  $con = $this->getCon();
14
+ /** @var ModCon $mod */
15
  $mod = $this->getMod();
16
  /** @var Options $opts */
17
  $opts = $this->getOptions();
61
  'tab_ip_analysis' => __( 'IP Analysis', 'wp-simple-firewall' ),
62
  ],
63
  'vars' => [
64
+ 'related_hrefs' => [
65
+ [
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() )
72
  ->black(),
113
  '/wpadmin_pages/insights/ips/ip_analyse/index.twig',
114
  [
115
  'ajax' => [
116
+ 'build_ip_analyse' => $mod->getAjaxActionData( 'build_ip_analyse', true ),
117
+ 'ip_analyse_action' => $mod->getAjaxActionData( 'ip_analyse_action', true ),
118
  ],
119
  'strings' => [
120
  'select_ip' => __( 'Select IP To Analyse', 'wp-simple-firewall' ),
131
  true
132
  );
133
  }
134
+
135
+ protected function getSettingsRelatedLinks() :array {
136
+ $modInsights = $this->getCon()->getModule_Insights();
137
+ return [
138
+ [
139
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'ips' ),
140
+ 'title' => __( 'Analyse & Manage IPs', 'wp-simple-firewall' ),
141
+ ]
142
+ ];
143
+ }
144
  }
src/lib/src/Modules/IPs/Upgrade.php CHANGED
@@ -2,13 +2,22 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class Upgrade extends Base\Upgrade {
9
 
 
 
 
 
 
 
 
 
10
  protected function upgrade_905() {
11
- /** @var \ICWP_WPSF_FeatureHandler_Ips $mod */
12
  $mod = $this->getMod();
13
  $schema = $mod->getDbHandler_IPs()->getTableSchema();
14
  Services::WpDb()->doSql(
@@ -21,7 +30,7 @@ class Upgrade extends Base\Upgrade {
21
  * Support larger transgression counts [smallint(1) => int(10)]
22
  */
23
  protected function upgrade_911() {
24
- /** @var \ICWP_WPSF_FeatureHandler_Ips $mod */
25
  $mod = $this->getMod();
26
  $schema = $mod->getDbHandler_IPs()->getTableSchema();
27
  Services::WpDb()->doSql(
@@ -37,12 +46,12 @@ class Upgrade extends Base\Upgrade {
37
  * Support Magic Links for logged-in users.
38
  */
39
  protected function upgrade_920() {
40
- /** @var Options $oOpts */
41
- $oOpts = $this->getOptions();
42
- $current = $oOpts->getOpt( 'user_auto_recover' );
43
  if ( !is_array( $current ) ) {
44
  $current = ( $current === 'gasp' ) ? [ 'gasp' ] : [];
45
- $oOpts->setOpt( 'user_auto_recover', $current );
46
  }
47
  }
48
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases\IPs\Delete;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class Upgrade extends Base\Upgrade {
10
 
11
+ protected function upgrade_1010() {
12
+ /** @var ModCon $mod */
13
+ $mod = $this->getMod();
14
+ /** @var Delete $del */
15
+ $del = $mod->getDbHandler_IPs()->getQueryDeleter();
16
+ $del->filterByLabel( 'iControlWP' )->query();
17
+ }
18
+
19
  protected function upgrade_905() {
20
+ /** @var ModCon $mod */
21
  $mod = $this->getMod();
22
  $schema = $mod->getDbHandler_IPs()->getTableSchema();
23
  Services::WpDb()->doSql(
30
  * Support larger transgression counts [smallint(1) => int(10)]
31
  */
32
  protected function upgrade_911() {
33
+ /** @var ModCon $mod */
34
  $mod = $this->getMod();
35
  $schema = $mod->getDbHandler_IPs()->getTableSchema();
36
  Services::WpDb()->doSql(
46
  * Support Magic Links for logged-in users.
47
  */
48
  protected function upgrade_920() {
49
+ /** @var Options $opts */
50
+ $opts = $this->getOptions();
51
+ $current = $opts->getOpt( 'user_auto_recover' );
52
  if ( !is_array( $current ) ) {
53
  $current = ( $current === 'gasp' ) ? [ 'gasp' ] : [];
54
+ $opts->setOpt( 'user_auto_recover', $current );
55
  }
56
  }
57
  }
src/lib/src/Modules/IPs/WpCli.php CHANGED
@@ -10,7 +10,7 @@ class WpCli extends Base\WpCli {
10
  /**
11
  * @inheritDoc
12
  */
13
- protected function getCmdHandlers() {
14
  return [
15
  new IPs\WpCli\Add(),
16
  new IPs\WpCli\Remove(),
10
  /**
11
  * @inheritDoc
12
  */
13
+ protected function getCmdHandlers() :array {
14
  return [
15
  new IPs\WpCli\Add(),
16
  new IPs\WpCli\Remove(),
src/lib/src/Modules/IPs/WpCli/Add.php CHANGED
@@ -34,17 +34,17 @@ class Add extends BaseAddRemove {
34
  */
35
  public function cmdIpAdd( array $null, array $aA ) {
36
 
37
- $sLabel = isset( $aA[ 'label' ] ) ? $aA[ 'label' ] : 'none';
38
 
39
  $oAdder = ( new Ops\AddIp() )
40
  ->setMod( $this->getMod() )
41
  ->setIP( $aA[ 'ip' ] );
42
  try {
43
  if ( $aA[ 'list' ] === 'white' ) {
44
- $oAdder->toManualWhitelist( $sLabel );
45
  }
46
  else {
47
- $oAdder->toManualBlacklist( $sLabel );
48
  }
49
  }
50
  catch ( \Exception $oE ) {
34
  */
35
  public function cmdIpAdd( array $null, array $aA ) {
36
 
37
+ $label = $aA[ 'label' ] ?? 'none';
38
 
39
  $oAdder = ( new Ops\AddIp() )
40
  ->setMod( $this->getMod() )
41
  ->setIP( $aA[ 'ip' ] );
42
  try {
43
  if ( $aA[ 'list' ] === 'white' ) {
44
+ $oAdder->toManualWhitelist( $label );
45
  }
46
  else {
47
+ $oAdder->toManualBlacklist( $label );
48
  }
49
  }
50
  catch ( \Exception $oE ) {
src/lib/src/Modules/IPs/WpCli/Enumerate.php CHANGED
@@ -4,6 +4,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\WpCli;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\WpCli\BaseWpCliCmd;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops;
 
7
  use WP_CLI;
8
 
9
  class Enumerate extends BaseWpCliCmd {
@@ -32,11 +33,11 @@ class Enumerate extends BaseWpCliCmd {
32
  }
33
 
34
  public function cmdPrint( $null, $aA ) {
35
- /** @var \ICWP_WPSF_FeatureHandler_Ips $oMod */
36
- $oMod = $this->getMod();
37
 
38
  $oRtr = ( new Ops\RetrieveIpsForLists() )
39
- ->setDbHandler( $oMod->getDbHandler_IPs() );
40
  $aIPs = $aA[ 'list' ] === 'white' ? $oRtr->white() : $oRtr->black();
41
  $aIPs = array_map(
42
  function ( $sIP ) {
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\WpCli\BaseWpCliCmd;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\ModCon;
8
  use WP_CLI;
9
 
10
  class Enumerate extends BaseWpCliCmd {
33
  }
34
 
35
  public function cmdPrint( $null, $aA ) {
36
+ /** @var ModCon $mod */
37
+ $mod = $this->getMod();
38
 
39
  $oRtr = ( new Ops\RetrieveIpsForLists() )
40
+ ->setDbHandler( $mod->getDbHandler_IPs() );
41
  $aIPs = $aA[ 'list' ] === 'white' ? $oRtr->white() : $oRtr->black();
42
  $aIPs = array_map(
43
  function ( $sIP ) {
src/lib/src/Modules/IPs/WpCli/Remove.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\WpCli;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops;
6
  use WP_CLI;
7
 
8
  class Remove extends BaseAddRemove {
@@ -25,11 +25,11 @@ class Remove extends BaseAddRemove {
25
  * @throws WP_CLI\ExitException
26
  */
27
  public function cmdIpRemove( array $null, array $aA ) {
28
- /** @var \ICWP_WPSF_FeatureHandler_Ips $oMod */
29
- $oMod = $this->getMod();
30
 
31
- $oDel = ( new Ops\DeleteIp() )
32
- ->setDbHandler( $oMod->getDbHandler_IPs() )
33
  ->setIP( $aA[ 'ip' ] );
34
  if ( $aA[ 'list' ] === 'white' ) {
35
  $bSuccess = $oDel->fromWhiteList();
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\WpCli;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
6
  use WP_CLI;
7
 
8
  class Remove extends BaseAddRemove {
25
  * @throws WP_CLI\ExitException
26
  */
27
  public function cmdIpRemove( array $null, array $aA ) {
28
+ /** @var IPs\ModCon $mod */
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();
src/lib/src/Modules/Insights/ModCon.php ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+
9
+ class ModCon extends BaseShield\ModCon {
10
+
11
+ protected function setupCustomHooks() {
12
+ add_action( 'admin_footer', function () {
13
+ /** @var UI $UI */
14
+ $UI = $this->getUIHandler();
15
+ $UI->printAdminFooterItems();
16
+ }, 100, 0 );
17
+ }
18
+
19
+ protected function onModulesLoaded() {
20
+ $this->maybeRedirectToAdmin();
21
+ }
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
+ }
30
+
31
+ public function getUrl_IpAnalysis( string $ip ) :string {
32
+ return add_query_arg( [ 'analyse_ip' => $ip ], $this->getUrl_SubInsightsPage( 'ips' ) );
33
+ }
34
+
35
+ public function getUrl_SubInsightsPage( string $subPage ) :string {
36
+ return add_query_arg(
37
+ [ 'inav' => sanitize_key( $subPage ) ],
38
+ $this->getUrl_AdminPage()
39
+ );
40
+ }
41
+
42
+ protected function renderModulePage( array $aData = [] ) :string {
43
+ /** @var UI $UI */
44
+ $UI = $this->getUIHandler();
45
+ return $UI->renderPages();
46
+ }
47
+
48
+ public function insertCustomJsVars_Admin() {
49
+ parent::insertCustomJsVars_Admin();
50
+
51
+ if ( $this->isThisModulePage() ) {
52
+
53
+ $con = $this->getCon();
54
+ $aStdDepsJs = [ $con->prefix( 'plugin' ) ];
55
+ $iNav = Services::Request()->query( 'inav', 'overview' );
56
+
57
+ $oModPlugin = $con->getModule_Plugin();
58
+ $oTourManager = $oModPlugin->getTourManager();
59
+ switch ( $iNav ) {
60
+
61
+ case 'importexport':
62
+
63
+ $sAsset = 'shield/import';
64
+ $sUnique = $con->prefix( $sAsset );
65
+ wp_register_script(
66
+ $sUnique,
67
+ $con->getPluginUrl_Js( $sAsset ),
68
+ $aStdDepsJs,
69
+ $con->getVersion(),
70
+ false
71
+ );
72
+ wp_enqueue_script( $sUnique );
73
+ break;
74
+
75
+ case 'overview':
76
+ case 'reports':
77
+
78
+ $aDeps = $aStdDepsJs;
79
+
80
+ $aJsAssets = [
81
+ 'chartist.min',
82
+ 'chartist-plugin-legend',
83
+ 'charts',
84
+ 'shuffle',
85
+ 'shield-card-shuffle'
86
+ ];
87
+ if ( $oTourManager->canShow( 'insights_overview' ) ) {
88
+ array_unshift( $aJsAssets, 'introjs.min.js' );
89
+ }
90
+ foreach ( $aJsAssets as $sAsset ) {
91
+ $sUnique = $con->prefix( $sAsset );
92
+ wp_register_script(
93
+ $sUnique,
94
+ $con->getPluginUrl_Js( $sAsset ),
95
+ $aDeps,
96
+ $con->getVersion(),
97
+ false
98
+ );
99
+ wp_enqueue_script( $sUnique );
100
+ $aDeps[] = $sUnique;
101
+ }
102
+
103
+ $aDeps = [];
104
+ $aCssAssets = [ 'chartist.min', 'chartist-plugin-legend' ];
105
+ if ( $oTourManager->canShow( 'insights_overview' ) ) {
106
+ array_unshift( $aCssAssets, 'introjs.min.css' );
107
+ }
108
+ foreach ( $aCssAssets as $sAsset ) {
109
+ $sUnique = $con->prefix( $sAsset );
110
+ wp_register_style(
111
+ $sUnique,
112
+ $con->getPluginUrl_Css( $sAsset ),
113
+ $aDeps,
114
+ $con->getVersion(),
115
+ false
116
+ );
117
+ wp_enqueue_style( $sUnique );
118
+ $aDeps[] = $sUnique;
119
+ }
120
+
121
+ $this->includeScriptIpDetect();
122
+ break;
123
+
124
+ case 'notes':
125
+ case 'scans':
126
+ case 'audit':
127
+ case 'traffic':
128
+ case 'ips':
129
+ case 'debug':
130
+ case 'users':
131
+
132
+ $sAsset = 'shield-tables';
133
+ $sUnique = $con->prefix( $sAsset );
134
+ wp_register_script(
135
+ $sUnique,
136
+ $con->getPluginUrl_Js( $sAsset ),
137
+ $aStdDepsJs,
138
+ $con->getVersion(),
139
+ false
140
+ );
141
+ wp_enqueue_script( $sUnique );
142
+
143
+ $aStdDepsJs[] = $sUnique;
144
+ if ( $iNav == 'scans' ) {
145
+ $sAsset = 'shield-scans';
146
+ $sUnique = $con->prefix( $sAsset );
147
+ wp_register_script(
148
+ $sUnique,
149
+ $con->getPluginUrl_Js( $sAsset ),
150
+ array_unique( $aStdDepsJs ),
151
+ $con->getVersion(),
152
+ false
153
+ );
154
+ wp_enqueue_script( $sUnique );
155
+ }
156
+
157
+ if ( $iNav == 'ips' ) {
158
+ $sAsset = 'shield/ipanalyse';
159
+ $sUnique = $con->prefix( $sAsset );
160
+ wp_register_script(
161
+ $sUnique,
162
+ $con->getPluginUrl_Js( $sAsset ),
163
+ array_unique( $aStdDepsJs ),
164
+ $con->getVersion(),
165
+ false
166
+ );
167
+ wp_enqueue_script( $sUnique );
168
+ }
169
+
170
+ if ( in_array( $iNav, [ 'audit', 'traffic' ] ) ) {
171
+ $sUnique = $con->prefix( 'datepicker' );
172
+ wp_register_script(
173
+ $sUnique, //TODO: use an includes services for CNDJS
174
+ 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.8.0/js/bootstrap-datepicker.min.js',
175
+ array_unique( $aStdDepsJs ),
176
+ $con->getVersion(),
177
+ false
178
+ );
179
+ wp_enqueue_script( $sUnique );
180
+
181
+ wp_register_style(
182
+ $sUnique,
183
+ 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.8.0/css/bootstrap-datepicker.min.css',
184
+ [],
185
+ $con->getVersion(),
186
+ false
187
+ );
188
+ wp_enqueue_style( $sUnique );
189
+ }
190
+
191
+ break;
192
+ }
193
+
194
+ wp_localize_script(
195
+ $con->prefix( 'plugin' ),
196
+ 'icwp_wpsf_vars_insights',
197
+ [
198
+ 'strings' => [
199
+ 'downloading_file' => __( 'Downloading file, please wait...', 'wp-simple-firewall' ),
200
+ 'downloading_file_problem' => __( 'There was a problem downloading the file.', 'wp-simple-firewall' ),
201
+ 'select_action' => __( 'Please select an action to perform.', 'wp-simple-firewall' ),
202
+ 'are_you_sure' => __( 'Are you sure?', 'wp-simple-firewall' ),
203
+ ],
204
+ ]
205
+ );
206
+ }
207
+ }
208
+
209
+ private function includeScriptIpDetect() {
210
+ $con = $this->getCon();
211
+ /** @var Modules\Plugin\Options $opts */
212
+ $opts = $con->getModule_Plugin()->getOptions();
213
+ if ( $opts->isIpSourceAutoDetect() ) {
214
+ wp_register_script(
215
+ $con->prefix( 'ip_detect' ),
216
+ $con->getPluginUrl_Js( 'ip_detect.js' ),
217
+ [],
218
+ $con->getVersion(),
219
+ true
220
+ );
221
+ wp_enqueue_script( $con->prefix( 'ip_detect' ) );
222
+
223
+ wp_localize_script(
224
+ $con->prefix( 'ip_detect' ),
225
+ 'icwp_wpsf_vars_ipdetect',
226
+ [ 'ajax' => $con->getModule_Plugin()->getAjaxActionData( 'ipdetect' ) ]
227
+ );
228
+ }
229
+ }
230
+ }
src/lib/src/Modules/Insights/Options.php CHANGED
@@ -1,9 +1,9 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
 
7
- class Options extends Base\ShieldOptions {
8
 
9
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
 
7
+ class Options extends BaseShield\Options {
8
 
9
  }
src/lib/src/Modules/Insights/UI.php CHANGED
@@ -2,63 +2,268 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib\OverviewCards;
 
7
  use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Changelog\Retrieve;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
10
- class UI extends Base\ShieldUI {
11
 
12
- public function buildInsightsVars() :array {
13
  $con = $this->getCon();
14
-
15
- /** @var \FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\UI $uiReporting */
16
- $uiReporting = $con->getModule_Reporting()->getUIHandler();
17
-
18
  return [
19
  'content' => [
20
  'tab_updates' => $this->renderTabUpdates(),
21
  'tab_freetrial' => $this->renderFreeTrial(),
22
- 'summary_stats' => $uiReporting->renderSummaryStats()
23
  ],
 
 
 
 
 
 
 
 
 
 
 
 
24
  'vars' => [
25
  'overview_cards' => ( new OverviewCards() )
26
  ->setMod( $this->getMod() )
27
  ->buildForShuffle(),
28
  ],
29
- 'hrefs' => [
30
- 'shield_pro_url' => 'https://shsec.io/shieldpro',
31
- 'shield_pro_more_info_url' => 'https://shsec.io/shld1',
32
- ],
33
- 'flags' => [
34
- 'show_ads' => false,
35
- 'show_standard_options' => false,
36
- 'show_alt_content' => true,
37
- 'is_pro' => $con->isPremiumActive(),
38
- ],
39
  'strings' => [
40
- 'tab_security_glance' => __( 'Security At A Glance', 'wp-simple-firewall' ),
41
- 'tab_freetrial' => __( 'Free Trial', 'wp-simple-firewall' ),
42
- 'tab_updates' => __( 'Updates and Changes', 'wp-simple-firewall' ),
43
- 'tab_summary_stats' => __( 'Summary Stats', 'wp-simple-firewall' ),
44
- 'click_filter_status' => __( 'Click To Filter By Security Status', 'wp-simple-firewall' ),
45
- 'click_filter_area' => __( 'Click To Filter By Security Area', 'wp-simple-firewall' ),
46
- 'discover' => __( 'Discover where your site security is doing well or areas that can be improved', 'wp-simple-firewall' ),
47
- 'clear_filter' => __( 'Clear Filter', 'wp-simple-firewall' ),
48
- 'click_to_toggle' => __( 'click to toggle', 'wp-simple-firewall' ),
49
- 'go_to_options' => sprintf(
50
  __( 'Go To %s', 'wp-simple-firewall' ),
51
  __( 'Options' )
52
  ),
53
- 'key' => __( 'Key' ),
54
- 'key_positive' => __( 'Positive Security', 'wp-simple-firewall' ),
55
- 'key_warning' => __( 'Potential Warning', 'wp-simple-firewall' ),
56
- 'key_danger' => __( 'Potential Danger', 'wp-simple-firewall' ),
57
- 'key_information' => __( 'Information', 'wp-simple-firewall' ),
58
  ],
59
  ];
60
  }
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  private function renderFreeTrial() :string {
63
  $user = Services::WpUsers()->getCurrentWpUser();
64
  return $this->getMod()
@@ -66,7 +271,6 @@ class UI extends Base\ShieldUI {
66
  '/forms/drip_trial_signup.twig',
67
  [
68
  'vars' => [
69
- // the keys here must match the changelog item types
70
  'activation_url' => Services::WpGeneral()->getHomeUrl(),
71
  'email' => $user->user_email,
72
  'name' => $user->user_firstname,
@@ -116,4 +320,66 @@ class UI extends Base\ShieldUI {
116
  true
117
  );
118
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib\OverviewCards;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Insights\AdminNotes;
9
  use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Changelog\Retrieve;
10
  use FernleafSystems\Wordpress\Services\Services;
11
 
12
+ class UI extends BaseShield\UI {
13
 
14
+ public function buildInsightsVars_Docs() :array {
15
  $con = $this->getCon();
 
 
 
 
16
  return [
17
  'content' => [
18
  'tab_updates' => $this->renderTabUpdates(),
19
  'tab_freetrial' => $this->renderFreeTrial(),
 
20
  ],
21
+ 'flags' => [
22
+ 'is_pro' => $con->isPremiumActive(),
23
+ ],
24
+ 'strings' => [
25
+ 'tab_freetrial' => __( 'Free Trial', 'wp-simple-firewall' ),
26
+ 'tab_updates' => __( 'Updates and Changes', 'wp-simple-firewall' ),
27
+ ],
28
+ ];
29
+ }
30
+
31
+ private function buildInsightsVars_Overview() :array {
32
+ return [
33
  'vars' => [
34
  'overview_cards' => ( new OverviewCards() )
35
  ->setMod( $this->getMod() )
36
  ->buildForShuffle(),
37
  ],
 
 
 
 
 
 
 
 
 
 
38
  'strings' => [
39
+ 'click_clear_filter' => __( 'Click To Filter By Security Area or Status', 'wp-simple-firewall' ),
40
+ 'clear_filter' => __( 'Clear Filter', 'wp-simple-firewall' ),
41
+ 'go_to_options' => sprintf(
 
 
 
 
 
 
 
42
  __( 'Go To %s', 'wp-simple-firewall' ),
43
  __( 'Options' )
44
  ),
 
 
 
 
 
45
  ],
46
  ];
47
  }
48
 
49
+ public function renderPages() :string {
50
+ $con = $this->getCon();
51
+ /** @var ModCon $mod */
52
+ $mod = $this->getMod();
53
+ $req = Services::Request();
54
+
55
+ $sNavSection = $req->query( 'inav', 'dashboard' );
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(),
72
+ ],
73
+ 'vars' => [
74
+ 'related_hrefs' => [
75
+ [
76
+ 'href' => $con->getModule_AuditTrail()->getUrl_AdminPage(),
77
+ 'title' => __( 'Audit Trail Settings', 'wp-simple-firewall' ),
78
+ ],
79
+ [
80
+ 'href' => 'https://shsec.io/audittrailglossary',
81
+ 'title' => __( 'Audit Trail Glossary', 'wp-simple-firewall' ),
82
+ 'new' => true,
83
+ ],
84
+ [
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(),
99
+ ],
100
+ 'vars' => [
101
+ 'related_hrefs' => [
102
+ [
103
+ 'href' => $con->getModule_Traffic()->getUrl_AdminPage(),
104
+ 'title' => __( 'Traffic Settings', 'wp-simple-firewall' ),
105
+ ],
106
+ [
107
+ 'href' => $mod->getUrl_SubInsightsPage( 'audit' ),
108
+ 'title' => __( 'Audit Trail', 'wp-simple-firewall' ),
109
+ ],
110
+ ]
111
+ ]
112
+ ];
113
+ break;
114
+
115
+ case 'dashboard':
116
+ /** @var Shield\Modules\Plugin\UI $UI */
117
+ $UI = $con->getModule_Plugin()->getUIHandler();
118
+ $data = $UI->buildInsightsVars_Dashboard();
119
+ break;
120
+
121
+ case 'debug':
122
+ /** @var Shield\Modules\Plugin\UI $UI */
123
+ $UI = $con->getModule_Plugin()->getUIHandler();
124
+ $data = $UI->buildInsightsVars_Debug();
125
+ break;
126
+
127
+ case 'docs':
128
+ $data = $this->buildInsightsVars_Docs();
129
+ break;
130
+
131
+ case 'free_trial':
132
+ $data = [
133
+ 'content' => [
134
+ 'free_trial' => $this->renderFreeTrial()
135
+ ]
136
+ ];
137
+ break;
138
+
139
+ case 'importexport':
140
+ $data = $modPlugin->getImpExpController()->buildInsightsVars();
141
+ break;
142
+
143
+ case 'ips':
144
+ /** @var Shield\Modules\IPs\UI $UI */
145
+ $UI = $con->getModule_IPs()->getUIHandler();
146
+ $data = $UI->buildInsightsVars();
147
+ break;
148
+
149
+ case 'license':
150
+ /** @var Shield\Modules\License\UI $UILicense */
151
+ $UILicense = $con->getModule_License()->getUIHandler();
152
+ $data = $UILicense->buildInsightsVars();
153
+ break;
154
+
155
+ case 'notes':
156
+ $data = [
157
+ 'content' => [
158
+ 'notes' => ( new AdminNotes() )
159
+ ->setMod( $modPlugin )
160
+ ->render()
161
+ ],
162
+ ];
163
+ break;
164
+
165
+ case 'reports':
166
+ /** @var Shield\Modules\Reporting\UI $UIReporting */
167
+ $UIReporting = $con->getModule_Reporting()->getUIHandler();
168
+ $data = $UIReporting->buildInsightsVars();
169
+ break;
170
+
171
+ case 'scans':
172
+ /** @var Shield\Modules\HackGuard\UI $UIHackGuard */
173
+ $UIHackGuard = $con->getModule_HackGuard()->getUIHandler();
174
+ $data = $UIHackGuard->buildInsightsVars();
175
+ break;
176
+
177
+ case 'settings':
178
+ $data = $con->modules[ $subNavSection ]->getUIHandler()->getBaseDisplayData();
179
+ break;
180
+
181
+ case 'users':
182
+ /** @var Shield\Modules\UserManagement\UI $UIUsers */
183
+ $UIUsers = $con->modules[ 'user_management' ]->getUIHandler();
184
+ $data = $UIUsers->buildInsightsVars();
185
+ break;
186
+
187
+ case 'overview':
188
+ case 'index':
189
+ $data = $this->buildInsightsVars_Overview();
190
+ break;
191
+ default:
192
+ throw new \Exception( 'Not available' );
193
+ }
194
+
195
+ $availablePages = [
196
+ 'settings' => __( 'Plugin Settings', 'wp-simple-firewall' ),
197
+ 'dashboard' => __( 'Dashboard', 'wp-simple-firewall' ),
198
+ 'overview' => __( 'Security Overview', 'wp-simple-firewall' ),
199
+ 'scans' => __( 'Scans', 'wp-simple-firewall' ),
200
+ 'docs' => __( 'Docs', 'wp-simple-firewall' ),
201
+ 'ips' => __( 'IP Management and Analysis', 'wp-simple-firewall' ),
202
+ 'audit' => __( 'Audit Trail', 'wp-simple-firewall' ),
203
+ 'traffic' => __( 'Traffic', 'wp-simple-firewall' ),
204
+ 'notes' => __( 'Admin Notes', 'wp-simple-firewall' ),
205
+ 'users' => __( 'User Sessions', 'wp-simple-firewall' ),
206
+ 'license' => __( 'ShieldPRO', 'wp-simple-firewall' ),
207
+ 'importexport' => __( 'Import / Export', 'wp-simple-firewall' ),
208
+ 'reports' => __( 'Reports', 'wp-simple-firewall' ),
209
+ 'debug' => __( 'Debug', 'wp-simple-firewall' ),
210
+ 'free_trial' => __( 'Free Trial', 'wp-simple-firewall' ),
211
+ ];
212
+
213
+ $modsToSearch = array_filter(
214
+ $mod->getModulesSummaryData(),
215
+ function ( $modSummary ) {
216
+ return !empty( $modSummary[ 'show_mod_opts' ] );
217
+ }
218
+ );
219
+
220
+ $pageTitle = $availablePages[ $sNavSection ];
221
+ if ( !empty( $subNavSection ) ) {
222
+ $pageTitle = sprintf( '%s: %s',
223
+ __( 'Settings', 'wp-simple-firewall' ), $modsToSearch[ $subNavSection ][ 'name' ] );
224
+ }
225
+
226
+ $DP = Services::DataManipulation();
227
+ $data = $DP->mergeArraysRecursive(
228
+ $this->getBaseDisplayData(),
229
+ [
230
+ 'classes' => [
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
249
+ ],
250
+ 'vars' => [
251
+ 'changelog_id' => $con->getPluginSpec()[ 'meta' ][ 'announcekit_changelog_id' ],
252
+ 'mods' => $this->buildSelectData_ModuleSettings(),
253
+ 'search_select' => $this->buildSelectData_OptionsSearch(),
254
+ 'active_module_settings' => $subNavSection
255
+ ],
256
+ ],
257
+ $data
258
+ );
259
+
260
+ return $mod->renderTemplate(
261
+ sprintf( '/wpadmin_pages/insights/%s/index.twig', $sNavSection ),
262
+ $data,
263
+ true
264
+ );
265
+ }
266
+
267
  private function renderFreeTrial() :string {
268
  $user = Services::WpUsers()->getCurrentWpUser();
269
  return $this->getMod()
271
  '/forms/drip_trial_signup.twig',
272
  [
273
  'vars' => [
 
274
  'activation_url' => Services::WpGeneral()->getHomeUrl(),
275
  'email' => $user->user_email,
276
  'name' => $user->user_firstname,
320
  true
321
  );
322
  }
323
+
324
+ public function printAdminFooterItems() {
325
+ $this->printGoProFooter();
326
+ $this->printToastTemplate();
327
+ }
328
+
329
+ private function printGoProFooter() {
330
+ $con = $this->getCon();
331
+ $nav = Services::Request()->query( 'inav', 'dashboard' );
332
+ echo $this->getMod()->renderTemplate(
333
+ 'snippets/go_pro_banner.twig',
334
+ [
335
+ 'flags' => [
336
+ 'show_promo' => $con->isModulePage()
337
+ && !$con->isPremiumActive()
338
+ && ( !in_array( $nav, [ 'scans' ] ) ),
339
+ ],
340
+ 'hrefs' => [
341
+ 'go_pro' => 'https://shsec.io/shieldgoprofeature',
342
+ ]
343
+ ]
344
+ );
345
+ }
346
+
347
+ private function printToastTemplate() {
348
+ if ( $this->getCon()->isModulePage() ) {
349
+ echo $this->getMod()->renderTemplate(
350
+ 'snippets/toaster.twig',
351
+ [
352
+ 'strings' => [
353
+ 'title' => $this->getCon()->getHumanName(),
354
+ ],
355
+ 'js_snippets' => []
356
+ ]
357
+ );
358
+ }
359
+ }
360
+
361
+ private function printPluginDeactivateSurvey() {
362
+ if ( Services::WpPost()->isCurrentPage( 'plugins.php' ) ) {
363
+
364
+ $opts = [
365
+ 'reason_confusing' => "It's too confusing",
366
+ 'reason_expected' => "It's not what I expected",
367
+ 'reason_accident' => "I downloaded it accidentally",
368
+ 'reason_alternative' => "I'm already using an alternative",
369
+ 'reason_trust' => "I don't trust the developer :(",
370
+ 'reason_not_work' => "It doesn't work",
371
+ 'reason_errors' => "I'm getting errors",
372
+ ];
373
+
374
+ echo $this->getMod()->renderTemplate( 'snippets/plugin-deactivate-survey.php', [
375
+ 'strings' => [
376
+ 'editing_restricted' => __( 'Editing this option is currently restricted.', 'wp-simple-firewall' ),
377
+ ],
378
+ 'inputs' => [
379
+ 'checkboxes' => Services::DataManipulation()->shuffleArray( $opts )
380
+ ],
381
+ 'js_snippets' => []
382
+ ] );
383
+ }
384
+ }
385
  }
src/lib/src/Modules/Integrations/Lib/MainWP/Client/Actions/ApiActionInit.php ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Client\Actions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
+
7
+ class ApiActionInit {
8
+
9
+ use ModConsumer;
10
+
11
+ public function run( string $action ) :array {
12
+
13
+ switch ( $action ) {
14
+ case 'license_check':
15
+ $valid = $this->getCon()
16
+ ->getModule_License()
17
+ ->getLicenseHandler()
18
+ ->verify()
19
+ ->hasValidWorkingLicense();
20
+ $response = [
21
+ 'success' => $valid,
22
+ 'message' => $valid ? __( 'ShieldPRO license verified', 'wp-simple-firewall' )
23
+ : __( "ShieldPRO license couldn't be found", 'wp-simple-firewall' )
24
+ ];
25
+ break;
26
+
27
+ case 'mwp_enable':
28
+ $intMod = $this->getCon()->getModule_Integrations();
29
+ $intOpts = $intMod->getOptions();
30
+ $intOpts->setOpt( 'enable_mainwp', 'Y' );
31
+ $intMod->saveModOptions();
32
+ $valid = $intOpts->isOpt( 'enable_mainwp', 'Y' );
33
+ $response = [
34
+ 'success' => $intOpts->isOpt( 'enable_mainwp', 'Y' ),
35
+ 'message' => $valid ? __( 'MainWP Integration Enabled', 'wp-simple-firewall' )
36
+ : __( "MainWP Integration couldn't be enabled.", 'wp-simple-firewall' )
37
+ ];
38
+ break;
39
+
40
+ default:
41
+ $response = [
42
+ 'success' => false,
43
+ 'message' => 'Not a supported Shield+MainWP Site Action',
44
+ ];
45
+ break;
46
+ }
47
+
48
+ return $response;
49
+ }
50
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Client/Actions/Init.php ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Client\Actions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\{
6
+ Client\Auth\ReproduceClientAuthByKey,
7
+ Controller
8
+ };
9
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops\AddIp;
10
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
11
+ use FernleafSystems\Wordpress\Services\Services;
12
+
13
+ class Init {
14
+
15
+ use ModConsumer;
16
+
17
+ public function run() {
18
+ if ( Controller::isMainWPChildVersionSupported() ) {
19
+
20
+ // Skip 2FA login if we can verify MainWP Authentication
21
+ add_filter( 'icwp_shield_2fa_skip', function ( $canSkip ) {
22
+ return $canSkip || ReproduceClientAuthByKey::Auth();
23
+ }, 20, 1 );
24
+
25
+ // Whitelist the MainWP Server IP
26
+ add_action( 'mainwp_child_site_stats', function () {
27
+ try {
28
+ ( new AddIp() )
29
+ ->setMod( $this->getCon()->getModule_IPs() )
30
+ ->setIP( Services::IP()->getRequestIp() )
31
+ ->toManualWhitelist( 'MainWP Server (automatically added)' );
32
+ }
33
+ catch ( \Exception $e ) {
34
+ }
35
+ }, 10, 0 );
36
+
37
+ // Augment Sync data with Shield Sync Data
38
+ add_filter( 'mainwp_site_sync_others_data', function ( $information, $othersData ) {
39
+ $con = $this->getCon();
40
+ if ( isset( $othersData[ $con->prefix( 'mainwp-sync' ) ] ) ) {
41
+ $information[ $con->prefix( 'mainwp-sync' ) ] = wp_json_encode( ( new Sync() )
42
+ ->setMod( $this->getMod() )
43
+ ->run() );
44
+ }
45
+ return $information;
46
+ }, 10, 2 );
47
+
48
+ // Execute custom actions via MainWP API.
49
+ add_filter( 'mainwp_child_extra_execution', function ( $information, $post ) {
50
+ $con = $this->getCon();
51
+ if ( !empty( $post[ $con->prefix( 'mainwp-action' ) ] ) ) {
52
+ $information[ $con->prefix( 'mainwp-action' ) ] =
53
+ wp_json_encode( ( new ApiActionInit() )
54
+ ->setMod( $this->getMod() )
55
+ ->run( $post[ $con->prefix( 'mainwp-action' ) ] ) );
56
+ }
57
+ return $information;
58
+ }, 10, 2 );
59
+ }
60
+ }
61
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Client/Actions/Sync.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Client\Actions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Options;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+
9
+ class Sync {
10
+
11
+ use ModConsumer;
12
+
13
+ public function run() :array {
14
+ $con = $this->getCon();
15
+ /** @var Options $intOpts */
16
+ $intOpts = $con->getModule_Integrations()->getOptions();
17
+ return [
18
+ 'meta' => $this->buildMetaData(),
19
+ 'modules' => ( $con->isPremiumActive() && $intOpts->isEnabledMainWP() ) ? $this->buildModulesData() : [],
20
+ ];
21
+ }
22
+
23
+ /**
24
+ * @return mixed[]
25
+ */
26
+ private function buildMetaData() :array {
27
+ $con = $this->getCon();
28
+ /** @var Options $intOpts */
29
+ $intOpts = $con->getModule_Integrations()->getOptions();
30
+ return [
31
+ 'is_pro' => $con->isPremiumActive(),
32
+ 'is_mainwp_on' => $con->isPremiumActive() && $intOpts->isEnabledMainWP(),
33
+ 'installed_at' => $con->getModule_Plugin()->getInstallDate(),
34
+ 'sync_at' => Services::Request()->ts(),
35
+ 'version' => $con->getVersion(),
36
+ 'has_update' => Services::WpPlugins()->isUpdateAvailable( $con->getPluginBaseFile() ),
37
+ ];
38
+ }
39
+
40
+ /**
41
+ * @return array[]
42
+ */
43
+ private function buildModulesData() :array {
44
+ $con = $this->getCon();
45
+ $data = [];
46
+ foreach ( $con->modules as $mod ) {
47
+ $data[ $mod->getSlug() ] = $mod->getMainWpData();
48
+ }
49
+ return $data;
50
+ }
51
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Client/Auth/ReproduceClientAuthByKey.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Client\Auth;
4
+
5
+ use FernleafSystems\Wordpress\Services\Services;
6
+ use MainWP\Child\MainWP_Connect;
7
+
8
+ /**
9
+ * This reproduces the authentication done by the MainWP client in MainWP_Child::parse_init().
10
+ *
11
+ * Class ReproduceClientAuthByKey
12
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Client\Auth
13
+ */
14
+ class ReproduceClientAuthByKey {
15
+
16
+ public static function Auth() :bool {
17
+ $req = Services::Request();
18
+
19
+ // 'function' for actions, 'where' for login
20
+ $functionOrWhere = $req->request( 'function' );
21
+ if ( empty( $functionOrWhere ) ) {
22
+ $functionOrWhere = $req->request( 'where' );
23
+ }
24
+
25
+ return (bool)MainWP_Connect::instance()->auth(
26
+ rawurldecode( (string)$req->request( 'mainwpsignature', '' ) ),
27
+ sanitize_text_field( $functionOrWhere ),
28
+ sanitize_text_field( $req->request( 'nonce' ) ),
29
+ sanitize_text_field( $req->request( 'nossl' ) )
30
+ );
31
+ }
32
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Common/Consumers/MWPSiteConsumer.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common\Consumers;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common\MWPSiteVO;
6
+
7
+ /**
8
+ * Trait MWPSiteConsumer
9
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common\Consumers
10
+ */
11
+ trait MWPSiteConsumer {
12
+
13
+ /**
14
+ * @var MWPSiteVO
15
+ */
16
+ private $mwpSite;
17
+
18
+ public function getMwpSite() :MWPSiteVO {
19
+ return $this->mwpSite;
20
+ }
21
+
22
+ public function setMwpSite( MWPSiteVO $site ) :self {
23
+ $this->mwpSite = $site;
24
+ return $this;
25
+ }
26
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Common/MWPExtensionVO.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
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
+ /**
9
+ * Class MWPExtensionVO
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 {
39
+ $data = [];
40
+ foreach ( MainWP_Extensions_Handler::get_extensions() as $ext ) {
41
+ if ( $ext[ 'plugin' ] === $this->child_file ) {
42
+ $data = $ext;
43
+ break;
44
+ }
45
+ }
46
+ return $data;
47
+ }
48
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Common/MWPSiteVO.php ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
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
+
9
+ /**
10
+ * Class MWPSiteVO
11
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common
12
+ * @property object $siteobj // For use with MainWP functions
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
24
+ * @return MWPSiteVO
25
+ * @throws \Exception
26
+ */
27
+ public static function LoadByID( int $siteID ) :MWPSiteVO {
28
+ $raw = MainWP_DB::instance()->get_website_by_id( $siteID );
29
+ if ( empty( $raw ) ) {
30
+ throw new \Exception( 'Invalid Site ID' );
31
+ }
32
+ return ( new MWPSiteVO() )->applyFromArray(
33
+ Services::DataManipulation()->convertStdClassToArray( $raw )
34
+ );
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
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Common/MainWPVO.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
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
+ /**
9
+ * Class MainwpVO
10
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common
11
+ * @property string $child_key
12
+ * @property string $child_file
13
+ * @property bool $is_client
14
+ * @property bool $is_server
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 {
47
+ $data = [];
48
+ foreach ( MainWP_Extensions_Handler::get_extensions() as $ext ) {
49
+ if ( $ext[ 'plugin' ] === $this->child_file ) {
50
+ $data = $ext;
51
+ break;
52
+ }
53
+ }
54
+ return $data;
55
+ }
56
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Common/SyncMetaVO.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
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
9
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common
10
+ * @property bool $is_pro
11
+ * @property bool $is_mainwp_on
12
+ * @property int $installed_at
13
+ * @property int $sync_at
14
+ * @property string $version
15
+ * @property bool $has_update
16
+ */
17
+ class SyncMetaVO {
18
+
19
+ use StdClassAdapter;
20
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Common/SyncVO.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
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
9
+ * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common
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;
31
+ default:
32
+ break;
33
+ }
34
+
35
+ return $mValue;
36
+ }
37
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Controller.php ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
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;
9
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
10
+
11
+ class Controller {
12
+
13
+ use ModConsumer;
14
+ use OneTimeExecute;
15
+
16
+ const MIN_VERSION_MAINWP = '4.1';
17
+
18
+ protected function run() {
19
+ try {
20
+ $this->runServerSide();
21
+ }
22
+ catch ( \Exception $e ) {
23
+ }
24
+ try {
25
+ $this->runClientSide();
26
+ }
27
+ catch ( \Exception $e ) {
28
+ }
29
+ }
30
+
31
+ /**
32
+ * @throws \Exception
33
+ */
34
+ private function runClientSide() {
35
+ $con = $this->getCon();
36
+ $mwpVO = $con->mwpVO ?? new MainWPVO();
37
+ $mwpVO->is_client = $this->isMainWPChildActive();
38
+
39
+ if ( !$mwpVO->is_client ) {
40
+ throw new \Exception( 'MainWP Child not active' );
41
+ }
42
+
43
+ ( new Client\Actions\Init() )
44
+ ->setMod( $this->getMod() )
45
+ ->run();
46
+
47
+ $con->mwpVO = $mwpVO;
48
+ }
49
+
50
+ /**
51
+ * @throws \Exception
52
+ */
53
+ private function runServerSide() {
54
+ $con = $this->getCon();
55
+ $mwpVO = $con->mwpVO ?? new MainWPVO();
56
+ $mwpVO->is_server = false;
57
+
58
+ if ( !$this->isMainWPServerActive() ) {
59
+ throw new \Exception( 'MainWP not active' );
60
+ }
61
+
62
+ $mwpVO->child_key = ( new Server\Init() )
63
+ ->setMod( $this->getMod() )
64
+ ->run();
65
+ $mwpVO->child_file = $con->getRootFile();
66
+
67
+ $mwpVO->is_server = true;
68
+
69
+ $con->mwpVO = $mwpVO;
70
+ }
71
+
72
+ private function isMainWPChildActive() :bool {
73
+ return @class_exists( '\MainWP\Child\MainWP_Child' );
74
+ }
75
+
76
+ private function isMainWPServerActive() :bool {
77
+ return (bool)apply_filters( 'mainwp_activated_check', false );
78
+ }
79
+
80
+ public static function isMainWPChildVersionSupported() :bool {
81
+ return version_compare( \MainWP\Child\MainWP_Child::$version, self::MIN_VERSION_MAINWP, '>=' );
82
+ }
83
+
84
+ public static function isMainWPServerVersionSupported() :bool {
85
+ return defined( 'MAINWP_VERSION' )
86
+ && version_compare( MAINWP_VERSION, self::MIN_VERSION_MAINWP, '>=' );
87
+ }
88
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Server/Actions/Action.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\Actions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
+ use MainWP\Dashboard\MainWP_Connect;
8
+
9
+ class Action {
10
+
11
+ use ModConsumer;
12
+ use MainWP\Common\Consumers\MWPSiteConsumer;
13
+
14
+ /**
15
+ * @param string $actionToExecute
16
+ * @param array $params
17
+ * @return array
18
+ * @throws \Exception
19
+ */
20
+ public function run( string $actionToExecute, array $params = [] ) :array {
21
+ $info = MainWP_Connect::fetch_url_authed(
22
+ $this->getMwpSite()->siteobj,
23
+ 'extra_execution',
24
+ [
25
+ $this->getCon()->prefix( 'mainwp-action' ) => $actionToExecute,
26
+ $this->getCon()->prefix( 'mainwp-params' ) => $params
27
+ ]
28
+ );
29
+
30
+ $key = $this->getCon()->prefix( 'mainwp-action' );
31
+ if ( empty( $info ) || !is_array( $info ) || !isset( $info[ $key ] ) ) {
32
+ throw new \Exception( 'Empty response from client site' );
33
+ }
34
+
35
+ $decoded = json_decode( $info[ $key ], true );
36
+ if ( empty( $decoded ) || !is_array( $decoded ) ) {
37
+ throw new \Exception( 'Invalid response from client site' );
38
+ }
39
+
40
+ return $decoded;
41
+ }
42
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Server/Actions/ShieldApiAction.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\Actions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\{
6
+ Common\Consumers\MWPSiteConsumer
7
+ };
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
9
+
10
+ class ShieldApiAction {
11
+
12
+ use ModConsumer;
13
+ use MWPSiteConsumer;
14
+
15
+ public function mwpEnable() :array {
16
+ return $this->runAction( 'mwp_enable' );
17
+ }
18
+
19
+ public function licenseCheck() :array {
20
+ return $this->runAction( 'license_check' );
21
+ }
22
+
23
+ private function runAction( string $action, array $params = [] ) :array {
24
+ return ( new Action() )
25
+ ->setMod( $this->getMod() )
26
+ ->setMwpSite( $this->getMwpSite() )
27
+ ->run( $action, $params );
28
+ }
29
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Server/Actions/ShieldPluginAction.php ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\Actions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\{
6
+ Common\Consumers\MWPSiteConsumer,
7
+ Server\Data\ClientPluginStatus
8
+ };
9
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
10
+ use FernleafSystems\Wordpress\Services\Utilities\WpOrg\Plugin\Api;
11
+ use MainWP\Dashboard\MainWP_Connect;
12
+ use MainWP\Dashboard\MainWP_Sync;
13
+
14
+ class ShieldPluginAction {
15
+
16
+ use ModConsumer;
17
+ use MWPSiteConsumer;
18
+
19
+ public function alignByStatus() {
20
+ $oStatus = ( new ClientPluginStatus() )
21
+ ->setMod( $this->getMod() )
22
+ ->setMwpSite( $this->getMwpSite() );
23
+
24
+ switch ( $oStatus->status() ) {
25
+
26
+ case ClientPluginStatus::INACTIVE:
27
+ if ( $this->activate() ) {
28
+ $this->sync();
29
+ }
30
+ break;
31
+
32
+ case ClientPluginStatus::NEED_SYNC:
33
+ $this->sync();
34
+ break;
35
+
36
+ case ClientPluginStatus::VERSION_OLDER_THAN_SERVER:
37
+ $this->sync();
38
+ $this->upgrade();
39
+ $this->sync();
40
+ break;
41
+
42
+ case ClientPluginStatus::NOT_INSTALLED:
43
+ $this->install();
44
+ $this->sync();
45
+ break;
46
+
47
+ case ClientPluginStatus::ACTIVE:
48
+ default:
49
+ // nothing
50
+ break;
51
+ }
52
+ }
53
+
54
+ public function activate() :bool {
55
+ $siteObj = $this->getMwpSite()->siteobj;
56
+ $info = MainWP_Connect::fetch_url_authed(
57
+ $siteObj,
58
+ 'plugin_action',
59
+ [
60
+ 'action' => 'activate',
61
+ 'plugin' => ( new ClientPluginStatus() )
62
+ ->setMod( $this->getMod() )
63
+ ->setMwpSite( $this->getMwpSite() )
64
+ ->getInstalledPlugin()[ 'slug' ],
65
+ ]
66
+ );
67
+
68
+ $status = $info[ 'status' ] ?? false;
69
+ return $status === 'SUCCESS';
70
+ }
71
+
72
+ public function deactivate() :bool {
73
+ $siteObj = $this->getMwpSite()->siteobj;
74
+ $info = MainWP_Connect::fetch_url_authed(
75
+ $siteObj,
76
+ 'plugin_action',
77
+ [
78
+ 'action' => 'deactivate',
79
+ 'plugin' => ( new ClientPluginStatus() )
80
+ ->setMod( $this->getMod() )
81
+ ->setMwpSite( $this->getMwpSite() )
82
+ ->getInstalledPlugin()[ 'slug' ],
83
+ ]
84
+ );
85
+
86
+ $status = $info[ 'status' ] ?? false;
87
+ return $status === 'SUCCESS';
88
+ }
89
+
90
+ public function install() :bool {
91
+ $urlInstall = ( new Api() )
92
+ ->setWorkingSlug( 'wp-simple-firewall' )
93
+ ->getInfo()->download_link;
94
+
95
+ $info = MainWP_Connect::fetch_url_authed(
96
+ $this->getMwpSite()->siteobj,
97
+ 'installplugintheme',
98
+ [
99
+ 'type' => 'plugin',
100
+ 'url' => wp_json_encode( $urlInstall ),
101
+ 'activatePlugin' => 'yes',
102
+ 'overwrite' => true,
103
+ ]
104
+ );
105
+ return !empty( $info[ 'installation' ] ) && $info[ 'installation' ] === 'SUCCESS';
106
+ }
107
+
108
+ public function sync() :bool {
109
+ return (bool)MainWP_Sync::sync_site( $this->getMwpSite()->siteobj );
110
+ }
111
+
112
+ public function upgrade() :bool {
113
+ $siteObj = $this->getMwpSite()->siteobj;
114
+ MainWP_Connect::fetch_url_authed(
115
+ $siteObj,
116
+ 'upgradeplugintheme',
117
+ [
118
+ 'type' => 'plugin',
119
+ 'list' => ( new ClientPluginStatus() )
120
+ ->setMod( $this->getMod() )
121
+ ->setMwpSite( $this->getMwpSite() )
122
+ ->getInstalledPlugin()[ 'slug' ],
123
+ ],
124
+ true
125
+ );
126
+ return true;
127
+ }
128
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Server/Ajax/AjaxHandlerMainwp.php ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\Ajax;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+
9
+ class AjaxHandlerMainwp extends Shield\Modules\BaseShield\AjaxHandler {
10
+
11
+ protected function processAjaxAction( string $action ) :array {
12
+ $resp = [];
13
+
14
+ // This allows us to provide a specific MainWP error message
15
+ if ( strpos( $action, 'mwp_' ) === 0 ) {
16
+
17
+ switch ( $action ) {
18
+ case 'mwp_sh_ext_table':
19
+ $resp = $this->ajaxExec_SiteAction();
20
+ break;
21
+
22
+ case 'mwp_sh_site_action':
23
+ $resp = $this->ajaxExec_ExtensionTableSites();
24
+ break;
25
+
26
+ default:
27
+ $resp = [
28
+ 'success' => false,
29
+ 'message' => sprintf( __( 'Not a supported MainWP+%s action.' ),
30
+ $this->getCon()->getHumanName() )
31
+ ];
32
+ }
33
+ }
34
+
35
+ return $resp;
36
+ }
37
+
38
+ private function ajaxExec_SiteAction() :array {
39
+ $req = Services::Request();
40
+
41
+ $siteID = (int)$req->post( 'sid' );
42
+ $action = $req->post( 'saction' );
43
+ try {
44
+ if ( empty( $siteID ) ) {
45
+ throw new \Exception( 'invalid site ID' );
46
+ }
47
+ $resp = ( new PerformSiteAction() )
48
+ ->setMwpSite( MainWP\Common\MWPSiteVO::LoadByID( $siteID ) )
49
+ ->setMod( $this->getMod() )
50
+ ->run( $action );
51
+ }
52
+ catch ( \Exception $e ) {
53
+ $resp = [
54
+ 'success' => false,
55
+ 'message' => $e->getMessage()
56
+ ];
57
+ }
58
+
59
+ return $resp;
60
+ }
61
+
62
+ private function ajaxExec_ExtensionTableSites() {
63
+
64
+ }
65
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Server/Ajax/PerformSiteAction.php ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\Ajax;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server;
8
+
9
+ class PerformSiteAction {
10
+
11
+ use MainWP\Common\Consumers\MWPSiteConsumer;
12
+ use Shield\Modules\ModConsumer;
13
+
14
+ public function run( string $action ) :array {
15
+ try {
16
+ $resp = [
17
+ 'success' => true,
18
+ 'message' => $this->{$action}()
19
+ ];
20
+ }
21
+ catch ( \Exception $e ) {
22
+ $resp = [
23
+ 'success' => false,
24
+ 'message' => $e->getMessage(),
25
+ ];
26
+ }
27
+
28
+ if ( !in_array( $action, [ 'sync' ] ) ) {
29
+ $this->getPluginActioner()->sync();
30
+ }
31
+
32
+ $resp[ 'page_reload' ] = true;
33
+ return $resp;
34
+ }
35
+
36
+ /**
37
+ * @return string
38
+ * @throws \Exception
39
+ */
40
+ private function activate() :string {
41
+ if ( !$this->getPluginActioner()->activate() ) {
42
+ throw new \Exception( sprintf( __( 'Failed to activate %s plugin.' ), $this->getCon()->getHumanName() ) );
43
+ }
44
+ return sprintf( __( 'Successfully activated %s plugin.', 'wp-simple-firewall' ),
45
+ $this->getCon()->getHumanName() );
46
+ }
47
+
48
+ /**
49
+ * @return string
50
+ * @throws \Exception
51
+ */
52
+ private function deactivate() :string {
53
+ if ( !$this->getPluginActioner()->deactivate() ) {
54
+ throw new \Exception( sprintf( __( 'Failed to deactivate %s plugin.' ), $this->getCon()->getHumanName() ) );
55
+ }
56
+ return sprintf( __( 'Successfully deactivated %s plugin.', 'wp-simple-firewall' ),
57
+ $this->getCon()->getHumanName() );
58
+ }
59
+
60
+ /**
61
+ * @return string
62
+ * @throws \Exception
63
+ */
64
+ private function install() :string {
65
+ if ( !$this->getPluginActioner()->install() ) {
66
+ throw new \Exception( sprintf( __( 'Failed to install %s plugin.' ), $this->getCon()->getHumanName() ) );
67
+ }
68
+ return sprintf( __( 'Successfully installed %s plugin.', 'wp-simple-firewall' ),
69
+ $this->getCon()->getHumanName() );
70
+ }
71
+
72
+ /**
73
+ * @return string
74
+ * @throws \Exception
75
+ */
76
+ private function license() :string {
77
+ $resp = $this->getApiActioner()->licenseCheck();
78
+ if ( empty( $resp[ 'success' ] ) ) {
79
+ throw new \Exception( $resp[ 'message' ] );
80
+ }
81
+ return $resp[ 'message' ];
82
+ }
83
+
84
+ /**
85
+ * @return string
86
+ * @throws \Exception
87
+ */
88
+ private function mwp() :string {
89
+ $resp = $this->getApiActioner()->mwpEnable();
90
+ if ( empty( $resp[ 'success' ] ) ) {
91
+ throw new \Exception( $resp[ 'message' ] );
92
+ }
93
+ return $resp[ 'message' ];
94
+ }
95
+
96
+ /**
97
+ * @return string
98
+ * @throws \Exception
99
+ */
100
+ private function sync() :string {
101
+ if ( !$this->getPluginActioner()->sync() ) {
102
+ throw new \Exception( sprintf( __( 'Failed to sync with %s plugin.' ), $this->getCon()->getHumanName() ) );
103
+ }
104
+ return sprintf( __( 'Successfully synced with %s plugin.', 'wp-simple-firewall' ),
105
+ $this->getCon()->getHumanName() );
106
+ }
107
+
108
+ private function getPluginActioner() :Server\Actions\ShieldPluginAction {
109
+ return ( new Server\Actions\ShieldPluginAction() )
110
+ ->setMod( $this->getMod() )
111
+ ->setMwpSite( $this->getMwpSite() );
112
+ }
113
+
114
+ private function getApiActioner() :Server\Actions\ShieldApiAction {
115
+ return ( new Server\Actions\ShieldApiAction() )
116
+ ->setMod( $this->getMod() )
117
+ ->setMwpSite( $this->getMwpSite() );
118
+ }
119
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Server/Data/ClientPluginStatus.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\Data;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common\Consumers\MWPSiteConsumer;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
+
8
+ class ClientPluginStatus {
9
+
10
+ use ModConsumer;
11
+ use MWPSiteConsumer;
12
+
13
+ const ACTIVE = 'acti';
14
+ const NEED_SYNC = 'nsync';
15
+ const NOT_PRO = 'npro';
16
+ const MWP_NOT_ON = 'mwpnoton';
17
+ const INACTIVE = 'inact';
18
+ const NOT_INSTALLED = 'ninst';
19
+ const VERSION_NEWER_THAN_SERVER = 'vnts';
20
+ const VERSION_OLDER_THAN_SERVER = 'vots';
21
+
22
+ public function status() :string {
23
+ $status = $this->detect();
24
+ return key( $status );
25
+ }
26
+
27
+ /**
28
+ * TODO: Consider things like global disabled / forceoff
29
+ * @return array
30
+ */
31
+ public function detect() :array {
32
+ $sync = LoadShieldSyncData::Load( $this->getMwpSite() );
33
+ $m = $sync->meta;
34
+
35
+ if ( $this->isActive() ) {
36
+
37
+ if ( empty( $sync->getRawDataAsArray() ) ) {
38
+ $status = self::NEED_SYNC;
39
+ }
40
+ elseif ( empty( $m->is_pro ) ) {
41
+ $status = self::NOT_PRO;
42
+ }
43
+ elseif ( empty( $m->is_mainwp_on ) ) {
44
+ $status = self::MWP_NOT_ON;
45
+ }
46
+ else {
47
+ $versionStatus = version_compare( $this->getCon()->getVersion(), $m->version );
48
+ if ( $versionStatus === -1 ) {
49
+ $status = self::VERSION_NEWER_THAN_SERVER;
50
+ }
51
+ elseif ( $versionStatus === 1 ) {
52
+ $status = self::VERSION_OLDER_THAN_SERVER;
53
+ }
54
+ else {
55
+ $status = self::ACTIVE;
56
+ }
57
+ }
58
+ }
59
+ elseif ( $this->isInstalled() ) {
60
+ $status = self::INACTIVE;
61
+ }
62
+ else {
63
+ $status = self::NOT_INSTALLED;
64
+ }
65
+ return [ $status => $this->getStatusText()[ $status ] ];
66
+ }
67
+
68
+ /**
69
+ * @return array|null
70
+ */
71
+ public function getInstalledPlugin() {
72
+ $thePlugin = null;
73
+
74
+ $baseName = basename( $this->getCon()->getPluginBaseFile() );
75
+ foreach ( $this->getMwpSite()->plugins as $plugin ) {
76
+ if ( basename( $plugin[ 'slug' ] ) === $baseName ) {
77
+ $thePlugin = $plugin;
78
+ break;
79
+ }
80
+ }
81
+
82
+ return $thePlugin;
83
+ }
84
+
85
+ public function isActive() :bool {
86
+ return !empty( $this->getInstalledPlugin()[ 'active' ] );
87
+ }
88
+
89
+ public function isInstalled() :bool {
90
+ return !empty( $this->getInstalledPlugin() );
91
+ }
92
+
93
+ public function getStatusText() :array {
94
+ return [
95
+ self::ACTIVE => __( 'Active' ),
96
+ self::NOT_PRO => __( 'Not Pro' ),
97
+ self::MWP_NOT_ON => __( 'MainWP Option Not Enabled' ),
98
+ self::NEED_SYNC => __( 'Sync Required' ),
99
+ self::INACTIVE => __( 'Installed' ),
100
+ self::NOT_INSTALLED => __( 'Not Installed' ),
101
+ self::VERSION_OLDER_THAN_SERVER => __( 'Update Required' ),
102
+ self::VERSION_NEWER_THAN_SERVER => __( 'Ahead Of Server' ),
103
+ ];
104
+ }
105
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Server/Data/LoadShieldSyncData.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\Data;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Controller\Controller;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common\{
7
+ MWPSiteVO,
8
+ SyncVO};
9
+ use MainWP\Dashboard\MainWP_DB;
10
+
11
+ 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 ) ) {
19
+ $data = '[]';
20
+ }
21
+ return ( new SyncVO() )->applyFromArray( json_decode( $data, true ) );
22
+ }
23
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Server/Data/SyncHandler.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
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 ) {
16
+ $othersData[ $this->getCon()->prefix( 'mainwp-sync' ) ] = 'shield';
17
+ return $othersData;
18
+ }, 10, 2 );
19
+ add_action( 'mainwp_site_synced', function ( $website, $info ) {
20
+ $this->syncSite( $website, $info );
21
+ }, 10, 2 );
22
+ }
23
+
24
+ /**
25
+ * @param object $website
26
+ * @param array $info
27
+ */
28
+ private function syncSite( $website, array $info ) {
29
+ $con = $this->getCon();
30
+ MainWP_DB::instance()->update_website_option(
31
+ $website,
32
+ $con->prefix( 'mainwp-sync' ),
33
+ $info[ $con->prefix( 'mainwp-sync' ) ] ?? '[]'
34
+ );
35
+ }
36
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Server/Init.php ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Controller;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\Ajax\AjaxHandlerMainwp;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\Data\SyncHandler;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI\ExtensionSettingsPage;
9
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Options;
10
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
11
+ use FernleafSystems\Wordpress\Services\Services;
12
+
13
+ class Init {
14
+
15
+ use ModConsumer;
16
+
17
+ /**
18
+ * @return string
19
+ * @throws \Exception
20
+ */
21
+ public function run() :string {
22
+ $con = $this->getCon();
23
+ /** @var Options $integOpts */
24
+ $integOpts = $con->getModule_Integrations()->getOptions();
25
+
26
+ // TODO: Consider have an "error" screen message to show it's not enabled instead?
27
+ if ( !$integOpts->isEnabledMainWP() ) {
28
+ throw new \Exception( 'MainWP Extension is not enabled' );
29
+ }
30
+
31
+ $extensionsPage = ( new ExtensionSettingsPage() )->setMod( $this->getMod() );
32
+ add_filter( 'mainwp_getextensions', function ( $exts ) use ( $extensionsPage ) {
33
+ $con = $this->getCon();
34
+ $exts[] = [
35
+ 'plugin' => $this->getCon()->getRootFile(),
36
+ // while this is a "callback" field, a Closure isn't supported as it's serialized for DB storage. Sigh.
37
+ 'callback' => [ $extensionsPage, 'render' ],
38
+ 'icon' => $con->getPluginUrl_Image( 'pluginlogo_col_32x32.png' ),
39
+ ];
40
+ return $exts;
41
+ }, 10, 1 );
42
+
43
+ $childEnabled = apply_filters( 'mainwp_extension_enabled_check', $con->getRootFile() );
44
+ $key = $childEnabled[ 'key' ] ?? '';
45
+ if ( empty( $key ) ) {
46
+ throw new \Exception( 'No child key provided' );
47
+ }
48
+
49
+ if ( Controller::isMainWPServerVersionSupported() && $con->isPremiumActive() ) {
50
+ ( new SyncHandler() )
51
+ ->setMod( $this->getMod() )
52
+ ->execute();
53
+ ( new UI\ManageSites\SitesListTableHandler() )
54
+ ->setMod( $this->getMod() )
55
+ ->execute();
56
+ $extensionsPage->execute();
57
+
58
+ if ( $this->getMod()->isModuleRequest() && Services::WpGeneral()->isAjax() ) {
59
+ ( new AjaxHandlerMainwp() )->setMod( $this->getMod() );
60
+ }
61
+ }
62
+
63
+ return $key;
64
+ }
65
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/BaseRender.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
+
7
+ abstract class BaseRender {
8
+
9
+ use ModConsumer;
10
+
11
+ public function render() :string {
12
+ $con = $this->getCon();
13
+
14
+ try {
15
+ $output = $con->getRenderer()
16
+ ->setTemplateEngineTwig()
17
+ ->setTemplate( sprintf( '/integration/mainwp/%s.twig', $this->getTemplateSlug() ) )
18
+ ->setRenderVars( $this->getData() )
19
+ ->render();
20
+ }
21
+ catch ( \Exception $e ) {
22
+ $output = $e->getMessage();
23
+ }
24
+ return $output;
25
+ }
26
+
27
+ protected function getData() :array {
28
+ return [
29
+ 'content' => [
30
+ ],
31
+ 'vars' => [
32
+ ]
33
+ ];
34
+ }
35
+
36
+ abstract protected function getTemplateSlug() :string;
37
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/ExtensionSettingsPage.php ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
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\Modules\Integrations\Lib\MainWP\Controller;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI\PageRender;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
9
+ use FernleafSystems\Wordpress\Services\Services;
10
+
11
+ class ExtensionSettingsPage {
12
+
13
+ use ModConsumer;
14
+ use OneTimeExecute;
15
+
16
+ protected function run() {
17
+ add_action( 'admin_enqueue_scripts', function ( $hook ) {
18
+ $con = $this->getCon();
19
+ if ( 'mainwp_page_'.$con->mwpVO->extension->page === $hook ) {
20
+ $handle = $con->prefix( 'mainwp-extension' );
21
+ wp_register_script(
22
+ $handle,
23
+ $con->getPluginUrl_Js( 'shield/mainwp-extension.js' ),
24
+ [ 'jquery' ],
25
+ $con->getVersion(),
26
+ true
27
+ );
28
+ wp_enqueue_script( $handle );
29
+
30
+ wp_register_style(
31
+ $handle,
32
+ $con->getPluginUrl_Css( 'mainwp.css' ),
33
+ [],
34
+ $con->getVersion()
35
+ );
36
+ wp_enqueue_style( $handle );
37
+
38
+ // $handle = 'semantic-ui-datatables-select';
39
+ // wp_register_script(
40
+ // $handle,
41
+ // 'https://cdn.datatables.net/select/1.3.1/js/dataTables.select.min.js',
42
+ // [ 'semantic-ui-datatables' ],
43
+ // $con->getVersion(),
44
+ // true
45
+ // );
46
+ // wp_enqueue_script( 'semantic-ui-datatables-select' );
47
+ // wp_register_style(
48
+ // $handle,
49
+ // 'https://cdn.datatables.net/select/1.3.1/css/select.dataTables.min.css',
50
+ // [ 'semantic-ui-datatables' ],
51
+ // $con->getVersion()
52
+ // );
53
+ // wp_enqueue_style( 'semantic-ui-datatables-select' );
54
+ }
55
+ } );
56
+ }
57
+
58
+ /**
59
+ * @throws \Exception
60
+ */
61
+ public function render() {
62
+ $con = $this->getCon();
63
+ $req = Services::Request();
64
+
65
+ // Adjust the title at the top of the page so it's not "Wp Simple Firewall"
66
+ add_filter( 'mainwp_header_title', function () {
67
+ return $this->getCon()->getHumanName();
68
+ }, 100, 0 );
69
+
70
+ ob_start();
71
+ do_action( 'mainwp_pageheader_extensions', $this->getCon()->getRootFile() );
72
+ $mainwpHeader = ob_get_contents();
73
+ ob_clean();
74
+ do_action( 'mainwp_pagefooter_extensions', $this->getCon()->getRootFile() );
75
+ $mainwpFooter = ob_get_clean();
76
+
77
+ $currentTab = empty( $req->query( 'tab' ) ) ? 'sites' : $req->query( 'tab' );
78
+ if ( !$con->isPremiumActive() ) {
79
+ $pageRenderer = new PageRender\NotShieldPro();
80
+ }
81
+ elseif ( $this->serverPluginNeedsUpdate() ) {
82
+ $pageRenderer = new PageRender\PluginOutOfDate();
83
+ }
84
+ elseif ( !Controller::isMainWPServerVersionSupported() ) {
85
+ $pageRenderer = new PageRender\MwpOutOfDate();
86
+ }
87
+ else {
88
+ switch ( $currentTab ) {
89
+ case 'sites':
90
+ $pageRenderer = new PageRender\SitesList();
91
+ break;
92
+ default:
93
+ throw new \Exception( 'Not a supported tab' );
94
+ }
95
+ }
96
+
97
+ try {
98
+ echo $this->getMod()
99
+ ->renderTemplate(
100
+ '/integration/mainwp/page_extension.twig',
101
+ [
102
+ 'content' => [
103
+ 'mainwp_header' => $mainwpHeader,
104
+ 'mainwp_footer' => $mainwpFooter,
105
+ 'page_inner' => $pageRenderer->setMod( $this->getMod() )->render(),
106
+ ],
107
+ 'vars' => [
108
+ 'submenu' => [
109
+ [
110
+ 'title' => 'Sites',
111
+ 'href' => add_query_arg( [ 'tab' => 'sites' ], $req->getUri() ),
112
+ 'icon' => 'globe',
113
+ 'active' => $currentTab === 'sites',
114
+ ]
115
+ ],
116
+ ]
117
+ ],
118
+ true
119
+ );
120
+ }
121
+ catch ( \Exception $e ) {
122
+ var_dump( $e->getMessage() );
123
+ }
124
+ }
125
+
126
+ private function serverPluginNeedsUpdate() :bool {
127
+ return Services::WpPlugins()->isUpdateAvailable(
128
+ $this->getCon()->getPluginBaseFile()
129
+ );
130
+ }
131
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/ManageSites/SitesListTableHandler.php ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
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;
9
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI\BaseRender;
10
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
11
+ use FernleafSystems\Wordpress\Services\Services;
12
+
13
+ class SitesListTableHandler extends BaseRender {
14
+
15
+ use ModConsumer;
16
+ use OneTimeExecute;
17
+
18
+ /**
19
+ * @var MWPSiteVO
20
+ */
21
+ private $workingSite;
22
+
23
+ protected function run() {
24
+ add_filter( 'mainwp_sitestable_getcolumns', function ( $columns ) {
25
+ $columns[ 'shield' ] = 'Shield';
26
+ return $columns;
27
+ }, 10, 1 );
28
+ add_filter( 'mainwp_sitestable_item', function ( array $item ) {
29
+ $item[ 'shield' ] = $this->renderShieldColumnEntryForItem( $item );
30
+ return $item;
31
+ }, 10, 1 );
32
+ }
33
+
34
+ private function renderShieldColumnEntryForItem( array $item ) :string {
35
+ $this->workingSite = ( new MWPSiteVO() )->applyFromArray( $item );
36
+ return $this->render();
37
+ }
38
+
39
+ protected function getData() :array {
40
+ $con = $this->getCon();
41
+
42
+ $sync = LoadShieldSyncData::Load( $this->workingSite );
43
+ $status = ( new ClientPluginStatus() )
44
+ ->setMod( $this->getMod() )
45
+ ->setMwpSite( $this->workingSite )
46
+ ->detect();
47
+
48
+ $statusKey = key( $status );
49
+ $isActive = $statusKey === ClientPluginStatus::ACTIVE;
50
+ if ( $isActive ) {
51
+ $issuesCount = array_sum( $sync->modules[ 'hack_protect' ][ 'scan_issues' ] );
52
+ }
53
+ else {
54
+ $issuesCount = 0;
55
+ }
56
+
57
+ return [
58
+ 'flags' => [
59
+ 'is_active' => $isActive,
60
+ 'is_sync_rqd' => $statusKey === ClientPluginStatus::NEED_SYNC,
61
+ 'is_inactive' => $statusKey === ClientPluginStatus::INACTIVE,
62
+ 'is_notpro' => $statusKey === ClientPluginStatus::NOT_PRO,
63
+ 'is_mwpnoton' => $statusKey === ClientPluginStatus::MWP_NOT_ON,
64
+ 'is_version_mismatch' => in_array( $statusKey, [
65
+ ClientPluginStatus::VERSION_NEWER_THAN_SERVER,
66
+ ClientPluginStatus::VERSION_OLDER_THAN_SERVER,
67
+ ] ),
68
+ ],
69
+ 'vars' => [
70
+ 'status_key' => $statusKey,
71
+ 'status_name' => current( $status ),
72
+ 'issues_count' => $issuesCount,
73
+ 'version' => $this->getCon()->getVersion()
74
+ ],
75
+ 'hrefs' => [
76
+ 'this_extension' => Services::WpGeneral()
77
+ ->getUrl_AdminPage( $con->mwpVO->official_extension_data[ 'page' ] ),
78
+ ],
79
+ 'strings' => [
80
+ 'tooltip_inactive' => __( "Shield plugin is installed, but not active.", 'wp-simple-firewall' ),
81
+ 'tooltip_notpro' => __( "The Shield plugin on this site doesn't have an active ShieldPRO license.", 'wp-simple-firewall' ),
82
+ 'tooltip_mwpnoton' => __( "Shield's MainWP integration isn't enabled for this site.", 'wp-simple-firewall' ),
83
+ 'tooltip_not_installed' => __( "Shield isn't installed on this site.", 'wp-simple-firewall' ),
84
+ 'tooltip_sync_required' => __( "Sync Required.", 'wp-simple-firewall' ),
85
+ 'tooltip_version_mismatch' => __( "Shield version on site doesn't match this server.", 'wp-simple-firewall' ),
86
+ 'tooltip_please_update' => __( "Please update your Shield plugins to the same versions and re-sync.", 'wp-simple-firewall' ),
87
+ 'tooltip_issues_found' => __( "Issues Found", 'wp-simple-firewall' ),
88
+ ]
89
+ ];
90
+ }
91
+
92
+ protected function getTemplateSlug() :string {
93
+ return 'tables/manage_sites_col';
94
+ }
95
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/MwpOutOfDate.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI\PageRender;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Controller;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI\BaseRender;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+
9
+ class MwpOutOfDate extends BaseRender {
10
+
11
+ protected function getData() :array {
12
+ return [
13
+ 'strings' => [
14
+ 'update' => __( "The MainWP Security plugin doesn't meet Shield's minimum requirements." ),
15
+ 'min_version' => __( 'Minimum required MainWP server version' ),
16
+ 'go_here' => __( 'Go to WordPress Updates' ),
17
+ ],
18
+ 'hrefs' => [
19
+ 'update' => Services::WpGeneral()->getAdminUrl_Updates()
20
+ ],
21
+ 'vars' => [
22
+ 'min_version' => Controller::MIN_VERSION_MAINWP
23
+ ],
24
+ ];
25
+ }
26
+
27
+ protected function getTemplateSlug() :string {
28
+ return 'pages/mwp_outofdate';
29
+ }
30
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/NotShieldPro.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI\PageRender;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI\BaseRender;
6
+
7
+ class NotShieldPro extends BaseRender {
8
+
9
+ protected function getData() :array {
10
+ return [
11
+ 'strings' => [
12
+ 'not_pro' => __( "Sorry, the MainWP server integration is available only for ShieldPRO clients." ),
13
+ 'go_pro' => __( 'Upgrade To ShieldPRO' ),
14
+ ],
15
+ 'hrefs' => [
16
+ 'go_pro' => 'https://shsec.io/mainwpservergopro'
17
+ ],
18
+ ];
19
+ }
20
+
21
+ protected function getTemplateSlug() :string {
22
+ return 'pages/mwp_for_pro';
23
+ }
24
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/PluginOutOfDate.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI\PageRender;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI\BaseRender;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class PluginOutOfDate extends BaseRender {
9
+
10
+ protected function getData() :array {
11
+ return [
12
+ 'strings' => [
13
+ 'update' => __( 'The Shield Security plugin on this site needs to be upgraded.' ),
14
+ 'go_here' => __( 'Go to WordPress Updates' )
15
+ ],
16
+ 'hrefs' => [
17
+ 'update' => Services::WpGeneral()->getAdminUrl_Updates()
18
+ ],
19
+ ];
20
+ }
21
+
22
+ protected function getTemplateSlug() :string {
23
+ return 'pages/shield_outofdate';
24
+ }
25
+ }
src/lib/src/Modules/Integrations/Lib/MainWP/Server/UI/PageRender/SitesList.php ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI\PageRender;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Common\MWPSiteVO;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\Data\ClientPluginStatus;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\Data\LoadShieldSyncData;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\MainWP\Server\UI\BaseRender;
9
+ use FernleafSystems\Wordpress\Services\Services;
10
+
11
+ class SitesList extends BaseRender {
12
+
13
+ protected function getData() :array {
14
+ $mod = $this->getMod();
15
+ $mwp = $this->getCon()->mwpVO;
16
+ $WP = Services::WpGeneral();
17
+ $req = Services::Request();
18
+
19
+ $statsHead = [
20
+ 'connected' => 0,
21
+ 'disconnected' => 0,
22
+ 'with_issues' => 0,
23
+ 'needs_update' => 0,
24
+ ];
25
+ $sites = apply_filters( 'mainwp_getsites', $mwp->child_file, $mwp->child_key );
26
+ foreach ( $sites as &$site ) {
27
+ $mwpSite = MWPSiteVO::LoadByID( (int)$site[ 'id' ] );
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 )
35
+ ->detect();
36
+ $shd[ 'status_key' ] = key( $status );
37
+ $shd[ 'status' ] = current( $status );
38
+
39
+ $shd[ 'is_active' ] = $shd[ 'status_key' ] === ClientPluginStatus::ACTIVE;
40
+ $shd[ 'is_inactive' ] = $shd[ 'status_key' ] === ClientPluginStatus::INACTIVE;
41
+ $shd[ 'is_notinstalled' ] = $shd[ 'status_key' ] === ClientPluginStatus::NOT_INSTALLED;
42
+ $shd[ 'is_notpro' ] = $shd[ 'status_key' ] === ClientPluginStatus::NOT_PRO;
43
+ $shd[ 'is_mwpnoton' ] = $shd[ 'status_key' ] === ClientPluginStatus::MWP_NOT_ON;
44
+ $shd[ 'is_sync_rqd' ] = $shd[ 'status_key' ] === ClientPluginStatus::NEED_SYNC;
45
+ $shd[ 'is_version_mismatch' ] = in_array( $shd[ 'status_key' ], [
46
+ ClientPluginStatus::VERSION_NEWER_THAN_SERVER,
47
+ ClientPluginStatus::VERSION_OLDER_THAN_SERVER,
48
+ ] );
49
+
50
+ if ( $shd[ 'is_active' ] ) {
51
+
52
+ $statsHead[ 'connected' ]++;
53
+ $shd[ 'sync_at_text' ] = $WP->getTimeStringForDisplay( $meta->sync_at );
54
+ $shd[ 'sync_at_diff' ] = $req->carbon()->setTimestamp( $meta->sync_at )->diffForHumans();
55
+
56
+ if ( empty( $sync->modules[ 'hack_protect' ][ 'scan_issues' ] ) ) {
57
+ $shd[ 'issues' ] = __( 'No Issues', 'wp-simple-firewall' );
58
+ $shd[ 'has_issues' ] = false;
59
+ }
60
+ else {
61
+ $shd[ 'has_issues' ] = true;
62
+ $shd[ 'issues' ] = array_sum( $sync->modules[ 'hack_protect' ][ 'scan_issues' ] );
63
+ $statsHead[ 'with_issues' ]++;
64
+ }
65
+
66
+ $shd[ 'issues_href' ] = add_query_arg(
67
+ [
68
+ 'newWindow' => 'yes',
69
+ 'websiteid' => $site[ 'id' ],
70
+ 'location' => base64_encode( $this->getScanPageUrlPart() )
71
+ ],
72
+ Services::WpGeneral()->getUrl_AdminPage( 'SiteOpen' )
73
+ );
74
+ }
75
+ else {
76
+ $statsHead[ 'disconnected' ]++;
77
+ }
78
+
79
+ $statsHead[ 'needs_update' ] += $meta->has_update ? 1 : 0;
80
+
81
+ $site[ 'shield' ] = $shd;
82
+ }
83
+
84
+ return [
85
+ 'vars' => [
86
+ 'sites' => $sites,
87
+ 'stats_head' => $statsHead,
88
+ ],
89
+ 'ajax' => [
90
+ 'mwp_sh_site_action' => $mod->getAjaxActionData( 'mwp_sh_site_action', true ),
91
+ 'mwp_sh_ext_table' => $mod->getAjaxActionData( 'mwp_sh_ext_table', true ),
92
+ ],
93
+ 'strings' => [
94
+ 'actions' => __( 'Actions', 'wp-simple-firewall' ),
95
+ 'site' => __( 'Site', 'wp-simple-firewall' ),
96
+ 'url' => __( 'URL', 'wp-simple-firewall' ),
97
+ 'issues' => __( 'Issues', 'wp-simple-firewall' ),
98
+ 'status' => __( 'Status', 'wp-simple-firewall' ),
99
+ 'last_sync' => __( 'Last Sync', 'wp-simple-firewall' ),
100
+ 'last_scan' => __( 'Last Scan', 'wp-simple-firewall' ),
101
+ 'version' => __( 'Version', 'wp-simple-firewall' ),
102
+ 'connected' => __( 'Connected', 'wp-simple-firewall' ),
103
+ 'disconnected' => __( 'Disconnected', 'wp-simple-firewall' ),
104
+ 'with_issues' => __( 'With Issues', 'wp-simple-firewall' ),
105
+ 'needs_update' => __( 'Needs Update', 'wp-simple-firewall' ),
106
+ 'st_inactive' => __( 'Shield Security plugin is installed but not activated.', 'wp-simple-firewall' ),
107
+ 'st_notinstalled' => __( "Shield Security plugin not detected in last sync.", 'wp-simple-firewall' ),
108
+ 'st_notpro' => __( "ShieldPRO isn't activated on this site.", 'wp-simple-firewall' ),
109
+ 'st_mwpnoton' => __( "Shield's MainWP integration isn't enabled for this site.", 'wp-simple-firewall' ),
110
+ 'st_sync_rqd' => __( 'Shield Security plugin needs to sync.', 'wp-simple-firewall' ),
111
+ 'st_version_mismatch' => __( 'Shield Security plugin versions are out of sync.', 'wp-simple-firewall' ),
112
+ 'st_unknown' => __( "Couldn't determine Shield plugin status.", 'wp-simple-firewall' ),
113
+ 'act_sync' => __( 'Sync Shield', 'wp-simple-firewall' ),
114
+ 'act_activate' => __( 'Activate Shield', 'wp-simple-firewall' ),
115
+ 'act_align' => __( 'Align Shield', 'wp-simple-firewall' ),
116
+ 'act_deactivate' => __( 'Deactivate Shield', 'wp-simple-firewall' ),
117
+ 'act_install' => __( 'Install Shield', 'wp-simple-firewall' ),
118
+ 'act_upgrade' => __( 'Upgrade Shield', 'wp-simple-firewall' ),
119
+ 'act_uninstall' => __( 'Uninstall Shield', 'wp-simple-firewall' ),
120
+ 'act_license' => __( 'Check ShieldPRO License', 'wp-simple-firewall' ),
121
+ 'act_mwp' => __( 'Switch-On MainWP Integration', 'wp-simple-firewall' ),
122
+ ]
123
+ ];
124
+ }
125
+
126
+ private function getScanPageUrlPart() :string {
127
+ $WP = Services::WpGeneral();
128
+ return str_replace(
129
+ $WP->getAdminUrl(),
130
+ '',
131
+ $this->getCon()->getModule_Insights()->getUrl_SubInsightsPage( 'scans' )
132
+ );
133
+ }
134
+
135
+ protected function getTemplateSlug() :string {
136
+ return 'pages/sites';
137
+ }
138
+ }
src/lib/src/Modules/Integrations/ModCon.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
+
8
+ class ModCon extends BaseShield\ModCon {
9
+
10
+ /**
11
+ * @var Lib\MainWP\Controller
12
+ */
13
+ private $mwp;
14
+
15
+ public function getControllerMWP() :Lib\MainWP\Controller {
16
+ if ( empty( $this->mwp ) ) {
17
+ $this->mwp = ( new Lib\MainWP\Controller() )
18
+ ->setMod( $this );
19
+ }
20
+ return $this->mwp;
21
+ }
22
+ }
src/lib/src/Modules/Integrations/Options.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+
7
+ class Options extends BaseShield\Options {
8
+
9
+ public function isEnabledMainWP() :bool {
10
+ return $this->isOpt( 'enable_mainwp', 'Y' );
11
+ }
12
+ }
src/lib/src/Modules/Integrations/Processor.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+
7
+ class Processor extends BaseShield\Processor {
8
+
9
+ protected function run() {
10
+ $this->getCon()
11
+ ->getModule_Integrations()
12
+ ->getControllerMWP()
13
+ ->execute();
14
+ }
15
+ }
src/lib/src/Modules/Integrations/Strings.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
+
7
+ class Strings extends Base\Strings {
8
+
9
+ /**
10
+ * @param string $section
11
+ * @return array
12
+ * @throws \Exception
13
+ */
14
+ public function getSectionStrings( string $section ) :array {
15
+
16
+ switch ( $section ) {
17
+
18
+ case 'section_integrations':
19
+ $titleShort = __( 'Integrations', 'wp-simple-firewall' );
20
+ $title = __( 'Built-In Shield Integrations', 'wp-simple-firewall' );
21
+ $summary = [
22
+ sprintf( '%s - %s', __( 'Summary', 'wp-simple-firewall' ),
23
+ __( "Shield can automatically integrate with 3rd party plugins.", 'wp-simple-firewall' ) ),
24
+ sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ),
25
+ __( "Only enable the integrations you require.", 'wp-simple-firewall' ) ),
26
+ ];
27
+ break;
28
+
29
+ default:
30
+ return parent::getSectionStrings( $section );
31
+ }
32
+
33
+ return [
34
+ 'title' => $title,
35
+ 'title_short' => $titleShort,
36
+ 'summary' => is_array( $summary ) ? $summary : [],
37
+ ];
38
+ }
39
+
40
+ /**
41
+ * @param string $key
42
+ * @return array
43
+ * @throws \Exception
44
+ */
45
+ public function getOptionStrings( string $key ) :array {
46
+
47
+ switch ( $key ) {
48
+
49
+ case 'enable_mainwp' :
50
+ $name = __( 'MainWP Integration', 'wp-simple-firewall' );
51
+ $summary = __( "Turn-On Shield's Built-In Extension For MainWP Server And Client Installations", 'wp-simple-firewall' );
52
+ $desc = [
53
+ __( 'Easily integrate Shield Security to help you manage your site security from within MainWP.', 'wp-simple-firewall' ),
54
+ __( "You don't need to install a separate extension for MainWP.", 'wp-simple-firewall' ),
55
+ sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ),
56
+ __( "If this is a MainWP client site, you should add your MainWP Admin Server's IP address to your IP bypass list.", 'wp-simple-firewall' ) )
57
+ ];
58
+ break;
59
+
60
+ default:
61
+ return parent::getOptionStrings( $key );
62
+ }
63
+
64
+ return [
65
+ 'name' => $name,
66
+ 'summary' => $summary,
67
+ 'description' => $desc,
68
+ ];
69
+ }
70
+ }
src/lib/src/Modules/License/AdminNotices.php CHANGED
@@ -3,29 +3,26 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
 
6
 
7
  class AdminNotices extends Shield\Modules\Base\AdminNotices {
8
 
9
  /**
10
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
11
- * @throws \Exception
12
  */
13
- protected function processNotice( $oNotice ) {
14
- switch ( $oNotice->id ) {
15
  case 'wphashes-token-fail':
16
- $this->buildNotice_WpHashesTokenFailure( $oNotice );
17
  break;
18
  default:
19
- parent::processNotice( $oNotice );
20
  break;
21
  }
22
  }
23
 
24
- /**
25
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
26
- */
27
- private function buildNotice_WpHashesTokenFailure( $oNotice ) {
28
- $oNotice->render_data = [
29
  'notice_attributes' => [],
30
  'strings' => [
31
  'title' => sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ),
@@ -46,25 +43,21 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
46
  ];
47
  }
48
 
49
- /**
50
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
51
- * @return bool
52
- */
53
- protected function isDisplayNeeded( $oNotice ) {
54
- /** @var \ICWP_WPSF_FeatureHandler_License $oMod */
55
- $oMod = $this->getMod();
56
 
57
- switch ( $oNotice->id ) {
58
 
59
  case 'wphashes-token-fail':
60
- $bNeeded = $this->getCon()->isPremiumActive()
61
- && !$oMod->getWpHashesTokenManager()->hasToken();
62
  break;
63
 
64
  default:
65
- $bNeeded = parent::isDisplayNeeded( $oNotice );
66
  break;
67
  }
68
- return $bNeeded;
69
  }
70
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\AdminNotices\NoticeVO;
7
 
8
  class AdminNotices extends Shield\Modules\Base\AdminNotices {
9
 
10
  /**
11
+ * @inheritDoc
 
12
  */
13
+ protected function processNotice( NoticeVO $notice ) {
14
+ switch ( $notice->id ) {
15
  case 'wphashes-token-fail':
16
+ $this->buildNotice_WpHashesTokenFailure( $notice );
17
  break;
18
  default:
19
+ parent::processNotice( $notice );
20
  break;
21
  }
22
  }
23
 
24
+ private function buildNotice_WpHashesTokenFailure( NoticeVO $notice ) {
25
+ $notice->render_data = [
 
 
 
26
  'notice_attributes' => [],
27
  'strings' => [
28
  'title' => sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ),
43
  ];
44
  }
45
 
46
+ protected function isDisplayNeeded( NoticeVO $notice ) :bool {
47
+ /** @var ModCon $mod */
48
+ $mod = $this->getMod();
 
 
 
 
49
 
50
+ switch ( $notice->id ) {
51
 
52
  case 'wphashes-token-fail':
53
+ $needed = $this->getCon()->isPremiumActive()
54
+ && !$mod->getWpHashesTokenManager()->hasToken();
55
  break;
56
 
57
  default:
58
+ $needed = parent::isDisplayNeeded( $notice );
59
  break;
60
  }
61
+ return $needed;
62
  }
63
  }
src/lib/src/Modules/License/AjaxHandler.php CHANGED
@@ -6,7 +6,7 @@ use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Services\Services;
7
  use FernleafSystems\Wordpress\Services\Utilities\Licenses\Keyless;
8
 
9
- class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
10
 
11
  protected function processAjaxAction( string $action ) :array {
12
 
@@ -56,9 +56,9 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
56
  * @return array
57
  */
58
  private function ajaxExec_LicenseHandling() {
59
- /** @var \ICWP_WPSF_FeatureHandler_License $oMod */
60
- $oMod = $this->getMod();
61
- $sHandler = $oMod->getLicenseHandler();
62
 
63
  $bSuccess = false;
64
  $sMessage = 'Unsupported license action';
6
  use FernleafSystems\Wordpress\Services\Services;
7
  use FernleafSystems\Wordpress\Services\Utilities\Licenses\Keyless;
8
 
9
+ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
10
 
11
  protected function processAjaxAction( string $action ) :array {
12
 
56
  * @return array
57
  */
58
  private function ajaxExec_LicenseHandling() {
59
+ /** @var ModCon $mod */
60
+ $mod = $this->getMod();
61
+ $sHandler = $mod->getLicenseHandler();
62
 
63
  $bSuccess = false;
64
  $sMessage = 'Unsupported license action';
src/lib/src/Modules/License/Lib/LicenseEmails.php CHANGED
@@ -2,6 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License\Lib;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
@@ -10,57 +11,57 @@ class LicenseEmails {
10
  use ModConsumer;
11
 
12
  public function sendLicenseWarningEmail() {
13
- /** @var \ICWP_WPSF_FeatureHandler_License $oMod */
14
- $oMod = $this->getMod();
15
- $oOpts = $this->getOptions();
16
 
17
  $bCanSend = Services::Request()
18
  ->carbon()
19
- ->subDay( 1 )->timestamp > $oOpts->getOpt( 'last_warning_email_sent_at' );
20
 
21
  if ( $bCanSend ) {
22
- $oOpts->setOptAt( 'last_warning_email_sent_at' );
23
- $oMod->saveModOptions();
24
 
25
  $aMessage = [
26
  __( 'Attempts to verify Shield Pro license has just failed.', 'wp-simple-firewall' ),
27
- sprintf( __( 'Please check your license on-site: %s', 'wp-simple-firewall' ), $oMod->getUrl_AdminPage() ),
28
  sprintf( __( 'If this problem persists, please contact support: %s', 'wp-simple-firewall' ), 'https://support.onedollarplugin.com/' )
29
  ];
30
- $oMod->getEmailProcessor()
31
- ->sendEmailWithWrap(
32
- $oMod->getPluginReportEmail(),
33
- 'Pro License Check Has Failed',
34
- $aMessage
35
- );
36
  $this->getCon()->fireEvent( 'lic_fail_email' );
37
  }
38
  }
39
 
40
  public function sendLicenseDeactivatedEmail() {
41
- /** @var \ICWP_WPSF_FeatureHandler_License $oMod */
42
- $oMod = $this->getMod();
43
- $oOpts = $this->getOptions();
44
 
45
  $bCanSend = Services::Request()
46
  ->carbon()
47
- ->subDay( 1 )->timestamp > $oOpts->getOpt( 'last_deactivated_email_sent_at' );
48
 
49
  if ( $bCanSend ) {
50
- $oOpts->setOptAt( 'last_deactivated_email_sent_at' );
51
- $oMod->saveModOptions();
52
 
53
  $aMessage = [
54
  __( 'All attempts to verify Shield Pro license have failed.', 'wp-simple-firewall' ),
55
- sprintf( __( 'Please check your license on-site: %s', 'wp-simple-firewall' ), $oMod->getUrl_AdminPage() ),
56
  sprintf( __( 'If this problem persists, please contact support: %s', 'wp-simple-firewall' ), 'https://support.onedollarplugin.com/' )
57
  ];
58
- $oMod->getEmailProcessor()
59
- ->sendEmailWithWrap(
60
- $oMod->getPluginReportEmail(),
61
- '[Action May Be Required] Pro License Has Been Deactivated',
62
- $aMessage
63
- );
64
  }
65
  }
66
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License\Lib;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\License\ModCon;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
11
  use ModConsumer;
12
 
13
  public function sendLicenseWarningEmail() {
14
+ /** @var ModCon $mod */
15
+ $mod = $this->getMod();
16
+ $opts = $this->getOptions();
17
 
18
  $bCanSend = Services::Request()
19
  ->carbon()
20
+ ->subDay( 1 )->timestamp > $opts->getOpt( 'last_warning_email_sent_at' );
21
 
22
  if ( $bCanSend ) {
23
+ $opts->setOptAt( 'last_warning_email_sent_at' );
24
+ $mod->saveModOptions();
25
 
26
  $aMessage = [
27
  __( 'Attempts to verify Shield Pro license has just failed.', 'wp-simple-firewall' ),
28
+ sprintf( __( 'Please check your license on-site: %s', 'wp-simple-firewall' ), $mod->getUrl_AdminPage() ),
29
  sprintf( __( 'If this problem persists, please contact support: %s', 'wp-simple-firewall' ), 'https://support.onedollarplugin.com/' )
30
  ];
31
+ $mod->getEmailProcessor()
32
+ ->sendEmailWithWrap(
33
+ $mod->getPluginReportEmail(),
34
+ 'Pro License Check Has Failed',
35
+ $aMessage
36
+ );
37
  $this->getCon()->fireEvent( 'lic_fail_email' );
38
  }
39
  }
40
 
41
  public function sendLicenseDeactivatedEmail() {
42
+ /** @var ModCon $mod */
43
+ $mod = $this->getMod();
44
+ $opts = $this->getOptions();
45
 
46
  $bCanSend = Services::Request()
47
  ->carbon()
48
+ ->subDay( 1 )->timestamp > $opts->getOpt( 'last_deactivated_email_sent_at' );
49
 
50
  if ( $bCanSend ) {
51
+ $opts->setOptAt( 'last_deactivated_email_sent_at' );
52
+ $mod->saveModOptions();
53
 
54
  $aMessage = [
55
  __( 'All attempts to verify Shield Pro license have failed.', 'wp-simple-firewall' ),
56
+ sprintf( __( 'Please check your license on-site: %s', 'wp-simple-firewall' ), $mod->getUrl_AdminPage() ),
57
  sprintf( __( 'If this problem persists, please contact support: %s', 'wp-simple-firewall' ), 'https://support.onedollarplugin.com/' )
58
  ];
59
+ $mod->getEmailProcessor()
60
+ ->sendEmailWithWrap(
61
+ $mod->getPluginReportEmail(),
62
+ '[Action May Be Required] Pro License Has Been Deactivated',
63
+ $aMessage
64
+ );
65
  }
66
  }
67
  }
src/lib/src/Modules/License/Lib/LicenseHandler.php CHANGED
@@ -5,6 +5,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License\Lib;
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\ShieldNetApi\HandshakingNonce;
9
  use FernleafSystems\Wordpress\Services\Services;
10
 
@@ -43,10 +44,10 @@ class LicenseHandler {
43
 
44
  // performs the license check on-demand
45
  add_action( $oCon->prefix( 'adhoc_cron_license_check' ), function () {
46
- /** @var \ICWP_WPSF_FeatureHandler_License $oMod */
47
- $oMod = $this->getMod();
48
  try {
49
- $oMod->getLicenseHandler()->verify( true );
50
  }
51
  catch ( \Exception $oE ) {
52
  }
@@ -126,16 +127,16 @@ class LicenseHandler {
126
  * @return int
127
  */
128
  public function getRegistrationExpiresAt() {
129
- /** @var \ICWP_WPSF_FeatureHandler_License $oMod */
130
- $oMod = $this->getMod();
131
- $oOpts = $this->getOptions();
132
 
133
- $nVerifiedExpiredDays = $oOpts->getDef( 'lic_verify_expire_days' )
134
- + $oOpts->getDef( 'lic_verify_expire_grace_days' );
135
 
136
- $oLic = $oMod->getLicenseHandler()->getLicense();
137
  return (int)min(
138
- $oLic->getExpiresAt() + $oOpts->getDef( 'lic_verify_expire_grace_days' )*DAY_IN_SECONDS,
139
  $oLic->last_verified_at + $nVerifiedExpiredDays*DAY_IN_SECONDS
140
  );
141
  }
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;
9
  use FernleafSystems\Wordpress\Plugin\Shield\ShieldNetApi\HandshakingNonce;
10
  use FernleafSystems\Wordpress\Services\Services;
11
 
44
 
45
  // performs the license check on-demand
46
  add_action( $oCon->prefix( 'adhoc_cron_license_check' ), function () {
47
+ /** @var ModCon $mod */
48
+ $mod = $this->getMod();
49
  try {
50
+ $mod->getLicenseHandler()->verify( true );
51
  }
52
  catch ( \Exception $oE ) {
53
  }
127
  * @return int
128
  */
129
  public function getRegistrationExpiresAt() {
130
+ /** @var ModCon $mod */
131
+ $mod = $this->getMod();
132
+ $opts = $this->getOptions();
133
 
134
+ $nVerifiedExpiredDays = $opts->getDef( 'lic_verify_expire_days' )
135
+ + $opts->getDef( 'lic_verify_expire_grace_days' );
136
 
137
+ $oLic = $mod->getLicenseHandler()->getLicense();
138
  return (int)min(
139
+ $oLic->getExpiresAt() + $opts->getDef( 'lic_verify_expire_grace_days' )*DAY_IN_SECONDS,
140
  $oLic->last_verified_at + $nVerifiedExpiredDays*DAY_IN_SECONDS
141
  );
142
  }
src/lib/src/Modules/License/Lib/Verify.php CHANGED
@@ -14,19 +14,19 @@ class Verify {
14
  * @throws \Exception
15
  */
16
  public function run() {
17
- $oCon = $this->getCon();
18
- /** @var \ICWP_WPSF_FeatureHandler_License $oMod */
19
- $oMod = $this->getMod();
20
- /** @var License\Options $oOpts */
21
- $oOpts = $this->getOptions();
22
- $oHandler = $oMod->getLicenseHandler();
23
 
24
  $this->preVerify();
25
 
26
  $oExisting = $oHandler->getLicense();
27
 
28
  $oLookupLicense = ( new LookupRequest() )
29
- ->setMod( $oMod )
30
  ->lookup();
31
 
32
  $bSuccessfulApiRequest = false;
@@ -36,11 +36,11 @@ class Verify {
36
  $oExisting = $oLookupLicense;
37
  $oExisting->updateLastVerifiedAt( true );
38
  if ( !$oHandler->isActive() ) {
39
- $oOpts->setOptAt( 'license_activated_at' );
40
  }
41
- $oMod->clearLastErrors();
42
- $oOpts->setOpt( 'license_data', $oExisting->getRawDataAsArray() ); // need to do this before event
43
- $oCon->fireEvent( 'lic_check_success' );
44
  }
45
  elseif ( $oLookupLicense->isReady() ) {
46
  $bSuccessfulApiRequest = true;
@@ -50,7 +50,7 @@ class Verify {
50
  }
51
  elseif ( $oExisting->isReady() ) { // Has a stored license but license HTTP request failed
52
 
53
- $oMod->setLastErrors( [
54
  __( 'The most recent request to verify the site license encountered a problem.', 'wp-simple-firewall' )
55
  ] );
56
 
@@ -66,7 +66,7 @@ class Verify {
66
  * We don't remove the license yet, but we warn the user
67
  */
68
  ( new LicenseEmails() )
69
- ->setMod( $oMod )
70
  ->sendLicenseWarningEmail();
71
  }
72
  }
@@ -76,7 +76,7 @@ class Verify {
76
  }
77
 
78
  $oExisting->last_request_at = Services::Request()->ts();
79
- $oOpts->setOpt( 'license_data', $oExisting->getRawDataAsArray() );
80
  $this->getMod()->saveModOptions();
81
 
82
  if ( !$bSuccessfulApiRequest ) {
14
  * @throws \Exception
15
  */
16
  public function run() {
17
+ $con = $this->getCon();
18
+ /** @var License\ModCon $mod */
19
+ $mod = $this->getMod();
20
+ /** @var License\Options $opts */
21
+ $opts = $this->getOptions();
22
+ $oHandler = $mod->getLicenseHandler();
23
 
24
  $this->preVerify();
25
 
26
  $oExisting = $oHandler->getLicense();
27
 
28
  $oLookupLicense = ( new LookupRequest() )
29
+ ->setMod( $mod )
30
  ->lookup();
31
 
32
  $bSuccessfulApiRequest = false;
36
  $oExisting = $oLookupLicense;
37
  $oExisting->updateLastVerifiedAt( true );
38
  if ( !$oHandler->isActive() ) {
39
+ $opts->setOptAt( 'license_activated_at' );
40
  }
41
+ $mod->clearLastErrors();
42
+ $opts->setOpt( 'license_data', $oExisting->getRawDataAsArray() ); // need to do this before event
43
+ $con->fireEvent( 'lic_check_success' );
44
  }
45
  elseif ( $oLookupLicense->isReady() ) {
46
  $bSuccessfulApiRequest = true;
50
  }
51
  elseif ( $oExisting->isReady() ) { // Has a stored license but license HTTP request failed
52
 
53
+ $mod->setLastErrors( [
54
  __( 'The most recent request to verify the site license encountered a problem.', 'wp-simple-firewall' )
55
  ] );
56
 
66
  * We don't remove the license yet, but we warn the user
67
  */
68
  ( new LicenseEmails() )
69
+ ->setMod( $mod )
70
  ->sendLicenseWarningEmail();
71
  }
72
  }
76
  }
77
 
78
  $oExisting->last_request_at = Services::Request()->ts();
79
+ $opts->setOpt( 'license_data', $oExisting->getRawDataAsArray() );
80
  $this->getMod()->saveModOptions();
81
 
82
  if ( !$bSuccessfulApiRequest ) {
src/lib/src/Modules/License/ModCon.php ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+
9
+ class ModCon extends BaseShield\ModCon {
10
+
11
+ /**
12
+ * @var Lib\LicenseHandler
13
+ */
14
+ private $licenseHandler;
15
+
16
+ /**
17
+ * @var Lib\WpHashes\ApiTokenManager
18
+ */
19
+ private $wpHashesTokenManager;
20
+
21
+ /**
22
+ * @return Lib\LicenseHandler
23
+ */
24
+ public function getLicenseHandler() :Lib\LicenseHandler {
25
+ if ( !isset( $this->licenseHandler ) ) {
26
+ $this->licenseHandler = ( new Lib\LicenseHandler() )->setMod( $this );
27
+ }
28
+ return $this->licenseHandler;
29
+ }
30
+
31
+ public function getWpHashesTokenManager() :Lib\WpHashes\ApiTokenManager {
32
+ if ( !isset( $this->wpHashesTokenManager ) ) {
33
+ $this->wpHashesTokenManager = ( new Lib\WpHashes\ApiTokenManager() )->setMod( $this );
34
+ }
35
+ return $this->wpHashesTokenManager;
36
+ }
37
+
38
+ protected function redirectToInsightsSubPage() {
39
+ Services::Response()->redirect(
40
+ $this->getCon()->getModule_Insights()->getUrl_AdminPage(),
41
+ [ 'inav' => 'license' ]
42
+ );
43
+ }
44
+
45
+ public function runHourlyCron() {
46
+ $this->getWpHashesTokenManager()->getToken();
47
+ }
48
+
49
+ public function onWpInit() {
50
+ parent::onWpInit();
51
+ $this->getWpHashesTokenManager()->execute();
52
+ }
53
+
54
+ public function getIfShowModuleMenuItem() :bool {
55
+ return parent::getIfShowModuleMenuItem() && !$this->isPremium();
56
+ }
57
+
58
+ public function onPluginShutdown() {
59
+ try {
60
+ $this->getLicenseHandler()->verify( false );
61
+ }
62
+ catch ( \Exception $e ) {
63
+ }
64
+ parent::onPluginShutdown();
65
+ }
66
+ }
src/lib/src/Modules/License/Options.php CHANGED
@@ -2,8 +2,8 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
 
7
- class Options extends Base\ShieldOptions {
8
 
9
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
 
7
+ class Options extends BaseShield\Options {
8
 
9
  }
src/lib/src/Modules/License/Processor.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+
7
+ class Processor extends BaseShield\Processor {
8
+
9
+ protected function run() {
10
+ /** @var ModCon $mod */
11
+ $mod = $this->getMod();
12
+ $mod->getLicenseHandler()->execute();
13
+ }
14
+ }
src/lib/src/Modules/License/UI.php CHANGED
@@ -2,18 +2,18 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
- class UI extends Base\ShieldUI {
9
 
10
  /**
11
  * @return array
12
  */
13
  public function buildInsightsVars() {
14
- /** @var \ICWP_WPSF_FeatureHandler_License $mod */
15
- $mod = $this->getMod();
16
  $con = $this->getCon();
 
 
17
  $opts = $this->getOptions();
18
  $WP = Services::WpGeneral();
19
  $oCarbon = Services::Request()->carbon();
@@ -55,7 +55,8 @@ class UI extends Base\ShieldUI {
55
  'vars' => [
56
  'license_table' => $aLicenseTableVars,
57
  'activation_url' => $WP->getHomeUrl(),
58
- 'error' => $mod->getLastErrors( true )
 
59
  ],
60
  'inputs' => [
61
  'license_key' => [
@@ -68,10 +69,9 @@ class UI extends Base\ShieldUI {
68
  'connection_debug' => $mod->getAjaxActionData( 'connection_debug' )
69
  ],
70
  'aHrefs' => [
71
- 'shield_pro_url' => 'https://shsec.io/shieldpro',
72
- 'shield_pro_more_info_url' => 'https://shsec.io/shld1',
73
- 'iframe_url' => $opts->getDef( 'landing_page_url' ),
74
- 'keyless_cp' => $opts->getDef( 'keyless_cp' ),
75
  ],
76
  'flags' => [
77
  'show_ads' => false,
@@ -85,12 +85,31 @@ class UI extends Base\ShieldUI {
85
  ];
86
  }
87
 
88
- /**
89
- * @return bool
90
- */
91
  public function isEnabledForUiSummary() :bool {
92
- /** @var \ICWP_WPSF_FeatureHandler_License $mod */
93
  $mod = $this->getMod();
94
  return $mod->getLicenseHandler()->hasValidWorkingLicense();
95
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
+ class UI extends BaseShield\UI {
9
 
10
  /**
11
  * @return array
12
  */
13
  public function buildInsightsVars() {
 
 
14
  $con = $this->getCon();
15
+ /** @var ModCon $mod */
16
+ $mod = $this->getMod();
17
  $opts = $this->getOptions();
18
  $WP = Services::WpGeneral();
19
  $oCarbon = Services::Request()->carbon();
55
  'vars' => [
56
  'license_table' => $aLicenseTableVars,
57
  'activation_url' => $WP->getHomeUrl(),
58
+ 'error' => $mod->getLastErrors( true ),
59
+ 'related_hrefs' => $this->getSettingsRelatedLinks()
60
  ],
61
  'inputs' => [
62
  'license_key' => [
69
  'connection_debug' => $mod->getAjaxActionData( 'connection_debug' )
70
  ],
71
  'aHrefs' => [
72
+ 'shield_pro_url' => 'https://shsec.io/shieldpro',
73
+ 'iframe_url' => $opts->getDef( 'landing_page_url' ),
74
+ 'keyless_cp' => $opts->getDef( 'keyless_cp' ),
 
75
  ],
76
  'flags' => [
77
  'show_ads' => false,
85
  ];
86
  }
87
 
 
 
 
88
  public function isEnabledForUiSummary() :bool {
89
+ /** @var ModCon $mod */
90
  $mod = $this->getMod();
91
  return $mod->getLicenseHandler()->hasValidWorkingLicense();
92
  }
93
+
94
+ protected function getSettingsRelatedLinks() :array {
95
+ $modInsights = $this->getCon()->getModule_Insights();
96
+ $links = [];
97
+ if ( !$this->getCon()->isPremiumActive() ) {
98
+ $links[] = [
99
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'free_trial' ),
100
+ 'title' => __( 'Free Trial', 'wp-simple-firewall' ),
101
+ ];
102
+ }
103
+ $links[] = [
104
+ 'href' => 'https://shsec.io/c5',
105
+ 'title' => __( 'License Activation', 'wp-simple-firewall' ),
106
+ 'new' => true,
107
+ ];
108
+ $links[] = [
109
+ 'href' => 'https://shsec.io/gp',
110
+ 'title' => __( 'ShieldPRO Features', 'wp-simple-firewall' ),
111
+ 'new' => true,
112
+ ];
113
+ return $links;
114
+ }
115
  }
src/lib/src/Modules/License/WpCli.php CHANGED
@@ -10,7 +10,7 @@ class WpCli extends Base\WpCli {
10
  /**
11
  * @inheritDoc
12
  */
13
- protected function getCmdHandlers() {
14
  return [
15
  new License\WpCli\License()
16
  ];
10
  /**
11
  * @inheritDoc
12
  */
13
+ protected function getCmdHandlers() :array {
14
  return [
15
  new License\WpCli\License()
16
  ];
src/lib/src/Modules/License/WpCli/License.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License\WpCli;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
 
6
  use WP_CLI;
7
 
8
  class License extends Base\WpCli\BaseWpCliCmd {
@@ -68,9 +69,9 @@ class License extends Base\WpCli\BaseWpCliCmd {
68
  if ( !$bConfirm ) {
69
  WP_CLI::confirm( __( 'Are you sure you want to remove the ShieldPRO license?', 'wp-simple-firewall' ) );
70
  }
71
- /** @var \ICWP_WPSF_FeatureHandler_License $oMod */
72
- $oMod = $this->getMod();
73
- $oMod->getLicenseHandler()->clearLicense();
74
  WP_CLI::success( __( 'License removed successfully.', 'wp-simple-firewall' ) );
75
  }
76
  }
@@ -79,9 +80,9 @@ class License extends Base\WpCli\BaseWpCliCmd {
79
  * @throws WP_CLI\ExitException
80
  */
81
  private function runStatus() {
82
- /** @var \ICWP_WPSF_FeatureHandler_License $oMod */
83
- $oMod = $this->getMod();
84
- $oMod->getLicenseHandler()->isActive() ?
85
  WP_CLI::success( __( 'Active license found.', 'wp-simple-firewall' ) )
86
  : WP_CLI::error( __( 'No active license present.', 'wp-simple-firewall' ) );
87
  }
@@ -90,14 +91,14 @@ class License extends Base\WpCli\BaseWpCliCmd {
90
  * @throws WP_CLI\ExitException
91
  */
92
  private function runVerify() {
93
- /** @var \ICWP_WPSF_FeatureHandler_License $oMod */
94
- $oMod = $this->getMod();
95
 
96
  try {
97
  if ( $this->getCon()->isPremiumActive() ) {
98
  WP_CLI::log( 'Premium license is already active. Re-checking...' );
99
  }
100
- $bSuccess = $oMod
101
  ->getLicenseHandler()
102
  ->verify()
103
  ->hasValidWorkingLicense();
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License\WpCli;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\License\ModCon;
7
  use WP_CLI;
8
 
9
  class License extends Base\WpCli\BaseWpCliCmd {
69
  if ( !$bConfirm ) {
70
  WP_CLI::confirm( __( 'Are you sure you want to remove the ShieldPRO license?', 'wp-simple-firewall' ) );
71
  }
72
+ /** @var ModCon $mod */
73
+ $mod = $this->getMod();
74
+ $mod->getLicenseHandler()->clearLicense();
75
  WP_CLI::success( __( 'License removed successfully.', 'wp-simple-firewall' ) );
76
  }
77
  }
80
  * @throws WP_CLI\ExitException
81
  */
82
  private function runStatus() {
83
+ /** @var ModCon $mod */
84
+ $mod = $this->getMod();
85
+ $mod->getLicenseHandler()->isActive() ?
86
  WP_CLI::success( __( 'Active license found.', 'wp-simple-firewall' ) )
87
  : WP_CLI::error( __( 'No active license present.', 'wp-simple-firewall' ) );
88
  }
91
  * @throws WP_CLI\ExitException
92
  */
93
  private function runVerify() {
94
+ /** @var ModCon $mod */
95
+ $mod = $this->getMod();
96
 
97
  try {
98
  if ( $this->getCon()->isPremiumActive() ) {
99
  WP_CLI::log( 'Premium license is already active. Re-checking...' );
100
  }
101
+ $bSuccess = $mod
102
  ->getLicenseHandler()
103
  ->verify()
104
  ->hasValidWorkingLicense();
src/lib/src/Modules/Lockdown/Insights/OverviewCards.php CHANGED
@@ -2,14 +2,14 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown\Insights;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
 
7
- class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
8
 
9
  public function build() :array {
10
- /** @var \ICWP_WPSF_FeatureHandler_Lockdown $mod */
11
  $mod = $this->getMod();
12
- /** @var \FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown\Options $opts */
13
  $opts = $this->getOptions();
14
 
15
  $cardSection = [
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown\Insights;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules;
6
 
7
+ class OverviewCards extends Modules\Base\Insights\OverviewCards {
8
 
9
  public function build() :array {
10
+ /** @var Modules\Lockdown\ModCon $mod */
11
  $mod = $this->getMod();
12
+ /** @var Modules\Lockdown\Options $opts */
13
  $opts = $this->getOptions();
14
 
15
  $cardSection = [
src/lib/src/Modules/Lockdown/ModCon.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+
7
+ class ModCon extends BaseShield\ModCon {
8
+
9
+ /**
10
+ * @param string $namespace
11
+ * @return bool
12
+ */
13
+ public function isPermittedAnonRestApiNamespace( $namespace ) {
14
+ /** @var Options $opts */
15
+ $opts = $this->getOptions();
16
+ return in_array( $namespace, $opts->getRestApiAnonymousExclusions() );
17
+ }
18
+
19
+ protected function preProcessOptions() {
20
+ $this->cleanApiExclusions();
21
+ }
22
+
23
+ private function cleanApiExclusions() {
24
+ /** @var Options $opts */
25
+ $opts = $this->getOptions();
26
+ $opts->setOpt(
27
+ 'api_namespace_exclusions',
28
+ $this->cleanStringArray( $opts->getRestApiAnonymousExclusions(), '#[^a-z0-9_-]#i' )
29
+ );
30
+ }
31
+ }
src/lib/src/Modules/Lockdown/Options.php CHANGED
@@ -2,9 +2,9 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
 
7
- class Options extends Base\ShieldOptions {
8
 
9
  /**
10
  * @return string[]
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
 
7
+ class Options extends BaseShield\Options {
8
 
9
  /**
10
  * @return string[]
src/lib/src/Modules/Lockdown/Processor.php ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class Processor extends BaseShield\Processor {
9
+
10
+ protected function run() {
11
+ /** @var Options $opts */
12
+ $opts = $this->getOptions();
13
+
14
+ if ( $opts->isOptFileEditingDisabled() ) {
15
+ $this->blockFileEditing();
16
+ }
17
+
18
+ if ( $opts->isOpt( 'force_ssl_admin', 'Y' ) && function_exists( 'force_ssl_admin' ) ) {
19
+ if ( !defined( 'FORCE_SSL_ADMIN' ) ) {
20
+ define( 'FORCE_SSL_ADMIN', true );
21
+ }
22
+ force_ssl_admin( true );
23
+ }
24
+
25
+ if ( $opts->isOpt( 'hide_wordpress_generator_tag', 'Y' ) ) {
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 );
44
+ }
45
+
46
+ add_filter( 'user_has_cap',
47
+ /**
48
+ * @param array $aAllCaps
49
+ * @param array $cap
50
+ * @param array $aArgs
51
+ * @return array
52
+ */
53
+ function ( $aAllCaps, $cap, $aArgs ) {
54
+ $sRequestedCapability = $aArgs[ 0 ];
55
+ if ( in_array( $sRequestedCapability, [ 'edit_themes', 'edit_plugins', 'edit_files' ] ) ) {
56
+ $aAllCaps[ $sRequestedCapability ] = false;
57
+ }
58
+ return $aAllCaps;
59
+ },
60
+ PHP_INT_MAX, 3
61
+ );
62
+ }
63
+
64
+ public function onWpInit() {
65
+ /** @var Options $opts */
66
+ $opts = $this->getOptions();
67
+
68
+ if ( !Services::WpUsers()->isUserLoggedIn() ) {
69
+ $this->interceptCanonicalRedirects();
70
+ if ( $opts->isRestApiAnonymousAccessDisabled() ) {
71
+ add_filter( 'rest_authentication_errors', [ $this, 'disableAnonymousRestApi' ], 99 );
72
+ }
73
+ }
74
+ }
75
+
76
+ /**
77
+ * @return array|false
78
+ */
79
+ public function disableXmlrpc() {
80
+ $this->getCon()->fireEvent( 'block_xml' );
81
+ return ( current_filter() == 'xmlrpc_enabled' ) ? false : [];
82
+ }
83
+
84
+ /**
85
+ * @uses wp_die()
86
+ */
87
+ private function interceptCanonicalRedirects() {
88
+
89
+ if ( $this->getOptions()->isOpt( 'block_author_discovery', 'Y' ) ) {
90
+ $sAuthor = Services::Request()->query( 'author', '' );
91
+ if ( !empty( $sAuthor ) ) {
92
+ Services::WpGeneral()->wpDie( sprintf(
93
+ __( 'The "author" query parameter has been blocked by %s to protect against user login name fishing.', 'wp-simple-firewall' )
94
+ .sprintf( '<br /><a href="%s" target="_blank">%s</a>',
95
+ 'https://shsec.io/7l',
96
+ __( 'Learn More.', 'wp-simple-firewall' )
97
+ ),
98
+ $this->getCon()->getHumanName()
99
+ ) );
100
+ }
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Understand that if $mCurrentStatus is null, no check has been made. If true, something has
106
+ * authenticated the request, and if WP_Error, then an error is already present
107
+ * @param \WP_Error|true|null $mStatus
108
+ * @return \WP_Error
109
+ */
110
+ public function disableAnonymousRestApi( $mStatus ) {
111
+ /** @var ModCon $mod */
112
+ $mod = $this->getMod();
113
+ $oWpRest = Services::Rest();
114
+
115
+ $sNamespace = $oWpRest->getNamespace();
116
+ if ( !empty( $sNamespace ) && $mStatus !== true && !is_wp_error( $mStatus )
117
+ && !$mod->isPermittedAnonRestApiNamespace( $sNamespace ) ) {
118
+
119
+ $mStatus = new \WP_Error(
120
+ 'shield_block_anon_restapi',
121
+ sprintf( __( 'Anonymous access to the WordPress Rest API has been restricted by %s.', 'wp-simple-firewall' ), $this->getCon()
122
+ ->getHumanName() ),
123
+ [ 'status' => rest_authorization_required_code() ] );
124
+
125
+ $this->getCon()
126
+ ->fireEvent(
127
+ 'block_anonymous_restapi',
128
+ [ 'audit' => [ 'namespace' => $sNamespace ] ]
129
+ );
130
+ }
131
+
132
+ return $mStatus;
133
+ }
134
+ }
src/lib/src/Modules/Lockdown/UI.php CHANGED
@@ -2,8 +2,8 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
 
7
- class UI extends Base\ShieldUI {
8
 
9
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
 
7
+ class UI extends BaseShield\UI {
8
 
9
  }
src/lib/src/Modules/LoginGuard/AdminNotices.php CHANGED
@@ -3,35 +3,32 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
 
6
 
7
  class AdminNotices extends Shield\Modules\Base\AdminNotices {
8
 
9
  /**
10
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
11
- * @throws \Exception
12
  */
13
- protected function processNotice( $oNotice ) {
14
 
15
- switch ( $oNotice->id ) {
16
 
17
  case 'email-verification-sent':
18
- $this->buildNotice_EmailVerificationSent( $oNotice );
19
  break;
20
 
21
  default:
22
- parent::processNotice( $oNotice );
23
  break;
24
  }
25
  }
26
 
27
- /**
28
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
29
- */
30
- private function buildNotice_EmailVerificationSent( $oNotice ) {
31
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $oMod */
32
- $oMod = $this->getMod();
33
 
34
- $oNotice->render_data = [
35
  'notice_attributes' => [],
36
  'strings' => [
37
  'title' => $this->getCon()->getHumanName()
@@ -46,33 +43,27 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
46
  'how_turn_off' => __( "Disable 2FA by email", 'wp-simple-firewall' ),
47
  ],
48
  'ajax' => [
49
- 'resend_verification_email' => $oMod->getAjaxActionData( 'resend_verification_email', true ),
50
- 'disable_2fa_email' => $oMod->getAjaxActionData( 'disable_2fa_email', true ),
51
  ]
52
  ];
53
  }
54
 
55
- /**
56
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
57
- * @return bool
58
- */
59
- protected function isDisplayNeeded( $oNotice ) {
60
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $oMod */
61
- $oMod = $this->getMod();
62
- /** @var Options $oOpts */
63
- $oOpts = $this->getOptions();
64
 
65
- switch ( $oNotice->id ) {
66
 
67
  case 'email-verification-sent':
68
- $bNeeded = $oOpts->isEnabledEmailAuth()
69
- && !$oOpts->isEmailAuthenticationActive() && !$oOpts->getIfCanSendEmailVerified();
70
  break;
71
 
72
  default:
73
- $bNeeded = parent::isDisplayNeeded( $oNotice );
74
  break;
75
  }
76
- return $bNeeded;
77
  }
78
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\AdminNotices\NoticeVO;
7
 
8
  class AdminNotices extends Shield\Modules\Base\AdminNotices {
9
 
10
  /**
11
+ * @inheritDoc
 
12
  */
13
+ protected function processNotice( NoticeVO $notice ) {
14
 
15
+ switch ( $notice->id ) {
16
 
17
  case 'email-verification-sent':
18
+ $this->buildNotice_EmailVerificationSent( $notice );
19
  break;
20
 
21
  default:
22
+ parent::processNotice( $notice );
23
  break;
24
  }
25
  }
26
 
27
+ private function buildNotice_EmailVerificationSent( NoticeVO $notice ) {
28
+ /** @var ModCon $mod */
29
+ $mod = $this->getMod();
 
 
 
30
 
31
+ $notice->render_data = [
32
  'notice_attributes' => [],
33
  'strings' => [
34
  'title' => $this->getCon()->getHumanName()
43
  'how_turn_off' => __( "Disable 2FA by email", 'wp-simple-firewall' ),
44
  ],
45
  'ajax' => [
46
+ 'resend_verification_email' => $mod->getAjaxActionData( 'resend_verification_email', true ),
47
+ 'disable_2fa_email' => $mod->getAjaxActionData( 'disable_2fa_email', true ),
48
  ]
49
  ];
50
  }
51
 
52
+ protected function isDisplayNeeded( NoticeVO $notice ) :bool {
53
+ /** @var Options $opts */
54
+ $opts = $this->getOptions();
 
 
 
 
 
 
55
 
56
+ switch ( $notice->id ) {
57
 
58
  case 'email-verification-sent':
59
+ $needed = $opts->isEnabledEmailAuth()
60
+ && !$opts->isEmailAuthenticationActive() && !$opts->getIfCanSendEmailVerified();
61
  break;
62
 
63
  default:
64
+ $needed = parent::isDisplayNeeded( $notice );
65
  break;
66
  }
67
+ return $needed;
68
  }
69
  }
src/lib/src/Modules/LoginGuard/AjaxHandler.php CHANGED
@@ -6,7 +6,7 @@ use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\TwoFactor;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
- class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
10
 
11
  protected function processAjaxAction( string $action ) :array {
12
 
@@ -42,48 +42,39 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
42
  return $aResponse;
43
  }
44
 
45
- /**
46
- * @return array
47
- */
48
- protected function ajaxExec_GenBackupCodes() {
49
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $oMod */
50
- $oMod = $this->getMod();
51
  /** @var TwoFactor\Provider\Backup $oBU */
52
- $oBU = $oMod->getLoginIntentController()
53
- ->getProviders()[ TwoFactor\Provider\Backup::SLUG ];
54
- $sPass = $oBU->resetSecret( Services::WpUsers()->getCurrentWpUser() );
55
 
56
- foreach ( [ 20, 15, 10, 5 ] as $nPos ) {
57
- $sPass = substr_replace( $sPass, '-', $nPos, 0 );
58
  }
59
 
60
  return [
61
- 'code' => $sPass,
62
  'success' => true
63
  ];
64
  }
65
 
66
- /**
67
- * @return array
68
- */
69
- private function ajaxExec_DeleteBackupCodes() {
70
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $oMod */
71
- $oMod = $this->getMod();
72
  /** @var TwoFactor\Provider\Backup $oBU */
73
- $oBU = $oMod->getLoginIntentController()
74
- ->getProviders()[ TwoFactor\Provider\Backup::SLUG ];
75
  $oBU->deleteSecret( Services::WpUsers()->getCurrentWpUser() );
76
- $oMod->setFlashAdminNotice( __( 'Multi-factor login backup code has been removed from your profile', 'wp-simple-firewall' ) );
77
  return [
78
  'success' => true
79
  ];
80
  }
81
 
82
- /**
83
- * @return array
84
- */
85
- private function ajaxExec_Disable2faEmail() {
86
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $mod */
87
  $mod = $this->getMod();
88
  $mod->setEnabled2FaEmail( false );
89
  return [
@@ -93,17 +84,14 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
93
  ];
94
  }
95
 
96
- /**
97
- * @return array
98
- */
99
- private function ajaxExec_ProfileU2fRemove() {
100
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $mod */
101
  $mod = $this->getMod();
102
 
103
- $sKey = Services::Request()->post( 'u2fid' );
104
  ( new TwoFactor\Provider\U2F() )
105
  ->setMod( $mod )
106
- ->removeRegisteredU2fId( Services::WpUsers()->getCurrentWpUser(), $sKey );
107
  return [
108
  'success' => true,
109
  'message' => __( 'Registered U2F device removed from profile.', 'wp-simple-firewall' ),
@@ -115,13 +103,13 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
115
  * @return array
116
  */
117
  private function ajaxExec_ProfileYubikeyRemove() {
118
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $oMod */
119
- $oMod = $this->getMod();
120
 
121
- $sKey = Services::Request()->post( 'yubikeyid' );
122
  ( new TwoFactor\Provider\Yubikey() )
123
- ->setMod( $oMod )
124
- ->addRemoveRegisteredYubiId( Services::WpUsers()->getCurrentWpUser(), $sKey, false );
125
  return [
126
  'success' => true,
127
  'message' => __( 'Yubikey removed from profile.', 'wp-simple-firewall' ),
@@ -133,17 +121,17 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
133
  * @return array
134
  */
135
  private function ajaxExec_ResendEmailVerification() {
136
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $mod */
137
  $mod = $this->getMod();
138
- /** @var Options $oOpts */
139
- $oOpts = $this->getOptions();
140
- $bSuccess = true;
141
 
142
- if ( !$oOpts->isEnabledEmailAuth() ) {
143
  $sMessage = __( 'Email 2FA option is not currently enabled.', 'wp-simple-firewall' );
144
- $bSuccess = false;
145
  }
146
- elseif ( $oOpts->getIfCanSendEmailVerified() ) {
147
  $sMessage = __( 'Email sending has already been verified.', 'wp-simple-firewall' );
148
  }
149
  else {
@@ -153,7 +141,7 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
153
  }
154
 
155
  return [
156
- 'success' => $bSuccess,
157
  'message' => $sMessage
158
  ];
159
  }
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\TwoFactor;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
+ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
10
 
11
  protected function processAjaxAction( string $action ) :array {
12
 
42
  return $aResponse;
43
  }
44
 
45
+ protected function ajaxExec_GenBackupCodes() :array {
46
+ /** @var ModCon $mod */
47
+ $mod = $this->getMod();
 
 
 
48
  /** @var TwoFactor\Provider\Backup $oBU */
49
+ $oBU = $mod->getLoginIntentController()
50
+ ->getProviders()[ TwoFactor\Provider\Backup::SLUG ];
51
+ $pass = $oBU->resetSecret( Services::WpUsers()->getCurrentWpUser() );
52
 
53
+ foreach ( [ 20, 15, 10, 5 ] as $pos ) {
54
+ $pass = substr_replace( $pass, '-', $pos, 0 );
55
  }
56
 
57
  return [
58
+ 'code' => $pass,
59
  'success' => true
60
  ];
61
  }
62
 
63
+ private function ajaxExec_DeleteBackupCodes() :array {
64
+ /** @var ModCon $mod */
65
+ $mod = $this->getMod();
 
 
 
66
  /** @var TwoFactor\Provider\Backup $oBU */
67
+ $oBU = $mod->getLoginIntentController()
68
+ ->getProviders()[ TwoFactor\Provider\Backup::SLUG ];
69
  $oBU->deleteSecret( Services::WpUsers()->getCurrentWpUser() );
70
+ $mod->setFlashAdminNotice( __( 'Multi-factor login backup code has been removed from your profile', 'wp-simple-firewall' ) );
71
  return [
72
  'success' => true
73
  ];
74
  }
75
 
76
+ private function ajaxExec_Disable2faEmail() :array {
77
+ /** @var ModCon $mod */
 
 
 
78
  $mod = $this->getMod();
79
  $mod->setEnabled2FaEmail( false );
80
  return [
84
  ];
85
  }
86
 
87
+ private function ajaxExec_ProfileU2fRemove() :array {
88
+ /** @var ModCon $mod */
 
 
 
89
  $mod = $this->getMod();
90
 
91
+ $key = Services::Request()->post( 'u2fid' );
92
  ( new TwoFactor\Provider\U2F() )
93
  ->setMod( $mod )
94
+ ->removeRegisteredU2fId( Services::WpUsers()->getCurrentWpUser(), $key );
95
  return [
96
  'success' => true,
97
  'message' => __( 'Registered U2F device removed from profile.', 'wp-simple-firewall' ),
103
  * @return array
104
  */
105
  private function ajaxExec_ProfileYubikeyRemove() {
106
+ /** @var ModCon $mod */
107
+ $mod = $this->getMod();
108
 
109
+ $key = Services::Request()->post( 'yubikeyid' );
110
  ( new TwoFactor\Provider\Yubikey() )
111
+ ->setMod( $mod )
112
+ ->addRemoveRegisteredYubiId( Services::WpUsers()->getCurrentWpUser(), $key, false );
113
  return [
114
  'success' => true,
115
  'message' => __( 'Yubikey removed from profile.', 'wp-simple-firewall' ),
121
  * @return array
122
  */
123
  private function ajaxExec_ResendEmailVerification() {
124
+ /** @var ModCon $mod */
125
  $mod = $this->getMod();
126
+ /** @var Options $opts */
127
+ $opts = $this->getOptions();
128
+ $success = true;
129
 
130
+ if ( !$opts->isEnabledEmailAuth() ) {
131
  $sMessage = __( 'Email 2FA option is not currently enabled.', 'wp-simple-firewall' );
132
+ $success = false;
133
  }
134
+ elseif ( $opts->getIfCanSendEmailVerified() ) {
135
  $sMessage = __( 'Email sending has already been verified.', 'wp-simple-firewall' );
136
  }
137
  else {
141
  }
142
 
143
  return [
144
+ 'success' => $success,
145
  'message' => $sMessage
146
  ];
147
  }
src/lib/src/Modules/LoginGuard/Insights/OverviewCards.php CHANGED
@@ -3,14 +3,14 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Insights;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Options;
7
 
8
  class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
9
 
10
  public function build() :array {
11
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $mod */
12
  $mod = $this->getMod();
13
- /** @var Options $opts */
14
  $opts = $this->getOptions();
15
 
16
  $cardSection = [
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Insights;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
7
 
8
  class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
9
 
10
  public function build() :array {
11
+ /** @var LoginGuard\ModCon $mod */
12
  $mod = $this->getMod();
13
+ /** @var LoginGuard\Options $opts */
14
  $opts = $this->getOptions();
15
 
16
  $cardSection = [
src/lib/src/Modules/LoginGuard/Lib/AntiBot/AntibotSetup.php CHANGED
@@ -23,7 +23,7 @@ class AntibotSetup {
23
  }
24
 
25
  private function run() {
26
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $mod */
27
  $mod = $this->getMod();
28
  /** @var LoginGuard\Options $opts */
29
  $opts = $this->getOptions();
@@ -40,12 +40,12 @@ class AntibotSetup {
40
  }
41
 
42
  if ( $mod->isEnabledCaptcha() ) {
43
- $oCfg = $mod->getCaptchaCfg();
44
- if ( $oCfg->provider === CaptchaConfigVO::PROV_GOOGLE_RECAP2 ) {
45
  $aProtectionProviders[] = ( new AntiBot\ProtectionProviders\GoogleRecaptcha() )
46
  ->setMod( $mod );
47
  }
48
- elseif ( $oCfg->provider === CaptchaConfigVO::PROV_HCAPTCHA ) {
49
  $aProtectionProviders[] = ( new AntiBot\ProtectionProviders\HCaptcha() )
50
  ->setMod( $mod );
51
  }
23
  }
24
 
25
  private function run() {
26
+ /** @var LoginGuard\ModCon $mod */
27
  $mod = $this->getMod();
28
  /** @var LoginGuard\Options $opts */
29
  $opts = $this->getOptions();
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
  }
src/lib/src/Modules/LoginGuard/Lib/AntiBot/FormProviders/WooCommerce.php CHANGED
@@ -2,15 +2,15 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot\FormProviders;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Options;
6
 
7
  class WooCommerce extends BaseFormProvider {
8
 
9
  public function run() {
10
  parent::run();
11
- /** @var Options $oOpts */
12
- $oOpts = $this->getOptions();
13
- if ( $oOpts->isProtect( 'checkout_woo' ) ) {
14
  $this->woocheckout();
15
  }
16
  }
@@ -50,10 +50,10 @@ class WooCommerce extends BaseFormProvider {
50
  * @return void
51
  */
52
  public function formInsertsPrint_WooLogin() {
53
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $oMod */
54
- $oMod = $this->getMod();
55
  $sInserts = $this->formInsertsBuild();
56
- if ( $oMod->getCaptchaCfg()->invisible ) {
57
  $sInserts .= '<input type="hidden" name="login" value="Log in" />';
58
  }
59
  echo $sInserts;
@@ -63,10 +63,10 @@ class WooCommerce extends BaseFormProvider {
63
  * @return void
64
  */
65
  public function formInsertsPrint_WooRegister() {
66
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $oMod */
67
- $oMod = $this->getMod();
68
  $sInserts = $this->formInsertsBuild();
69
- if ( $oMod->getCaptchaCfg()->invisible ) {
70
  $sInserts .= '<input type="hidden" name="register" value="Register" />';
71
  }
72
  echo $sInserts;
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot\FormProviders;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
6
 
7
  class WooCommerce extends BaseFormProvider {
8
 
9
  public function run() {
10
  parent::run();
11
+ /** @var LoginGuard\Options $opts */
12
+ $opts = $this->getOptions();
13
+ if ( $opts->isProtect( 'checkout_woo' ) ) {
14
  $this->woocheckout();
15
  }
16
  }
50
  * @return void
51
  */
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
  }
59
  echo $sInserts;
63
  * @return void
64
  */
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
  }
72
  echo $sInserts;
src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/GaspJs.php CHANGED
@@ -22,13 +22,13 @@ class GaspJs extends BaseProtectionProvider {
22
  return;
23
  }
24
 
25
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $oMod */
26
- $oMod = $this->getMod();
27
  $this->setFactorTested( true );
28
 
29
- $oReq = Services::Request();
30
- $sGaspCheckBox = $oReq->post( $oMod->getGaspKey() );
31
- $sHoney = $oReq->post( 'icwp_wpsf_login_email' );
32
 
33
  $sUsername = $oForm->getUserToAudit();
34
  $sActionAttempted = $oForm->getActionToAudit();
@@ -71,10 +71,10 @@ class GaspJs extends BaseProtectionProvider {
71
 
72
  public function onWpEnqueueJs() {
73
  $con = $this->getCon();
74
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $mod */
75
  $mod = $this->getMod();
76
- /** @var LoginGuard\Options $oOpts */
77
- $oOpts = $this->getOptions();
78
 
79
  $sAsset = 'shield-antibot';
80
  $sUnique = $con->prefix( $sAsset );
@@ -90,7 +90,7 @@ class GaspJs extends BaseProtectionProvider {
90
  $sUnique,
91
  'icwp_wpsf_vars_lpantibot',
92
  [
93
- 'form_selectors' => implode( ',', $oOpts->getAntiBotFormSelectors() ),
94
  'uniq' => preg_replace( '#[^a-zA-Z0-9]#', '', apply_filters( 'icwp_shield_lp_gasp_uniqid', uniqid() ) ),
95
  'cbname' => $mod->getGaspKey(),
96
  'strings' => [
@@ -99,7 +99,7 @@ class GaspJs extends BaseProtectionProvider {
99
  'loading' => __( 'Loading', 'wp-simple-firewall' )
100
  ],
101
  'flags' => [
102
- 'gasp' => $oOpts->isEnabledGaspCheck(),
103
  'captcha' => $mod->isEnabledCaptcha(),
104
  ]
105
  ]
22
  return;
23
  }
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();
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
  $sAsset = 'shield-antibot';
80
  $sUnique = $con->prefix( $sAsset );
90
  $sUnique,
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' => [
99
  'loading' => __( 'Loading', 'wp-simple-firewall' )
100
  ],
101
  'flags' => [
102
+ 'gasp' => $opts->isEnabledGaspCheck(),
103
  'captcha' => $mod->isEnabledCaptcha(),
104
  ]
105
  ]
src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/GoogleRecaptcha.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\Utilities\ReCaptcha\TestRequest;
6
 
7
  class GoogleRecaptcha extends BaseProtectionProvider {
@@ -50,9 +51,9 @@ class GoogleRecaptcha extends BaseProtectionProvider {
50
  * @return string
51
  */
52
  private function getCaptchaHtml() {
53
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $oMod */
54
- $oMod = $this->getMod();
55
- if ( $oMod->getCaptchaCfg()->invisible ) {
56
  $sExtraStyles = '';
57
  }
58
  else {
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib\AntiBot\ProtectionProviders;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\ModCon;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Utilities\ReCaptcha\TestRequest;
7
 
8
  class GoogleRecaptcha extends BaseProtectionProvider {
51
  * @return string
52
  */
53
  private function getCaptchaHtml() {
54
+ /** @var ModCon $mod */
55
+ $mod = $this->getMod();
56
+ if ( $mod->getCaptchaCfg()->invisible ) {
57
  $sExtraStyles = '';
58
  }
59
  else {
src/lib/src/Modules/LoginGuard/Lib/CooldownRedirect.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
@@ -30,10 +31,10 @@ class CooldownRedirect {
30
  }
31
 
32
  private function renderCooldownPage() {
33
- /** @var \ICWP_WPSF_FeatureHandler_Ips $oMod */
34
- $oMod = $this->getMod();
35
  $nTimeRemaining = ( new LoginGuard\Lib\CooldownFlagFile() )
36
- ->setMod( $oMod )
37
  ->getCooldownRemaining();
38
  $aData = [
39
  'strings' => [
@@ -56,7 +57,7 @@ class CooldownRedirect {
56
  ];
57
  Services::WpGeneral()
58
  ->wpDie(
59
- $oMod->renderTemplate( '/snippets/cooldown_login_block.twig', $aData, true )
60
  );
61
  }
62
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Lib;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\ModCon;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
31
  }
32
 
33
  private function renderCooldownPage() {
34
+ /** @var ModCon $mod */
35
+ $mod = $this->getMod();
36
  $nTimeRemaining = ( new LoginGuard\Lib\CooldownFlagFile() )
37
+ ->setMod( $mod )
38
  ->getCooldownRemaining();
39
  $aData = [
40
  'strings' => [
57
  ];
58
  Services::WpGeneral()
59
  ->wpDie(
60
+ $mod->renderTemplate( '/snippets/cooldown_login_block.twig', $aData, true )
61
  );
62
  }
63
  }
src/lib/src/Modules/LoginGuard/Lib/Rename/RenameLogin.php ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
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;
9
+ use FernleafSystems\Wordpress\Services\Services;
10
+
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 !empty( $opts->getCustomLoginPath() )
24
+ && !$this->hasPluginConflict() && !$this->hasUnsupportedConfiguration();
25
+ }
26
+
27
+ public function onWpInit() {
28
+ /** @var LoginGuard\ModCon $mod */
29
+ $mod = $this->getMod();
30
+
31
+ if ( Services::WpGeneral()->isLoginUrl() &&
32
+ ( $mod->isVisitorWhitelisted() || Services::WpUsers()->isUserLoggedIn() ) ) {
33
+ return;
34
+ }
35
+ if ( is_admin() && $mod->isVisitorWhitelisted() && !Services::WpUsers()->isUserLoggedIn() ) {
36
+ return;
37
+ }
38
+
39
+ $this->doBlockPossibleWpLoginLoad();
40
+
41
+ // Loads the wp-login.php if the correct URL is loaded
42
+ add_action( 'wp_loaded', [ $this, 'aLoadWpLogin' ] );
43
+
44
+ // Shouldn't be necessary, but in-case something else includes the wp-login.php, we block that too.
45
+ add_action( 'login_init', [ $this, 'aLoginFormAction' ], 0 );
46
+
47
+ // ensure that wp-login.php is never used in site urls or redirects
48
+ add_filter( 'site_url', [ $this, 'fCheckForLoginPhp' ], 20, 1 );
49
+ add_filter( 'network_site_url', [ $this, 'fCheckForLoginPhp' ], 20, 1 );
50
+ add_filter( 'wp_redirect', [ $this, 'fCheckForLoginPhp' ], 20, 1 );
51
+ if ( !Services::WpUsers()->isUserLoggedIn() ) {
52
+ add_filter( 'wp_redirect', [ $this, 'fProtectUnauthorizedLoginRedirect' ], 50, 1 );
53
+ }
54
+ add_filter( 'register_url', [ $this, 'blockRegisterUrlRedirect' ], 20, 1 );
55
+
56
+ add_filter( 'et_anticipate_exceptions', [ $this, 'fAddToEtMaintenanceExceptions' ] );
57
+ }
58
+
59
+ private function hasPluginConflict() :bool {
60
+ /** @var LoginGuard\ModCon $mod */
61
+ $mod = $this->getMod();
62
+ /** @var LoginGuard\Options $opts */
63
+ $opts = $this->getOptions();
64
+
65
+ $sMessage = '';
66
+ $bConflicted = false;
67
+
68
+ $path = $opts->getCustomLoginPath();
69
+
70
+ $WP = Services::WpGeneral();
71
+ if ( $WP->isMultisite() ) {
72
+ $sMessage = __( 'Your login URL is unchanged because the Rename WP Login feature is not currently supported on WPMS.', 'wp-simple-firewall' );
73
+ $bConflicted = true;
74
+ }
75
+ elseif ( class_exists( 'Rename_WP_Login' ) ) {
76
+ $sMessage = sprintf( __( 'Can not use the Rename WP Login feature because you have the "%s" plugin installed and it is active.', 'wp-simple-firewall' ), 'Rename WP Login' );
77
+ $bConflicted = true;
78
+ }
79
+ elseif ( class_exists( 'Theme_My_Login' ) ) {
80
+ $sMessage = sprintf( __( 'Can not use the Rename WP Login feature because you have the "%s" plugin installed and it is active.', 'wp-simple-firewall' ), 'Theme My Login' );
81
+ $bConflicted = true;
82
+ }
83
+ elseif ( !$WP->isPermalinksEnabled() ) {
84
+ $sMessage = sprintf( __( 'Can not use the Rename WP Login feature because you have not enabled %s.', 'wp-simple-firewall' ), __( 'Permalinks' ) );
85
+ $bConflicted = true;
86
+ }
87
+ elseif ( $WP->isPermalinksEnabled() && ( $WP->getDoesWpSlugExist( $path ) || in_array( $path, $WP->getAutoRedirectLocations() ) ) ) {
88
+ $sMessage = sprintf( __( 'Can not use the Rename WP Login feature because you have chosen a path ("%s") that is reserved on your WordPress site.', 'wp-simple-firewall' ), $path );
89
+ $bConflicted = true;
90
+ }
91
+
92
+ if ( $bConflicted ) {
93
+ $sNoticeMessage = sprintf( '<strong>%s</strong>: %s',
94
+ __( 'Warning', 'wp-simple-firewall' ),
95
+ $sMessage
96
+ );
97
+ $mod->setFlashAdminNotice( $sNoticeMessage, true );
98
+ }
99
+
100
+ return $bConflicted;
101
+ }
102
+
103
+ private function hasUnsupportedConfiguration() :bool {
104
+ /** @var LoginGuard\ModCon $mod */
105
+ $mod = $this->getMod();
106
+ $path = Services::Request()->getPath();
107
+
108
+ $unsupported = empty( $path );
109
+ if ( $unsupported ) {
110
+ $mod->setFlashAdminNotice(
111
+ sprintf(
112
+ '<strong>%s</strong>: %s',
113
+ __( 'Warning', 'wp-simple-firewall' ),
114
+ __( 'Your login URL is unchanged because your current hosting/PHP configuration cannot parse the necessary information.', 'wp-simple-firewall' )
115
+ ),
116
+ true
117
+ );
118
+ }
119
+
120
+ return $unsupported;
121
+ }
122
+
123
+ public function doBlockPossibleWpLoginLoad() {
124
+
125
+ // To begin, we block if it's an access to the admin area and the user isn't logged in (and it's not ajax)
126
+ $bDoBlock = is_admin() && !Services::WpGeneral()->isAjax()
127
+ && !Services::WpUsers()->isUserLoggedIn();
128
+
129
+ // Next block option is where it's a direct attempt to access the old login URL
130
+ if ( !$bDoBlock ) {
131
+ $path = trim( Services::Request()->getPath(), '/' );
132
+ $possible = [
133
+ trim( home_url( 'wp-login.php', 'relative' ), '/' ),
134
+ trim( home_url( 'wp-signup.php', 'relative' ), '/' ),
135
+ trim( site_url( 'wp-signup.php', 'relative' ), '/' ),
136
+ // trim( site_url( 'wp-login.php', 'relative' ), '/' ), our own filters in run() scuttle us here so we have to build it manually
137
+ trim( rtrim( site_url( '', 'relative' ), '/' ).'/wp-login.php', '/' ),
138
+ trim( home_url( 'login', 'relative' ), '/' ),
139
+ trim( site_url( 'login', 'relative' ), '/' )
140
+ ];
141
+ $bDoBlock = !empty( $path )
142
+ && ( in_array( $path, $possible ) || preg_match( '/wp-login\.php/i', $path ) );
143
+ }
144
+
145
+ if ( $bDoBlock ) {
146
+ $this->doWpLoginFailedRedirect404();
147
+ }
148
+ }
149
+
150
+ /**
151
+ * @param string $sLocation
152
+ * @return string
153
+ */
154
+ public function fCheckForLoginPhp( $sLocation ) {
155
+ /** @var LoginGuard\Options $opts */
156
+ $opts = $this->getOptions();
157
+
158
+ $sRedirectPath = parse_url( $sLocation, PHP_URL_PATH );
159
+ if ( strpos( $sRedirectPath, 'wp-login.php' ) !== false ) {
160
+
161
+ $sLoginUrl = home_url( $opts->getCustomLoginPath() );
162
+ $aQueryArgs = explode( '?', $sLocation );
163
+ if ( !empty( $aQueryArgs[ 1 ] ) ) {
164
+ parse_str( $aQueryArgs[ 1 ], $aNewQueryArgs );
165
+ $sLoginUrl = add_query_arg( $aNewQueryArgs, $sLoginUrl );
166
+ }
167
+ return $sLoginUrl;
168
+ }
169
+ return $sLocation;
170
+ }
171
+
172
+ /**
173
+ * @param string $sLocation
174
+ * @return string
175
+ */
176
+ public function fProtectUnauthorizedLoginRedirect( $sLocation ) {
177
+ /** @var LoginGuard\Options $opts */
178
+ $opts = $this->getOptions();
179
+
180
+ if ( !Services::WpGeneral()->isLoginUrl() ) {
181
+ $sRedirectPath = trim( parse_url( $sLocation, PHP_URL_PATH ), '/' );
182
+ $bRedirectIsHiddenUrl = ( $sRedirectPath == $opts->getCustomLoginPath() );
183
+ if ( $bRedirectIsHiddenUrl && !Services::WpUsers()->isUserLoggedIn() ) {
184
+ $this->doWpLoginFailedRedirect404();
185
+ }
186
+ }
187
+ return $sLocation;
188
+ }
189
+
190
+ /**
191
+ * @param string $url
192
+ * @return string
193
+ */
194
+ public function blockRegisterUrlRedirect( $url ) {
195
+ if ( strpos( Services::Request()->getPath(), 'wp-register.php' ) ) {
196
+ $this->doWpLoginFailedRedirect404();
197
+ die();
198
+ }
199
+ return $url;
200
+ }
201
+
202
+ public function aLoadWpLogin() {
203
+ if ( Services::WpGeneral()->isLoginUrl() ) {
204
+ @require_once( ABSPATH.'wp-login.php' );
205
+ die();
206
+ }
207
+ }
208
+
209
+ public function aLoginFormAction() {
210
+ if ( !Services::WpGeneral()->isLoginUrl() ) {
211
+ $this->doWpLoginFailedRedirect404();
212
+ die();
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Add the custom login URL to the Elegant Themes Maintenance Mode plugin URL exceptions list
218
+ * @param array $aUrlExceptions
219
+ * @return array
220
+ */
221
+ public function fAddToEtMaintenanceExceptions( $aUrlExceptions ) {
222
+ /** @var LoginGuard\Options $opts */
223
+ $opts = $this->getOptions();
224
+ $aUrlExceptions[] = $opts->getCustomLoginPath();
225
+ return $aUrlExceptions;
226
+ }
227
+
228
+ /**
229
+ * Will by default send a 404 response screen. Has a filter to specify redirect URL.
230
+ */
231
+ protected function doWpLoginFailedRedirect404() {
232
+ $this->getCon()->fireEvent( 'hide_login_url' );
233
+
234
+ $sRedirectUrl = apply_filters( 'icwp_shield_renamewplogin_redirect_url', false );
235
+ if ( !empty( $sRedirectUrl ) ) {
236
+ $sRedirectUrl = esc_url( $sRedirectUrl );
237
+ if ( @parse_url( $sRedirectUrl ) !== false ) {
238
+ Services::Response()->redirect( $sRedirectUrl, [], false );
239
+ }
240
+ }
241
+
242
+ Services::Response()->sendApache404( '', Services::WpGeneral()->getHomeUrl() );
243
+ }
244
+ }
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/LoginIntentPage.php CHANGED
@@ -20,7 +20,7 @@ class LoginIntentPage {
20
  */
21
  public function renderForm() {
22
  $oIC = $this->getMfaCon();
23
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $mod */
24
  $mod = $oIC->getMod();
25
  /** @var LoginGuard\Options $opts */
26
  $opts = $oIC->getOptions();
@@ -96,7 +96,7 @@ class LoginIntentPage {
96
  ],
97
  'flags' => [
98
  'can_skip_mfa' => $opts->isMfaSkip(),
99
- 'show_branded_links' => !$mod->isWlEnabled(), // white label mitigation
100
  ]
101
  ];
102
 
@@ -113,34 +113,34 @@ class LoginIntentPage {
113
  */
114
  private function renderPage() {
115
  $oIC = $this->getMfaCon();
116
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $oMod */
117
- $oMod = $oIC->getMod();
118
- $oCon = $oIC->getCon();
119
- $oReq = Services::Request();
120
-
121
- $aLabels = $oCon->getLabels();
122
- $sBannerUrl = empty( $aLabels[ 'url_login2fa_logourl' ] ) ? $oCon->getPluginUrl_Image( 'pluginlogo_banner-772x250.png' ) : $aLabels[ 'url_login2fa_logourl' ];
123
- $nTimeRemaining = $oMod->getSession()->login_intent_expires_at - $oReq->ts();
124
  $aDisplayData = [
125
  'strings' => [
126
  'what_is_this' => __( 'What is this?', 'wp-simple-firewall' ),
127
- 'page_title' => sprintf( __( '%s Login Verification', 'wp-simple-firewall' ), $oCon->getHumanName() ),
128
  ],
129
  'data' => [
130
  'time_remaining' => $nTimeRemaining,
131
  ],
132
  'hrefs' => [
133
- 'css_bootstrap' => $oCon->getPluginUrl_Css( 'bootstrap4.min' ),
134
- 'js_bootstrap' => $oCon->getPluginUrl_Js( 'bootstrap4.min' ),
135
  'shield_logo' => 'https://ps.w.org/wp-simple-firewall/assets/banner-772x250.png',
136
  'what_is_this' => 'https://icontrolwp.freshdesk.com/support/solutions/articles/3000064840',
137
  ],
138
  'imgs' => [
139
  'banner' => $sBannerUrl,
140
- 'favicon' => $oCon->getPluginUrl_Image( 'pluginlogo_24x24.png' ),
141
  ],
142
  'flags' => [
143
- 'show_branded_links' => !$oMod->isWlEnabled(), // white label mitigation
144
  'has_u2f' => isset( $oIC->getProvidersForUser(
145
  Services::WpUsers()->getCurrentWpUser(), true )[ LoginGuard\Lib\TwoFactor\Provider\U2F::SLUG ] )
146
  ],
@@ -154,17 +154,17 @@ class LoginIntentPage {
154
  $aDisplayData[ 'head' ] = [
155
  'scripts' => [
156
  [
157
- 'src' => $oCon->getPluginUrl_Js( 'u2f-bundle.js' ),
158
  ],
159
  [
160
- 'src' => $oCon->getPluginUrl_Js( 'u2f-frontend.js' ),
161
  ]
162
  ]
163
  ];
164
  }
165
 
166
- return $oMod->renderTemplate( '/pages/login_intent/index.twig',
167
  Services::DataManipulation()->mergeArraysRecursive(
168
- $oMod->getUIHandler()->getBaseDisplayData(), $aDisplayData ), true );
169
  }
170
  }
20
  */
21
  public function renderForm() {
22
  $oIC = $this->getMfaCon();
23
+ /** @var LoginGuard\ModCon $mod */
24
  $mod = $oIC->getMod();
25
  /** @var LoginGuard\Options $opts */
26
  $opts = $oIC->getOptions();
96
  ],
97
  'flags' => [
98
  'can_skip_mfa' => $opts->isMfaSkip(),
99
+ 'show_branded_links' => !$con->getModule_SecAdmin()->isWlEnabled(), // white label mitigation
100
  ]
101
  ];
102
 
113
  */
114
  private function renderPage() {
115
  $oIC = $this->getMfaCon();
116
+ /** @var LoginGuard\ModCon $mod */
117
+ $mod = $oIC->getMod();
118
+ $con = $oIC->getCon();
119
+ $req = Services::Request();
120
+
121
+ $aLabels = $con->getLabels();
122
+ $sBannerUrl = empty( $aLabels[ 'url_login2fa_logourl' ] ) ? $con->getPluginUrl_Image( 'pluginlogo_banner-772x250.png' ) : $aLabels[ 'url_login2fa_logourl' ];
123
+ $nTimeRemaining = $mod->getSession()->login_intent_expires_at - $req->ts();
124
  $aDisplayData = [
125
  'strings' => [
126
  'what_is_this' => __( 'What is this?', 'wp-simple-firewall' ),
127
+ 'page_title' => sprintf( __( '%s Login Verification', 'wp-simple-firewall' ), $con->getHumanName() ),
128
  ],
129
  'data' => [
130
  'time_remaining' => $nTimeRemaining,
131
  ],
132
  'hrefs' => [
133
+ 'css_bootstrap' => $con->getPluginUrl_Css( 'bootstrap4.min' ),
134
+ 'js_bootstrap' => $con->getPluginUrl_Js( 'bootstrap4.min' ),
135
  'shield_logo' => 'https://ps.w.org/wp-simple-firewall/assets/banner-772x250.png',
136
  'what_is_this' => 'https://icontrolwp.freshdesk.com/support/solutions/articles/3000064840',
137
  ],
138
  'imgs' => [
139
  'banner' => $sBannerUrl,
140
+ 'favicon' => $con->getPluginUrl_Image( '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
  ],
154
  $aDisplayData[ 'head' ] = [
155
  'scripts' => [
156
  [
157
+ 'src' => $con->getPluginUrl_Js( 'u2f-bundle.js' ),
158
  ],
159
  [
160
+ 'src' => $con->getPluginUrl_Js( 'u2f-frontend.js' ),
161
  ]
162
  ]
163
  ];
164
  }
165
 
166
+ return $mod->renderTemplate( '/pages/login_intent/index.twig',
167
  Services::DataManipulation()->mergeArraysRecursive(
168
+ $mod->getUIHandler()->getBaseDisplayData(), $aDisplayData ), true );
169
  }
170
  }
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/MfaController.php CHANGED
@@ -11,17 +11,13 @@ use FernleafSystems\Wordpress\Services\Services;
11
  class MfaController {
12
 
13
  use Shield\Modules\ModConsumer;
 
14
 
15
  /**
16
  * @var Provider\BaseProvider[]
17
  */
18
  private $aProviders;
19
 
20
- /**
21
- * @var bool
22
- */
23
- protected $bLoginAttemptCaptured;
24
-
25
  /**
26
  * @var LoginIntentPage
27
  */
@@ -29,23 +25,15 @@ class MfaController {
29
 
30
  public function run() {
31
  add_action( 'init', [ $this, 'onWpInit' ], 10, 2 );
32
- add_action( 'wp_login', [ $this, 'onWpLogin' ], 10, 2 );
33
- if ( !Services::WpUsers()->isProfilePage() ) { // This can be fired during profile update.
34
- add_action( 'set_logged_in_cookie', [ $this, 'onWpSetLoggedInCookie' ], 5, 4 );
35
- }
36
  add_action( 'wp_loaded', [ $this, 'onWpLoaded' ], 10, 2 );
 
37
  }
38
 
39
  public function onWpInit() {
40
- $this->assessLoginIntent();
41
- }
42
-
43
- /**
44
- * @param string $sUsername
45
- * @param \WP_User $oUser
46
- */
47
- public function onWpLogin( $sUsername, $oUser ) {
48
- $this->captureLoginIntent( $oUser );
49
  }
50
 
51
  public function onWpLoaded() {
@@ -58,51 +46,34 @@ class MfaController {
58
  } );
59
  }
60
 
61
- /**
62
- * @param string $sCookie
63
- * @param int $nExpire
64
- * @param int $nExpiration
65
- * @param int $nUserId
66
- */
67
- public function onWpSetLoggedInCookie( $sCookie, $nExpire, $nExpiration, $nUserId ) {
68
- $this->captureLoginIntent( Services::WpUsers()->getUserById( $nUserId ) );
69
  }
70
 
71
- /**
72
- * @param \WP_User $oUser
73
- */
74
- private function captureLoginIntent( $oUser ) {
75
- if ( empty( $this->bLoginAttemptCaptured ) && $oUser instanceof \WP_User ) {
76
- $this->bLoginAttemptCaptured = true;
77
-
78
- /** @var LoginGuard\Options $opts */
79
- $opts = $this->getOptions();
80
- if ( $this->isSubjectToLoginIntent( $oUser ) && !$this->canUserMfaSkip( $oUser ) ) {
81
-
82
- $aProviders = $this->getProvidersForUser( $oUser );
83
- if ( !empty( $aProviders ) ) {
84
- foreach ( $aProviders as $oProvider ) {
85
- $oProvider->captureLoginAttempt( $oUser );
86
- }
87
-
88
- $this->setLoginIntentExpiresAt(
89
- Services::Request()
90
- ->carbon()
91
- ->addMinutes( $opts->getLoginIntentMinutes() )->timestamp
92
- );
93
  }
 
 
 
 
 
 
94
  }
95
  }
96
  }
97
 
98
- /**
99
- * Deals with the scenario when the user session has a login intent.
100
- */
101
- private function assessLoginIntent() {
102
- $oUser = Services::WpUsers()->getCurrentWpUser();
103
- if ( $oUser instanceof \WP_User && $this->hasLoginIntent() ) {
104
 
105
- if ( $this->isSubjectToLoginIntent( $oUser ) ) {
106
 
107
  if ( $this->getLoginIntentExpiresAt() > Services::Request()->ts() ) {
108
  $this->processActiveLoginIntent();
@@ -120,10 +91,7 @@ class MfaController {
120
  }
121
  }
122
 
123
- /**
124
- * @return LoginIntentPage
125
- */
126
- private function getLoginIntentPageHandler() {
127
  if ( !isset( $this->oLoginIntentPageHandler ) ) {
128
  $this->oLoginIntentPageHandler = ( new LoginIntentPage() )->setMfaController( $this );
129
  }
@@ -133,7 +101,7 @@ class MfaController {
133
  /**
134
  * @return Provider\BaseProvider[]
135
  */
136
- public function getProviders() {
137
  if ( !is_array( $this->aProviders ) ) {
138
  $this->aProviders = [
139
  Provider\Email::SLUG => ( new Provider\Email() )->setMod( $this->getMod() ),
@@ -148,81 +116,81 @@ class MfaController {
148
 
149
  /**
150
  * Ensures that BackupCode provider isn't supplied on its own, and the user profile is setup for each.
151
- * @param \WP_User $oUser
152
  * @param bool $bOnlyActiveProfiles
153
  * @return Provider\BaseProvider[]
154
  */
155
- public function getProvidersForUser( $oUser, $bOnlyActiveProfiles = false ) {
156
- $aPs = array_filter( $this->getProviders(),
157
- function ( $oProvider ) use ( $oUser, $bOnlyActiveProfiles ) {
158
  /** @var Provider\BaseProvider $oProvider */
159
- return $oProvider->isProviderAvailableToUser( $oUser )
160
- && ( !$bOnlyActiveProfiles || $oProvider->isProfileActive( $oUser ) );
161
  }
162
  );
163
 
164
  // Neither BackupCode NOR U2F should EVER be the only 1 provider available.
165
- if ( count( $aPs ) === 1 ) {
166
  /** @var Provider\BaseProvider $oFirst */
167
- $oFirst = reset( $aPs );
168
  if ( !$oFirst::STANDALONE ) {
169
- $aPs = [];
170
  }
171
  }
172
- return $aPs;
173
  }
174
 
175
  /**
176
  * hooked to 'init' and only run if a user is logged-in (not on the login request)
177
  */
178
  private function processActiveLoginIntent() {
179
- /** @var LoginGuard\Options $oOpts */
180
- $oOpts = $this->getOptions();
181
- $oCon = $this->getCon();
182
- $oReq = Services::Request();
183
- $oWpResp = Services::Response();
184
- $oWpUsers = Services::WpUsers();
185
 
186
  // Is 2FA/login-intent submit
187
- if ( $oReq->request( $this->getLoginIntentRequestFlag() ) == 1 ) {
188
 
189
- if ( $oReq->post( 'cancel' ) == 1 ) {
190
- $oWpUsers->logoutUser(); // clears the login and login intent
191
- $sRedirectHref = $oReq->post( 'cancel_href' );
192
- empty( $sRedirectHref ) ? $oWpResp->redirectToLogin() : $oWpResp->redirect( $sRedirectHref );
193
  }
194
  elseif ( $this->validateLoginIntentRequest() ) {
195
 
196
- if ( $oReq->post( 'skip_mfa' ) === 'Y' ) {
197
  ( new MfaSkip() )
198
  ->setMod( $this->getMod() )
199
- ->addMfaSkip( $oWpUsers->getCurrentWpUser() );
200
  }
201
 
202
- $oCon->fireEvent( '2fa_success' );
203
 
204
  $sFlash = __( 'Success', 'wp-simple-firewall' ).'! '.__( 'Thank you for authenticating your login.', 'wp-simple-firewall' );
205
- if ( $oOpts->isEnabledBackupCodes() ) {
206
  $sFlash .= ' '.__( 'If you used your Backup Code, you will need to reset it.', 'wp-simple-firewall' ); //TODO::
207
  }
208
  $this->getMod()->setFlashAdminNotice( $sFlash );
209
 
210
  $this->removeLoginIntent();
211
 
212
- $sRedirectHref = $oReq->post( 'redirect_to' );
213
- empty( $sRedirectHref ) ? $oWpResp->redirectHere() : $oWpResp->redirect( rawurldecode( $sRedirectHref ) );
214
  }
215
  else {
216
- $oCon->getAdminNotices()
217
- ->addFlash(
218
- __( 'One or more of your authentication codes failed or was missing.', 'wp-simple-firewall' ),
219
- true
220
- );
221
  // We don't protect against loops here to prevent bypassing of the login intent page.
222
  Services::Response()->redirect( Services::Request()->getUri(), [], true, false );
223
  }
224
  }
225
- elseif ( $oOpts->isUseLoginIntentPage() ) {
226
  $this->getLoginIntentPageHandler()->loadPage();
227
  }
228
  die();
@@ -234,54 +202,40 @@ class MfaController {
234
  */
235
  private function validateLoginIntentRequest() {
236
  try {
237
- $bValid = ( new ValidateLoginIntentRequest() )
238
  ->setMfaController( $this )
239
  ->run();
240
  }
241
- catch ( \Exception $oE ) {
242
- $bValid = true;
243
  }
244
- return $bValid;
245
  }
246
 
247
- /**
248
- * @param \WP_User $oUser
249
- * @return bool
250
- */
251
- private function canUserMfaSkip( $oUser ) {
252
- $bCanSkip = ( new MfaSkip() )
253
  ->setMod( $this->getMod() )
254
- ->canMfaSkip( $oUser );
255
 
256
- if ( !$bCanSkip && $this->getCon()->isPremiumActive() && @class_exists( 'WC_Social_Login' ) ) {
257
  // custom support for WooCommerce Social login
258
- $oMeta = $this->getCon()->getUserMeta( $oUser );
259
- $bCanSkip = isset( $oMeta->wc_social_login_valid ) ? $oMeta->wc_social_login_valid : false;
260
  }
261
 
262
- return apply_filters( 'odp-shield-2fa_skip', $bCanSkip );
 
263
  }
264
 
265
- /**
266
- * @param \WP_User $oUser
267
- * @return bool
268
- */
269
- private function isSubjectToLoginIntent( $oUser ) {
270
- return count( $this->getProvidersForUser( $oUser, true ) ) > 0;
271
  }
272
 
273
- /**
274
- * @return int
275
- */
276
- private function getLoginIntentExpiresAt() {
277
- return $this->getMod()->hasSession() ? $this->getMod()->getSession()->login_intent_expires_at : 0;
278
- }
279
-
280
- /**
281
- * @return bool
282
- */
283
- protected function hasLoginIntent() {
284
- return $this->getLoginIntentExpiresAt() > 0;
285
  }
286
 
287
  /**
@@ -292,25 +246,26 @@ class MfaController {
292
  return $this->setLoginIntentExpiresAt( 0 );
293
  }
294
 
295
- /**
296
- * @param int $nExpirationTime
297
- * @return $this
298
- */
299
- protected function setLoginIntentExpiresAt( $nExpirationTime ) {
300
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $oMod */
301
- $oMod = $this->getMod();
302
- if ( $oMod->hasSession() ) {
303
- /** @var Update $oUpd */
304
- $oUpd = $oMod->getDbHandler_Sessions()->getQueryUpdater();
305
- $oUpd->updateLoginIntentExpiresAt( $oMod->getSession(), $nExpirationTime );
306
  }
307
  return $this;
308
  }
309
 
 
 
 
 
310
  /**
311
- * @return string
 
312
  */
313
- private function getLoginIntentRequestFlag() {
314
- return $this->getCon()->prefix( 'login-intent-request' );
315
  }
316
  }
11
  class MfaController {
12
 
13
  use Shield\Modules\ModConsumer;
14
+ use Shield\Utilities\Consumer\WpLoginCapture;
15
 
16
  /**
17
  * @var Provider\BaseProvider[]
18
  */
19
  private $aProviders;
20
 
 
 
 
 
 
21
  /**
22
  * @var LoginIntentPage
23
  */
25
 
26
  public function run() {
27
  add_action( 'init', [ $this, 'onWpInit' ], 10, 2 );
 
 
 
 
28
  add_action( 'wp_loaded', [ $this, 'onWpLoaded' ], 10, 2 );
29
+ $this->setupLoginCaptureHooks();
30
  }
31
 
32
  public function onWpInit() {
33
+ $user = Services::WpUsers()->getCurrentWpUser();
34
+ if ( $user instanceof \WP_User ) {
35
+ $this->assessLoginIntent( $user );
36
+ }
 
 
 
 
 
37
  }
38
 
39
  public function onWpLoaded() {
46
  } );
47
  }
48
 
49
+ protected function captureLogin( \WP_User $user ) {
50
+ $this->captureLoginIntent( $user );
 
 
 
 
 
 
51
  }
52
 
53
+ private function captureLoginIntent( \WP_User $user ) {
54
+ /** @var LoginGuard\Options $opts */
55
+ $opts = $this->getOptions();
56
+ if ( $this->isSubjectToLoginIntent( $user ) && !$this->canUserMfaSkip( $user ) ) {
57
+
58
+ $providers = $this->getProvidersForUser( $user );
59
+ if ( !empty( $providers ) ) {
60
+ foreach ( $providers as $provider ) {
61
+ $provider->captureLoginAttempt( $user );
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  }
63
+
64
+ $this->setLoginIntentExpiresAt(
65
+ Services::Request()
66
+ ->carbon()
67
+ ->addMinutes( $opts->getLoginIntentMinutes() )->timestamp
68
+ );
69
  }
70
  }
71
  }
72
 
73
+ private function assessLoginIntent( \WP_User $user ) {
74
+ if ( $this->getLoginIntentExpiresAt() > 0 ) {
 
 
 
 
75
 
76
+ if ( $this->isSubjectToLoginIntent( $user ) ) {
77
 
78
  if ( $this->getLoginIntentExpiresAt() > Services::Request()->ts() ) {
79
  $this->processActiveLoginIntent();
91
  }
92
  }
93
 
94
+ private function getLoginIntentPageHandler() :LoginIntentPage {
 
 
 
95
  if ( !isset( $this->oLoginIntentPageHandler ) ) {
96
  $this->oLoginIntentPageHandler = ( new LoginIntentPage() )->setMfaController( $this );
97
  }
101
  /**
102
  * @return Provider\BaseProvider[]
103
  */
104
+ public function getProviders() :array {
105
  if ( !is_array( $this->aProviders ) ) {
106
  $this->aProviders = [
107
  Provider\Email::SLUG => ( new Provider\Email() )->setMod( $this->getMod() ),
116
 
117
  /**
118
  * Ensures that BackupCode provider isn't supplied on its own, and the user profile is setup for each.
119
+ * @param \WP_User $user
120
  * @param bool $bOnlyActiveProfiles
121
  * @return Provider\BaseProvider[]
122
  */
123
+ public function getProvidersForUser( $user, $bOnlyActiveProfiles = false ) {
124
+ $Ps = array_filter( $this->getProviders(),
125
+ function ( $oProvider ) use ( $user, $bOnlyActiveProfiles ) {
126
  /** @var Provider\BaseProvider $oProvider */
127
+ return $oProvider->isProviderAvailableToUser( $user )
128
+ && ( !$bOnlyActiveProfiles || $oProvider->isProfileActive( $user ) );
129
  }
130
  );
131
 
132
  // Neither BackupCode NOR U2F should EVER be the only 1 provider available.
133
+ if ( count( $Ps ) === 1 ) {
134
  /** @var Provider\BaseProvider $oFirst */
135
+ $oFirst = reset( $Ps );
136
  if ( !$oFirst::STANDALONE ) {
137
+ $Ps = [];
138
  }
139
  }
140
+ return $Ps;
141
  }
142
 
143
  /**
144
  * hooked to 'init' and only run if a user is logged-in (not on the login request)
145
  */
146
  private function processActiveLoginIntent() {
147
+ /** @var LoginGuard\Options $opts */
148
+ $opts = $this->getOptions();
149
+ $con = $this->getCon();
150
+ $req = Services::Request();
151
+ $WPResp = Services::Response();
152
+ $WPUsers = Services::WpUsers();
153
 
154
  // Is 2FA/login-intent submit
155
+ if ( $req->request( $this->getLoginIntentRequestFlag() ) == 1 ) {
156
 
157
+ if ( $req->post( 'cancel' ) == 1 ) {
158
+ $WPUsers->logoutUser(); // clears the login and login intent
159
+ $sRedirectHref = $req->post( 'cancel_href' );
160
+ empty( $sRedirectHref ) ? $WPResp->redirectToLogin() : $WPResp->redirect( $sRedirectHref );
161
  }
162
  elseif ( $this->validateLoginIntentRequest() ) {
163
 
164
+ if ( $req->post( 'skip_mfa' ) === 'Y' ) {
165
  ( new MfaSkip() )
166
  ->setMod( $this->getMod() )
167
+ ->addMfaSkip( $WPUsers->getCurrentWpUser() );
168
  }
169
 
170
+ $con->fireEvent( '2fa_success' );
171
 
172
  $sFlash = __( 'Success', 'wp-simple-firewall' ).'! '.__( 'Thank you for authenticating your login.', 'wp-simple-firewall' );
173
+ if ( $opts->isEnabledBackupCodes() ) {
174
  $sFlash .= ' '.__( 'If you used your Backup Code, you will need to reset it.', 'wp-simple-firewall' ); //TODO::
175
  }
176
  $this->getMod()->setFlashAdminNotice( $sFlash );
177
 
178
  $this->removeLoginIntent();
179
 
180
+ $sRedirectHref = $req->post( 'redirect_to' );
181
+ empty( $sRedirectHref ) ? $WPResp->redirectHere() : $WPResp->redirect( rawurldecode( $sRedirectHref ) );
182
  }
183
  else {
184
+ $con->getAdminNotices()
185
+ ->addFlash(
186
+ __( 'One or more of your authentication codes failed or was missing.', 'wp-simple-firewall' ),
187
+ true
188
+ );
189
  // We don't protect against loops here to prevent bypassing of the login intent page.
190
  Services::Response()->redirect( Services::Request()->getUri(), [], true, false );
191
  }
192
  }
193
+ elseif ( $opts->isUseLoginIntentPage() ) {
194
  $this->getLoginIntentPageHandler()->loadPage();
195
  }
196
  die();
202
  */
203
  private function validateLoginIntentRequest() {
204
  try {
205
+ $valid = ( new ValidateLoginIntentRequest() )
206
  ->setMfaController( $this )
207
  ->run();
208
  }
209
+ catch ( \Exception $e ) {
210
+ $valid = true;
211
  }
212
+ return $valid;
213
  }
214
 
215
+ private function canUserMfaSkip( \WP_User $user ) :bool {
216
+ $canSkip = ( new MfaSkip() )
 
 
 
 
217
  ->setMod( $this->getMod() )
218
+ ->canMfaSkip( $user );
219
 
220
+ if ( !$canSkip && $this->getCon()->isPremiumActive() && @class_exists( 'WC_Social_Login' ) ) {
221
  // custom support for WooCommerce Social login
222
+ $meta = $this->getCon()->getUserMeta( $user );
223
+ $canSkip = isset( $meta->wc_social_login_valid ) ? $meta->wc_social_login_valid : false;
224
  }
225
 
226
+ return (bool)apply_filters( 'icwp_shield_2fa_skip',
227
+ apply_filters( 'odp-shield-2fa_skip', $canSkip ) );
228
  }
229
 
230
+ public function isSubjectToLoginIntent( \WP_User $user ) :bool {
231
+ return count( $this->getProvidersForUser( $user, true ) ) > 0;
 
 
 
 
232
  }
233
 
234
+ private function getLoginIntentExpiresAt() :int {
235
+ return $this->getCon()
236
+ ->getModule_Sessions()
237
+ ->getSessionCon()
238
+ ->hasSession() ? (int)$this->getMod()->getSession()->login_intent_expires_at : 0;
 
 
 
 
 
 
 
239
  }
240
 
241
  /**
246
  return $this->setLoginIntentExpiresAt( 0 );
247
  }
248
 
249
+ protected function setLoginIntentExpiresAt( int $expiresAt ) :self {
250
+ $sessMod = $this->getCon()->getModule_Sessions();
251
+ $sessCon = $sessMod->getSessionCon();
252
+ if ( $sessCon->hasSession() ) {
253
+ /** @var Update $upd */
254
+ $upd = $sessMod->getDbHandler_Sessions()->getQueryUpdater();
255
+ $upd->updateLoginIntentExpiresAt( $sessCon->getCurrent(), $expiresAt );
 
 
 
 
256
  }
257
  return $this;
258
  }
259
 
260
+ private function getLoginIntentRequestFlag() :string {
261
+ return $this->getCon()->prefix( 'login-intent-request' );
262
+ }
263
+
264
  /**
265
+ * @return bool
266
+ * @deprecated 10.1
267
  */
268
+ private function hasLoginIntent() :bool {
269
+ return $this->getLoginIntentExpiresAt() > 0;
270
  }
271
  }
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Profiles/CustomForms.php CHANGED
@@ -36,9 +36,9 @@ class CustomForms {
36
  */
37
  private function renderCustomProfileFormOutput( array $aPs ) {
38
  $oMC = $this->getMfaCon();
39
- $oUser = $this->getWpUser();
40
 
41
- $aProviders = $oMC->getProvidersForUser( $oUser, true );
42
  if ( !empty( $aPs ) ) {
43
  $aProviders = array_filter(
44
  $aProviders,
36
  */
37
  private function renderCustomProfileFormOutput( array $aPs ) {
38
  $oMC = $this->getMfaCon();
39
+ $user = $this->getWpUser();
40
 
41
+ $aProviders = $oMC->getProvidersForUser( $user, true );
42
  if ( !empty( $aPs ) ) {
43
  $aProviders = array_filter(
44
  $aProviders,
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/Email.php CHANGED
@@ -142,8 +142,7 @@ class Email extends BaseProvider {
142
  */
143
  private function sendEmailTwoFactorVerify( \WP_User $user ) {
144
  $sureCon = $this->getCon()->getModule_Comms()->getSureSendController();
145
- $useSureSend = $sureCon->isEnabled2Fa()
146
- && $sureCon->canUserSend( $user );
147
 
148
  try {
149
  $code = $this->genNewCode( $user );
142
  */
143
  private function sendEmailTwoFactorVerify( \WP_User $user ) {
144
  $sureCon = $this->getCon()->getModule_Comms()->getSureSendController();
145
+ $useSureSend = $sureCon->isEnabled2Fa() && $sureCon->canUserSend( $user );
 
146
 
147
  try {
148
  $code = $this->genNewCode( $user );
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/ValidateLoginIntentRequest.php CHANGED
@@ -15,53 +15,53 @@ class ValidateLoginIntentRequest {
15
  * @return bool
16
  * @throws \Exception
17
  */
18
- public function run() {
19
  $oMfaCon = $this->getMfaCon();
20
- /** @var LoginGuard\Options $oOpts */
21
- $oOpts = $oMfaCon->getOptions();
22
 
23
- $oUser = Services::WpUsers()->getCurrentWpUser();
24
- if ( !$oUser instanceof \WP_User ) {
25
  throw new \Exception( 'No user logged-in.' );
26
  }
27
- $aProviders = $oMfaCon->getProvidersForUser( $oUser, true );
28
- if ( empty( $aProviders ) ) {
29
  throw new \Exception( 'No valid providers' );
30
  }
31
 
32
  $aSuccessfulProviders = [];
33
 
34
- $bValidated = false;
35
  { // Backup code is special case
36
- if ( isset( $aProviders[ Provider\Backup::SLUG ] ) ) {
37
- if ( $aProviders[ Provider\Backup::SLUG ]->validateLoginIntent( $oUser ) ) {
38
- $bValidated = true;
39
- $aSuccessfulProviders[] = $aProviders[ Provider\Backup::SLUG ];
40
  }
41
- unset( $aProviders[ Provider\Backup::SLUG ] );
42
  }
43
  }
44
 
45
- if ( !$bValidated ) {
46
  $aStates = [];
47
- foreach ( $aProviders as $sSlug => $oProvider ) {
48
- $aStates[ $sSlug ] = $oProvider->validateLoginIntent( $oUser );
49
  if ( $aStates[ $sSlug ] ) {
50
- $aSuccessfulProviders[] = $oProvider;
51
  }
52
  }
53
 
54
  $nSuccessful = count( array_filter( $aStates ) );
55
- $bValidated = $oOpts->isChainedAuth() ? $nSuccessful == count( $aProviders ) : $nSuccessful > 0;
56
  }
57
 
58
- if ( $bValidated ) {
59
  // Some cleanup can only run if login is completely tested and completely valid.
60
- foreach ( $aSuccessfulProviders as $oProvider ) {
61
- $oProvider->postSuccessActions( $oUser );
62
  }
63
  }
64
 
65
- return $bValidated;
66
  }
67
  }
15
  * @return bool
16
  * @throws \Exception
17
  */
18
+ public function run() :bool {
19
  $oMfaCon = $this->getMfaCon();
20
+ /** @var LoginGuard\Options $opts */
21
+ $opts = $oMfaCon->getOptions();
22
 
23
+ $user = Services::WpUsers()->getCurrentWpUser();
24
+ if ( !$user instanceof \WP_User ) {
25
  throw new \Exception( 'No user logged-in.' );
26
  }
27
+ $providers = $oMfaCon->getProvidersForUser( $user, true );
28
+ if ( empty( $providers ) ) {
29
  throw new \Exception( 'No valid providers' );
30
  }
31
 
32
  $aSuccessfulProviders = [];
33
 
34
+ $validated = false;
35
  { // Backup code is special case
36
+ if ( isset( $providers[ Provider\Backup::SLUG ] ) ) {
37
+ if ( $providers[ Provider\Backup::SLUG ]->validateLoginIntent( $user ) ) {
38
+ $validated = true;
39
+ $aSuccessfulProviders[] = $providers[ Provider\Backup::SLUG ];
40
  }
41
+ unset( $providers[ Provider\Backup::SLUG ] );
42
  }
43
  }
44
 
45
+ if ( !$validated ) {
46
  $aStates = [];
47
+ foreach ( $providers as $sSlug => $provider ) {
48
+ $aStates[ $sSlug ] = $provider->validateLoginIntent( $user );
49
  if ( $aStates[ $sSlug ] ) {
50
+ $aSuccessfulProviders[] = $provider;
51
  }
52
  }
53
 
54
  $nSuccessful = count( array_filter( $aStates ) );
55
+ $validated = $opts->isChainedAuth() ? $nSuccessful == count( $providers ) : $nSuccessful > 0;
56
  }
57
 
58
+ if ( $validated ) {
59
  // Some cleanup can only run if login is completely tested and completely valid.
60
+ foreach ( $aSuccessfulProviders as $provider ) {
61
+ $provider->postSuccessActions( $user );
62
  }
63
  }
64
 
65
+ return $validated;
66
  }
67
  }
src/lib/src/Modules/LoginGuard/ModCon.php ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Captcha\CaptchaConfigVO;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+
9
+ class ModCon extends BaseShield\ModCon {
10
+
11
+ /**
12
+ * @var Lib\TwoFactor\MfaController
13
+ */
14
+ private $loginIntentCon;
15
+
16
+ protected function preProcessOptions() {
17
+ /** @var Options $opts */
18
+ $opts = $this->getOptions();
19
+ /**
20
+ * $oWp = $this->loadWpFunctionsProcessor();
21
+ * $sCustomLoginPath = $this->cleanLoginUrlPath();
22
+ * if ( !empty( $sCustomLoginPath ) && $oWp->getIsPermalinksEnabled() ) {
23
+ * $oWp->resavePermalinks();
24
+ * }
25
+ */
26
+ if ( $this->isModuleOptionsRequest() && $opts->isEnabledEmailAuth() && !$opts->getIfCanSendEmailVerified() ) {
27
+ $this->setIfCanSendEmail( false )
28
+ ->sendEmailVerifyCanSend();
29
+ }
30
+
31
+ $aIds = $opts->getOpt( 'antibot_form_ids', [] );
32
+ foreach ( $aIds as $nKey => $sId ) {
33
+ $sId = trim( strip_tags( $sId ) );
34
+ if ( empty( $sId ) ) {
35
+ unset( $aIds[ $nKey ] );
36
+ }
37
+ else {
38
+ $aIds[ $nKey ] = $sId;
39
+ }
40
+ }
41
+ $opts->setOpt( 'antibot_form_ids', array_values( array_unique( $aIds ) ) );
42
+
43
+ $this->cleanLoginUrlPath();
44
+ $this->ensureCorrectCaptchaConfig();
45
+ }
46
+
47
+ public function ensureCorrectCaptchaConfig() {
48
+ /** @var Options $opts */
49
+ $opts = $this->getOptions();
50
+
51
+ $sStyle = $opts->getOpt( 'enable_google_recaptcha_login' );
52
+ if ( $this->isPremium() ) {
53
+ $cfg = $this->getCaptchaCfg();
54
+ if ( $cfg->provider == $cfg::PROV_GOOGLE_RECAP2 ) {
55
+ if ( !$cfg->invisible && $sStyle == 'invisible' ) {
56
+ $opts->setOpt( 'enable_google_recaptcha_login', 'default' );
57
+ }
58
+ }
59
+ }
60
+ elseif ( !in_array( $sStyle, [ 'disabled', 'default' ] ) ) {
61
+ $opts->setOpt( 'enable_google_recaptcha_login', 'default' );
62
+ }
63
+ }
64
+
65
+ protected function handleModAction( string $action ) {
66
+ switch ( $action ) {
67
+ case 'email_send_verify':
68
+ $this->processEmailSendVerify();
69
+ break;
70
+ default:
71
+ break;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * @uses wp_redirect()
77
+ */
78
+ private function processEmailSendVerify() {
79
+ /** @var Options $opts */
80
+ $opts = $this->getOptions();
81
+ $this->setIfCanSendEmail( true );
82
+ $this->saveModOptions();
83
+
84
+ if ( $opts->getIfCanSendEmailVerified() ) {
85
+ $bSuccess = true;
86
+ $sMessage = __( 'Email verification completed successfully.', 'wp-simple-firewall' );
87
+ }
88
+ else {
89
+ $bSuccess = false;
90
+ $sMessage = __( 'Email verification could not be completed.', 'wp-simple-firewall' );
91
+ }
92
+ $this->setFlashAdminNotice( $sMessage, !$bSuccess );
93
+ if ( Services::WpUsers()->isUserLoggedIn() ) {
94
+ Services::Response()->redirect( $this->getUrl_AdminPage() );
95
+ }
96
+ }
97
+
98
+ /**
99
+ * @param string $to
100
+ * @param bool $bSendAsLink
101
+ * @return bool
102
+ */
103
+ public function sendEmailVerifyCanSend( $to = null, $bSendAsLink = true ) {
104
+
105
+ if ( !Services::Data()->validEmail( $to ) ) {
106
+ $to = get_bloginfo( 'admin_email' );
107
+ }
108
+
109
+ $msg = [
110
+ __( 'Before enabling 2-factor email authentication for your WordPress site, you must verify you can receive this email.', 'wp-simple-firewall' ),
111
+ __( 'This verifies your website can send email and that your account can receive emails sent from your site.', 'wp-simple-firewall' ),
112
+ ''
113
+ ];
114
+
115
+ if ( $bSendAsLink ) {
116
+ $msg[] = sprintf(
117
+ __( 'Click the verify link: %s', 'wp-simple-firewall' ),
118
+ add_query_arg( $this->getModActionParams( 'email_send_verify' ), Services::WpGeneral()->getHomeUrl() )
119
+ );
120
+ }
121
+ else {
122
+ $msg[] = sprintf( __( "Here's your code for the guided wizard: %s", 'wp-simple-firewall' ), $this->getCanEmailVerifyCode() );
123
+ }
124
+
125
+ return $this->getEmailProcessor()
126
+ ->sendEmailWithWrap(
127
+ $to,
128
+ __( 'Email Sending Verification', 'wp-simple-firewall' ),
129
+ $msg
130
+ );
131
+ }
132
+
133
+ private function cleanLoginUrlPath() {
134
+ /** @var Options $opts */
135
+ $opts = $this->getOptions();
136
+ $path = $opts->getCustomLoginPath();
137
+ if ( !empty( $path ) ) {
138
+ $path = preg_replace( '#[^0-9a-zA-Z-]#', '', trim( $path, '/' ) );
139
+ $this->getOptions()->setOpt( 'rename_wplogin_path', $path );
140
+ }
141
+ }
142
+
143
+ /**
144
+ * @param bool $bAsOptDefaults
145
+ * @return array
146
+ */
147
+ public function getOptEmailTwoFactorRolesDefaults( $bAsOptDefaults = true ) {
148
+ $aTwoAuthRoles = [
149
+ 'type' => 'multiple_select',
150
+ 0 => __( 'Subscribers', 'wp-simple-firewall' ),
151
+ 1 => __( 'Contributors', 'wp-simple-firewall' ),
152
+ 2 => __( 'Authors', 'wp-simple-firewall' ),
153
+ 3 => __( 'Editors', 'wp-simple-firewall' ),
154
+ 8 => __( 'Administrators', 'wp-simple-firewall' )
155
+ ];
156
+ if ( $bAsOptDefaults ) {
157
+ unset( $aTwoAuthRoles[ 'type' ] );
158
+ unset( $aTwoAuthRoles[ 0 ] );
159
+ return array_keys( $aTwoAuthRoles );
160
+ }
161
+ return $aTwoAuthRoles;
162
+ }
163
+
164
+ public function getGaspKey() :string {
165
+ /** @var Options $opts */
166
+ $opts = $this->getOptions();
167
+ $sKey = $opts->getOpt( 'gasp_key' );
168
+ if ( empty( $sKey ) ) {
169
+ $sKey = uniqid();
170
+ $opts->setOpt( 'gasp_key', $sKey );
171
+ }
172
+ return $this->prefix( $sKey );
173
+ }
174
+
175
+ public function getTextImAHuman() :string {
176
+ return stripslashes( $this->getTextOpt( 'text_imahuman' ) );
177
+ }
178
+
179
+ public function getTextPleaseCheckBox() :string {
180
+ return stripslashes( $this->getTextOpt( 'text_pleasecheckbox' ) );
181
+ }
182
+
183
+ /**
184
+ * @return string
185
+ */
186
+ public function getCanEmailVerifyCode() {
187
+ return strtoupper( substr( $this->getCon()->getSiteInstallationId(), 10, 6 ) );
188
+ }
189
+
190
+ public function isEnabledCaptcha() :bool {
191
+ return !$this->getOptions()->isOpt( 'enable_google_recaptcha_login', 'disabled' )
192
+ && $this->getCaptchaCfg()->ready;
193
+ }
194
+
195
+ public function getCaptchaCfg() :CaptchaConfigVO {
196
+ $cfg = parent::getCaptchaCfg();
197
+ $sStyle = $this->getOptions()->getOpt( 'enable_google_recaptcha_login' );
198
+ if ( $sStyle !== 'default' && $this->isPremium() ) {
199
+ $cfg->theme = $sStyle;
200
+ $cfg->invisible = $cfg->theme == 'invisible';
201
+ }
202
+ return $cfg;
203
+ }
204
+
205
+ /**
206
+ * @return Lib\TwoFactor\MfaController
207
+ */
208
+ public function getLoginIntentController() {
209
+ if ( !isset( $this->loginIntentCon ) ) {
210
+ $this->loginIntentCon = ( new Lib\TwoFactor\MfaController() )
211
+ ->setMod( $this );
212
+ }
213
+ return $this->loginIntentCon;
214
+ }
215
+
216
+ public function setIsChainedAuth( bool $isChained ) {
217
+ $this->getOptions()->setOpt( 'enable_chained_authentication', $isChained ? 'Y' : 'N' );
218
+ }
219
+
220
+ /**
221
+ * @param bool $bCan
222
+ * @return $this
223
+ */
224
+ public function setIfCanSendEmail( $bCan ) {
225
+ $this->getOptions()->setOpt( 'email_can_send_verified_at', $bCan ? Services::Request()->ts() : 0 );
226
+ return $this;
227
+ }
228
+
229
+ public function setEnabled2FaEmail( bool $enable ) {
230
+ $this->getOptions()->setOpt( 'enable_email_authentication', $enable ? 'Y' : 'N' );
231
+ }
232
+
233
+ public function setEnabled2FaGoogleAuthenticator( bool $enable ) {
234
+ $this->getOptions()->setOpt( 'enable_google_authenticator', $enable ? 'Y' : 'N' );
235
+ }
236
+
237
+ /**
238
+ * @return string
239
+ */
240
+ public function getLoginIntentRequestFlag() {
241
+ return $this->getCon()->prefix( 'login-intent-request' );
242
+ }
243
+
244
+ public function getTextOptDefault( string $key ) :string {
245
+
246
+ switch ( $key ) {
247
+ case 'text_imahuman':
248
+ $text = __( "I'm a human.", 'wp-simple-firewall' );
249
+ break;
250
+
251
+ case 'text_pleasecheckbox':
252
+ $text = __( "Please check the box to show us you're a human.", 'wp-simple-firewall' );
253
+ break;
254
+
255
+ default:
256
+ $text = parent::getTextOptDefault( $key );
257
+ break;
258
+ }
259
+ return $text;
260
+ }
261
+
262
+ public function setEnabledGaspCheck( bool $enable ) {
263
+ $this->getOptions()->setOpt( 'enable_login_gasp_check', $enable ? 'Y' : 'N' );
264
+ }
265
+
266
+ public function insertCustomJsVars_Admin() {
267
+ parent::insertCustomJsVars_Admin();
268
+
269
+ wp_localize_script(
270
+ $this->getCon()->prefix( 'global-plugin' ),
271
+ 'icwp_wpsf_vars_lg',
272
+ [
273
+ 'ajax_gen_backup_codes' => $this->getAjaxActionData( 'gen_backup_codes' ),
274
+ 'ajax_del_backup_codes' => $this->getAjaxActionData( 'del_backup_codes' ),
275
+ ]
276
+ );
277
+ wp_enqueue_script( 'jquery-ui-dialog' );
278
+ wp_enqueue_style( 'wp-jquery-ui-dialog' );
279
+ }
280
+ }
src/lib/src/Modules/LoginGuard/Options.php CHANGED
@@ -2,155 +2,98 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
- use FernleafSystems\Wordpress\Services\Services;
7
 
8
- /**
9
- * Class Options
10
- * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard
11
- */
12
- class Options extends Base\ShieldOptions {
13
 
14
- /**
15
- * @return int
16
- */
17
- public function getLoginIntentMinutes() {
18
  return (int)max( 1, apply_filters(
19
  $this->getCon()->prefix( 'login_intent_timeout' ),
20
  $this->getDef( 'login_intent_timeout' )
21
  ) );
22
  }
23
 
24
- /**
25
- * @return array
26
- */
27
- public function getAntiBotFormSelectors() {
28
- $aIds = $this->getOpt( 'antibot_form_ids', [] );
29
- return ( $this->isPremium() && is_array( $aIds ) ) ? $aIds : [];
30
  }
31
 
32
- /**
33
- * @return int
34
- */
35
- public function getCooldownInterval() {
36
  return (int)$this->getOpt( 'login_limit_interval' );
37
  }
38
 
39
- /**
40
- * @return string
41
- */
42
- public function getCustomLoginPath() {
43
- return $this->getOpt( 'rename_wplogin_path', '' );
44
  }
45
 
46
  /**
47
  * @return array
48
  */
49
  public function getEmail2FaRoles() {
50
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $oMod */
51
- $oMod = $this->getMod();
52
- $aRoles = $this->getOpt( 'two_factor_auth_user_roles', [] );
53
- if ( empty( $aRoles ) || !is_array( $aRoles ) ) {
54
- $aRoles = $oMod->getOptEmailTwoFactorRolesDefaults();
55
- $this->setOpt( 'two_factor_auth_user_roles', $aRoles );
56
  }
57
  if ( $this->isPremium() ) {
58
- $aRoles = apply_filters( 'odp-shield-2fa_email_user_roles', $aRoles );
59
  }
60
- return is_array( $aRoles ) ? $aRoles : $oMod->getOptEmailTwoFactorRolesDefaults();
61
  }
62
 
63
- /**
64
- * @return bool
65
- */
66
- public function getIfCanSendEmailVerified() {
67
  return (int)$this->getOpt( 'email_can_send_verified_at' ) > 0;
68
  }
69
 
70
- /**
71
- * @return int - seconds
72
- */
73
- public function getMfaSkip() {
74
  return DAY_IN_SECONDS*( $this->isPremium() ? (int)$this->getOpt( 'mfa_skip', 0 ) : 0 );
75
  }
76
 
77
- /**
78
- * @return string
79
- */
80
- public function getYubikeyAppId() {
81
  return (string)$this->getOpt( 'yubikey_app_id', '' );
82
  }
83
 
84
- /**
85
- * @return bool
86
- */
87
- public function isMfaSkip() {
88
  return $this->getMfaSkip() > 0;
89
  }
90
 
91
- /**
92
- * @return bool
93
- */
94
- public function isChainedAuth() {
95
  return $this->isOpt( 'enable_chained_authentication', 'Y' );
96
  }
97
 
98
- /**
99
- * Also considers whether email sending ability has been verified
100
- * @return bool
101
- */
102
- public function isEmailAuthenticationActive() {
103
  return $this->getIfCanSendEmailVerified() && $this->isEnabledEmailAuth();
104
  }
105
 
106
- /**
107
- * @return bool
108
- */
109
- public function isEnabledEmailAuth() {
110
  return $this->isOpt( 'enable_email_authentication', 'Y' );
111
  }
112
 
113
- /**
114
- * @return bool
115
- */
116
- public function isEnabledCooldown() {
117
  return $this->getCooldownInterval() > 0;
118
  }
119
 
120
- /**
121
- * @return bool
122
- */
123
- public function isEnabledGaspCheck() {
124
  return $this->isOpt( 'enable_login_gasp_check', 'Y' );
125
  }
126
 
127
- /**
128
- * @return bool
129
- */
130
- public function isEnabledEmailAuthAnyUserSet() {
131
- return $this->isEmailAuthenticationActive() && $this->isOpt( 'email_any_user_set', 'Y' ) && $this->isPremium();
132
  }
133
 
134
- /**
135
- * @return bool
136
- */
137
- public function isEnabledBackupCodes() {
138
  return $this->isPremium() && $this->isOpt( 'allow_backupcodes', 'Y' );
139
  }
140
 
141
- /**
142
- * @return bool
143
- */
144
- public function isEnabledGoogleAuthenticator() {
145
  return $this->isOpt( 'enable_google_authenticator', 'Y' );
146
  }
147
 
148
- /**
149
- * @return bool
150
- */
151
- public function isEnabledU2F() {
152
- return Services::Data()->getPhpVersionIsAtLeast( '7.0' )
153
- && $this->isPremium() && $this->isOpt( 'enable_u2f', 'Y' );
154
  }
155
 
156
  /**
@@ -175,34 +118,23 @@ class Options extends Base\ShieldOptions {
175
  }
176
 
177
  /**
178
- * @param string $sLocation - see config for keys, e.g. login, register, password, checkout_woo
179
  * @return bool
180
  */
181
- public function isProtect( $sLocation ) {
182
  $aLocs = $this->getOpt( 'bot_protection_locations' );
183
- return in_array( $sLocation, is_array( $aLocs ) ? $aLocs : $this->getOptDefault( 'bot_protection_locations' ) );
184
  }
185
 
186
- /**
187
- * @return bool
188
- */
189
- public function isUseLoginIntentPage() {
190
  return $this->isOpt( 'use_login_intent_page', true );
191
  }
192
 
193
- /**
194
- * @return bool
195
- */
196
- public function isEnabledYubikey() {
197
  return $this->isOpt( 'enable_yubikey', 'Y' ) && $this->isYubikeyConfigReady();
198
  }
199
 
200
- /**
201
- * @return bool
202
- */
203
- private function isYubikeyConfigReady() {
204
- $sAppId = $this->getOpt( 'yubikey_app_id' );
205
- $sApiKey = $this->getOpt( 'yubikey_api_key' );
206
- return !empty( $sAppId ) && !empty( $sApiKey );
207
  }
208
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
6
 
7
+ class Options extends BaseShield\Options {
 
 
 
 
8
 
9
+ public function getLoginIntentMinutes() :int {
 
 
 
10
  return (int)max( 1, apply_filters(
11
  $this->getCon()->prefix( 'login_intent_timeout' ),
12
  $this->getDef( 'login_intent_timeout' )
13
  ) );
14
  }
15
 
16
+ public function getAntiBotFormSelectors() :array {
17
+ $ids = $this->getOpt( 'antibot_form_ids', [] );
18
+ return ( $this->isPremium() && is_array( $ids ) ) ? $ids : [];
 
 
 
19
  }
20
 
21
+ public function getCooldownInterval() :int {
 
 
 
22
  return (int)$this->getOpt( 'login_limit_interval' );
23
  }
24
 
25
+ public function getCustomLoginPath() :string {
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', [] );
36
+ if ( empty( $roles ) || !is_array( $roles ) ) {
37
+ $roles = $mod->getOptEmailTwoFactorRolesDefaults();
38
+ $this->setOpt( 'two_factor_auth_user_roles', $roles );
39
  }
40
  if ( $this->isPremium() ) {
41
+ $roles = apply_filters( 'odp-shield-2fa_email_user_roles', $roles );
42
  }
43
+ return is_array( $roles ) ? $roles : $mod->getOptEmailTwoFactorRolesDefaults();
44
  }
45
 
46
+ public function getIfCanSendEmailVerified() :bool {
 
 
 
47
  return (int)$this->getOpt( 'email_can_send_verified_at' ) > 0;
48
  }
49
 
50
+ public function getMfaSkip() :int { // seconds
 
 
 
51
  return DAY_IN_SECONDS*( $this->isPremium() ? (int)$this->getOpt( 'mfa_skip', 0 ) : 0 );
52
  }
53
 
54
+ public function getYubikeyAppId() :string {
 
 
 
55
  return (string)$this->getOpt( 'yubikey_app_id', '' );
56
  }
57
 
58
+ public function isMfaSkip() :bool {
 
 
 
59
  return $this->getMfaSkip() > 0;
60
  }
61
 
62
+ public function isChainedAuth() :bool {
 
 
 
63
  return $this->isOpt( 'enable_chained_authentication', 'Y' );
64
  }
65
 
66
+ public function isEmailAuthenticationActive() :bool {
 
 
 
 
67
  return $this->getIfCanSendEmailVerified() && $this->isEnabledEmailAuth();
68
  }
69
 
70
+ public function isEnabledEmailAuth() :bool {
 
 
 
71
  return $this->isOpt( 'enable_email_authentication', 'Y' );
72
  }
73
 
74
+ public function isEnabledCooldown() :bool {
 
 
 
75
  return $this->getCooldownInterval() > 0;
76
  }
77
 
78
+ public function isEnabledGaspCheck() :bool {
 
 
 
79
  return $this->isOpt( 'enable_login_gasp_check', 'Y' );
80
  }
81
 
82
+ public function isEnabledEmailAuthAnyUserSet() :bool {
83
+ return $this->isEmailAuthenticationActive()
84
+ && $this->isOpt( 'email_any_user_set', 'Y' ) && $this->isPremium();
 
 
85
  }
86
 
87
+ public function isEnabledBackupCodes() :bool {
 
 
 
88
  return $this->isPremium() && $this->isOpt( 'allow_backupcodes', 'Y' );
89
  }
90
 
91
+ public function isEnabledGoogleAuthenticator() :bool {
 
 
 
92
  return $this->isOpt( 'enable_google_authenticator', 'Y' );
93
  }
94
 
95
+ public function isEnabledU2F() :bool {
96
+ return $this->isPremium() && $this->isOpt( 'enable_u2f', 'Y' );
 
 
 
 
97
  }
98
 
99
  /**
118
  }
119
 
120
  /**
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 {
 
 
 
130
  return $this->isOpt( 'use_login_intent_page', true );
131
  }
132
 
133
+ public function isEnabledYubikey() :bool {
 
 
 
134
  return $this->isOpt( 'enable_yubikey', 'Y' ) && $this->isYubikeyConfigReady();
135
  }
136
 
137
+ private function isYubikeyConfigReady() :bool {
138
+ return !empty( $this->getOpt( 'yubikey_app_id' ) ) && !empty( $this->getOpt( 'yubikey_api_key' ) );
 
 
 
 
 
139
  }
140
  }
src/lib/src/Modules/LoginGuard/Processor.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class Processor extends BaseShield\Processor {
9
+
10
+ protected function run() {
11
+ /** @var ModCon $mod */
12
+ $mod = $this->getMod();
13
+
14
+ // XML-RPC Compatibility
15
+ if ( Services::WpGeneral()->isXmlrpc() && $mod->isXmlrpcBypass() ) {
16
+ return;
17
+ }
18
+
19
+ ( new Lib\Rename\RenameLogin() )
20
+ ->setMod( $mod )
21
+ ->execute();
22
+
23
+ if ( !$mod->isVisitorWhitelisted() ) {
24
+ ( new Lib\AntiBot\AntibotSetup() )->setMod( $mod );
25
+ $mod->getLoginIntentController()->run();
26
+ }
27
+ }
28
+ }
src/lib/src/Modules/LoginGuard/Strings.php CHANGED
@@ -3,7 +3,6 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
- use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class Strings extends Base\Strings {
9
 
@@ -145,98 +144,98 @@ class Strings extends Base\Strings {
145
  * @throws \Exception
146
  */
147
  public function getOptionStrings( string $key ) :array {
148
- /** @var \ICWP_WPSF_FeatureHandler_LoginProtect $mod */
149
  $mod = $this->getMod();
150
- $sModName = $mod->getMainFeatureName();
151
 
152
  switch ( $key ) {
153
 
154
  case 'enable_login_protect' :
155
- $sName = sprintf( __( 'Enable %s Module', 'wp-simple-firewall' ), $sModName );
156
- $sSummary = sprintf( __( 'Enable (or Disable) The %s Module', 'wp-simple-firewall' ), $sModName );
157
- $sDescription = sprintf( __( 'Un-Checking this option will completely disable the %s module.', 'wp-simple-firewall' ), $sModName );
158
  break;
159
 
160
  case 'rename_wplogin_path' :
161
- $sName = __( 'Hide WP Login Page', 'wp-simple-firewall' );
162
- $sSummary = __( 'Hide The WordPress Login Page', 'wp-simple-firewall' );
163
- $sDescription = __( 'Creating a path here will disable your wp-login.php', 'wp-simple-firewall' )
164
- .'<br />'
165
- .sprintf( __( 'Only letters and numbers are permitted: %s', 'wp-simple-firewall' ), '<strong>abc123</strong>' )
166
- .'<br />'
167
- .sprintf( __( 'Your current login URL is: %s', 'wp-simple-firewall' ), '<br /><strong>&nbsp;&nbsp;'.wp_login_url().'</strong>' );
168
  break;
169
 
170
  case 'enable_chained_authentication' :
171
- $sName = sprintf( __( 'Enable %s', 'wp-simple-firewall' ), __( 'Multi-Factor Authentication', 'wp-simple-firewall' ) );
172
- $sSummary = __( 'Require All Active Authentication Factors', 'wp-simple-firewall' );
173
- $sDescription = __( 'When enabled, all multi-factor authentication methods will be applied to a user login. Disable to require only one to login.', 'wp-simple-firewall' );
174
  break;
175
 
176
  case 'mfa_skip' :
177
- $sName = __( 'Multi-Factor Bypass', 'wp-simple-firewall' );
178
- $sSummary = __( 'A User Can Bypass Multi-Factor Authentication (MFA) For The Set Number Of Days', 'wp-simple-firewall' );
179
- $sDescription = __( 'Enter the number of days a user can bypass future MFA after a successful MFA-login. 0 to disable.', 'wp-simple-firewall' );
180
  break;
181
 
182
  case 'allow_backupcodes' :
183
- $sName = __( 'Allow Backup Codes', 'wp-simple-firewall' );
184
- $sSummary = __( 'Allow Users To Generate A Backup Code', 'wp-simple-firewall' );
185
- $sDescription = __( 'Allow users to generate a backup code that can be used to login if MFA factors are unavailable.', 'wp-simple-firewall' );
186
  break;
187
 
188
  case 'enable_google_authenticator' :
189
- $sName = sprintf( __( 'Enable %s', 'wp-simple-firewall' ), __( 'Google Authenticator', 'wp-simple-firewall' ) );
190
- $sSummary = __( 'Allow Users To Use Google Authenticator', 'wp-simple-firewall' );
191
- $sDescription = __( 'When enabled, users will have the option to add Google Authenticator to their WordPress user profile', 'wp-simple-firewall' );
192
  break;
193
 
194
  case 'enable_email_authentication' :
195
- $sName = sprintf( __( 'Enable %s', 'wp-simple-firewall' ), __( 'Email Authentication', 'wp-simple-firewall' ) );
196
- $sSummary = sprintf( __( 'Two-Factor Login Authentication By %s', 'wp-simple-firewall' ), __( 'Email', 'wp-simple-firewall' ) );
197
- $sDescription = __( 'All users will be required to verify their login by email-based two-factor authentication.', 'wp-simple-firewall' );
198
  break;
199
 
200
  case 'email_any_user_set' :
201
- $sName = __( 'Allow Any User', 'wp-simple-firewall' );
202
- $sSummary = __( 'Allow Any User To Turn-On Two-Factor Authentication By Email.', 'wp-simple-firewall' );
203
- $sDescription = __( 'Any user can turn on two-factor authentication by email from their profile.', 'wp-simple-firewall' );
204
  break;
205
 
206
  case 'two_factor_auth_user_roles' :
207
- $sName = sprintf( '%s - %s', __( 'Enforce', 'wp-simple-firewall' ), __( 'Email Authentication', 'wp-simple-firewall' ) );
208
- $sSummary = __( 'All User Roles Subject To Email Authentication', 'wp-simple-firewall' );
209
- $sDescription = __( 'Enforces email-based authentication on all users with the selected roles.', 'wp-simple-firewall' )
210
- .'<br /><strong>'.sprintf( '%s: %s', __( 'Note', 'wp-simple-firewall' ), sprintf( __( 'This setting only applies to %s.', 'wp-simple-firewall' ), __( 'Email Authentication', 'wp-simple-firewall' ) ) ).'</strong>';
211
  break;
212
 
213
  case 'enable_google_recaptcha_login' :
214
- $sName = __( 'CAPTCHA', 'wp-simple-firewall' );
215
- $sSummary = __( 'Protect WordPress Account Access Requests With CAPTCHA', 'wp-simple-firewall' );
216
- $sDescription = __( 'Use CAPTCHA on the user account forms such as login, register, etc.', 'wp-simple-firewall' ).'<br />'
217
- .sprintf( __( 'Use of any theme other than "%s", requires a Pro license.', 'wp-simple-firewall' ), __( 'Light Theme', 'wp-simple-firewall' ) )
218
- .'<br/>'.sprintf( '%s - %s', __( 'Note', 'wp-simple-firewall' ), __( "You'll need to setup your CAPTCHA API Keys in 'General' settings.", 'wp-simple-firewall' ) )
219
- .'<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>';
220
  break;
221
 
222
  case 'bot_protection_locations' :
223
- $sName = __( 'Protection Locations', 'wp-simple-firewall' );
224
- $sSummary = __( 'Which Forms Should Be Protected', 'wp-simple-firewall' );
225
- $sDescription = __( 'Choose the forms for which bot protection measures will be deployed.', 'wp-simple-firewall' ).'<br />'
226
- .sprintf( '%s - %s', __( 'Note', 'wp-simple-firewall' ), sprintf( __( "Use with 3rd party systems such as %s, requires a Pro license.", 'wp-simple-firewall' ), 'WooCommerce' ) );
227
  break;
228
 
229
  case 'enable_login_gasp_check' :
230
- $sName = __( 'Bot Protection', 'wp-simple-firewall' );
231
- $sSummary = __( 'Protect WP Login From Automated Login Attempts By Bots', 'wp-simple-firewall' );
232
- $sDescription = __( 'Adds a dynamically (Javascript) generated checkbox to the login form that prevents bots using automated login techniques.', 'wp-simple-firewall' )
233
- .'<br />'.sprintf( '%s: %s', __( 'Recommendation', 'wp-simple-firewall' ), __( 'ON', 'wp-simple-firewall' ) );
234
  break;
235
 
236
  case 'antibot_form_ids' :
237
- $sName = __( 'AntiBot Forms', 'wp-simple-firewall' );
238
- $sSummary = __( 'Enter The Selectors Of The 3rd Party Login Forms For Use With AntiBot JS', 'wp-simple-firewall' );
239
- $sDescription = [
240
  __( 'Provide DOM selectors to attached AntiBot protection to any form.', 'wp-simple-firewall' ),
241
  __( 'IDs are prefixed with "#".', 'wp-simple-firewall' ),
242
  __( 'Classes are prefixed with ".".', 'wp-simple-firewall' ),
@@ -245,83 +244,80 @@ class Strings extends Base\Strings {
245
  break;
246
 
247
  case 'login_limit_interval' :
248
- $sName = __( 'Cooldown Period', 'wp-simple-firewall' );
249
- $sSummary = __( 'Limit account access requests to every X seconds', 'wp-simple-firewall' );
250
- $sDescription = __( 'WordPress will process only ONE account access attempt per number of seconds specified.', 'wp-simple-firewall' )
251
- .'<br />'.__( 'Zero (0) turns this off.', 'wp-simple-firewall' )
252
- .' '.sprintf( '%s: %s', __( 'Default', 'wp-simple-firewall' ), $this->getOptions()
253
- ->getOptDefault( 'login_limit_interval' ) );
254
  break;
255
 
256
  case 'enable_user_register_checking' :
257
- $sName = __( 'User Registration', 'wp-simple-firewall' );
258
- $sSummary = __( 'Apply Brute Force Protection To User Registration And Lost Passwords', 'wp-simple-firewall' );
259
- $sDescription = __( 'When enabled, settings in this section will also apply to new user registration and users trying to reset passwords.', 'wp-simple-firewall' );
260
  break;
261
 
262
  case 'enable_u2f' :
263
- $sName = __( 'Allow U2F', 'wp-simple-firewall' );
264
- $sSummary = __( 'Allow Registration Of U2F Devices', 'wp-simple-firewall' );
265
- $sDescription = [
266
  __( 'Allow users to register U2F devices to complete their login.', 'wp-simple-firewall' ),
267
  __( "Currently only U2F keys are supported. Built-in fingerprint scanners aren't supported (yet).", 'wp-simple-firewall' ),
268
  __( "Beta! This may only be used when at least 1 other 2FA option is enabled on a user account.", 'wp-simple-firewall' ),
269
  ];
270
- if ( !Services::Data()->getPhpVersionIsAtLeast( '7.0' ) ) {
271
- $sDescription[] = sprintf( '%s - %s', __( 'Important', 'wp-simple-firewall' ), __( "Requires PHP 7.0 or later.", 'wp-simple-firewall' ) );
272
- }
273
  break;
274
 
275
  case 'enable_yubikey' :
276
- $sName = __( 'Allow Yubikey OTP', 'wp-simple-firewall' );
277
- $sSummary = __( 'Allow Yubikey Registration For One Time Passwords', 'wp-simple-firewall' );
278
- $sDescription = __( 'Combined with your Yubikey API details this will form the basis of your Yubikey Authentication', 'wp-simple-firewall' );
279
  break;
280
 
281
  case 'yubikey_app_id' :
282
- $sName = __( 'Yubikey App ID', 'wp-simple-firewall' );
283
- $sSummary = __( 'Your Unique Yubikey App ID', 'wp-simple-firewall' );
284
- $sDescription = [
285
  __( 'Combined with your Yubikey API Key this will form the basis of your Yubikey Authentication', 'wp-simple-firewall' ),
286
  __( 'Please review the info link on how to obtain your own Yubikey App ID and API Key.', 'wp-simple-firewall' )
287
  ];
288
  break;
289
 
290
  case 'yubikey_api_key' :
291
- $sName = __( 'Yubikey API Key', 'wp-simple-firewall' );
292
- $sSummary = __( 'Your Unique Yubikey App API Key', 'wp-simple-firewall' );
293
- $sDescription = __( 'Combined with your Yubikey App ID this will form the basis of your Yubikey Authentication.', 'wp-simple-firewall' )
294
- .'<br />'.__( 'Please review the info link on how to get your own Yubikey App ID and API Key.', 'wp-simple-firewall' );
295
  break;
296
 
297
  case 'yubikey_unique_keys' :
298
- $sName = __( 'Yubikey Unique Keys', 'wp-simple-firewall' );
299
- $sSummary = __( 'This method for Yubikeys is no longer supported. Please see your user profile', 'wp-simple-firewall' );
300
- $sDescription = '<strong>'.sprintf( '%s: %s', __( 'Format', 'wp-simple-firewall' ), 'Username,Yubikey' ).'</strong>'
301
- .'<br />- '.__( 'Provide Username<->Yubikey Pairs that are usable for this site.', 'wp-simple-firewall' )
302
- .'<br />- '.__( 'If a Username is not assigned a Yubikey, Yubikey Authentication is OFF for that user.', 'wp-simple-firewall' )
303
- .'<br />- '.__( 'Each [Username,Key] pair should be separated by a new line: you only need to provide the first 12 characters of the yubikey.', 'wp-simple-firewall' );
304
  break;
305
 
306
  case 'text_imahuman' :
307
- $sName = __( 'GASP Checkbox Text', 'wp-simple-firewall' );
308
- $sSummary = __( 'The User Message Displayed Next To The GASP Checkbox', 'wp-simple-firewall' );
309
- $sDescription = __( "You can change the text displayed to the user beside the checkbox if you need a custom message.", 'wp-simple-firewall' )
310
- .'<br />'.sprintf( '%s: %s', __( 'Default', 'wp-simple-firewall' ), $mod->getTextOptDefault( 'text_imahuman' ) );
311
  break;
312
 
313
  case 'text_pleasecheckbox' :
314
- $sName = __( 'GASP Alert Text', 'wp-simple-firewall' );
315
- $sSummary = __( "The Message Displayed If The User Doesn't Check The Box", 'wp-simple-firewall' );
316
- $sDescription = __( "You can change the text displayed to the user in the alert message if they don't check the box.", 'wp-simple-firewall' )
317
- .'<br />'.sprintf( '%s: %s', __( 'Default', 'wp-simple-firewall' ), $mod->getTextOptDefault( 'text_pleasecheckbox' ) );
318
  break;
319
 
320
  // removed 9.0
321
  case 'enable_antibot_js' :
322
- $sName = __( 'AntiBot JS', 'wp-simple-firewall' );
323
- $sSummary = __( 'Use AntiBot JS Includes For Custom 3rd Party Forms', 'wp-simple-firewall' );
324
- $sDescription = __( 'Important: This is experimental. Please contact support for further assistance.', 'wp-simple-firewall' );
325
  break;
326
 
327
  default:
@@ -329,9 +325,9 @@ class Strings extends Base\Strings {
329
  }
330
 
331
  return [
332
- 'name' => $sName,
333
- 'summary' => $sSummary,
334
- 'description' => $sDescription,
335
  ];
336
  }
337
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
 
6
 
7
  class Strings extends Base\Strings {
8
 
144
  * @throws \Exception
145
  */
146
  public function getOptionStrings( string $key ) :array {
147
+ /** @var ModCon $mod */
148
  $mod = $this->getMod();
149
+ $modName = $mod->getMainFeatureName();
150
 
151
  switch ( $key ) {
152
 
153
  case 'enable_login_protect' :
154
+ $name = sprintf( __( 'Enable %s Module', 'wp-simple-firewall' ), $modName );
155
+ $summary = sprintf( __( 'Enable (or Disable) The %s Module', 'wp-simple-firewall' ), $modName );
156
+ $desc = sprintf( __( 'Un-Checking this option will completely disable the %s module.', 'wp-simple-firewall' ), $modName );
157
  break;
158
 
159
  case 'rename_wplogin_path' :
160
+ $name = __( 'Hide WP Login Page', 'wp-simple-firewall' );
161
+ $summary = __( 'Hide The WordPress Login Page', 'wp-simple-firewall' );
162
+ $desc = __( 'Creating a path here will disable your wp-login.php', 'wp-simple-firewall' )
163
+ .'<br />'
164
+ .sprintf( __( 'Only letters and numbers are permitted: %s', 'wp-simple-firewall' ), '<strong>abc123</strong>' )
165
+ .'<br />'
166
+ .sprintf( __( 'Your current login URL is: %s', 'wp-simple-firewall' ), '<br /><strong>&nbsp;&nbsp;'.wp_login_url().'</strong>' );
167
  break;
168
 
169
  case 'enable_chained_authentication' :
170
+ $name = sprintf( __( 'Enable %s', 'wp-simple-firewall' ), __( 'Multi-Factor Authentication', 'wp-simple-firewall' ) );
171
+ $summary = __( 'Require All Active Authentication Factors', 'wp-simple-firewall' );
172
+ $desc = __( 'When enabled, all multi-factor authentication methods will be applied to a user login. Disable to require only one to login.', 'wp-simple-firewall' );
173
  break;
174
 
175
  case 'mfa_skip' :
176
+ $name = __( 'Multi-Factor Bypass', 'wp-simple-firewall' );
177
+ $summary = __( 'A User Can Bypass Multi-Factor Authentication (MFA) For The Set Number Of Days', 'wp-simple-firewall' );
178
+ $desc = __( 'Enter the number of days a user can bypass future MFA after a successful MFA-login. 0 to disable.', 'wp-simple-firewall' );
179
  break;
180
 
181
  case 'allow_backupcodes' :
182
+ $name = __( 'Allow Backup Codes', 'wp-simple-firewall' );
183
+ $summary = __( 'Allow Users To Generate A Backup Code', 'wp-simple-firewall' );
184
+ $desc = __( 'Allow users to generate a backup code that can be used to login if MFA factors are unavailable.', 'wp-simple-firewall' );
185
  break;
186
 
187
  case 'enable_google_authenticator' :
188
+ $name = sprintf( __( 'Enable %s', 'wp-simple-firewall' ), __( 'Google Authenticator', 'wp-simple-firewall' ) );
189
+ $summary = __( 'Allow Users To Use Google Authenticator', 'wp-simple-firewall' );
190
+ $desc = __( 'When enabled, users will have the option to add Google Authenticator to their WordPress user profile', 'wp-simple-firewall' );
191
  break;
192
 
193
  case 'enable_email_authentication' :
194
+ $name = sprintf( __( 'Enable %s', 'wp-simple-firewall' ), __( 'Email Authentication', 'wp-simple-firewall' ) );
195
+ $summary = sprintf( __( 'Two-Factor Login Authentication By %s', 'wp-simple-firewall' ), __( 'Email', 'wp-simple-firewall' ) );
196
+ $desc = __( 'All users will be required to verify their login by email-based two-factor authentication.', 'wp-simple-firewall' );
197
  break;
198
 
199
  case 'email_any_user_set' :
200
+ $name = __( 'Allow Any User', 'wp-simple-firewall' );
201
+ $summary = __( 'Allow Any User To Turn-On Two-Factor Authentication By Email.', 'wp-simple-firewall' );
202
+ $desc = __( 'Any user can turn on two-factor authentication by email from their profile.', 'wp-simple-firewall' );
203
  break;
204
 
205
  case 'two_factor_auth_user_roles' :
206
+ $name = sprintf( '%s - %s', __( 'Enforce', 'wp-simple-firewall' ), __( 'Email Authentication', 'wp-simple-firewall' ) );
207
+ $summary = __( 'All User Roles Subject To Email Authentication', 'wp-simple-firewall' );
208
+ $desc = __( 'Enforces email-based authentication on all users with the selected roles.', 'wp-simple-firewall' )
209
+ .'<br /><strong>'.sprintf( '%s: %s', __( 'Note', 'wp-simple-firewall' ), sprintf( __( 'This setting only applies to %s.', 'wp-simple-firewall' ), __( 'Email Authentication', 'wp-simple-firewall' ) ) ).'</strong>';
210
  break;
211
 
212
  case 'enable_google_recaptcha_login' :
213
+ $name = __( 'CAPTCHA', 'wp-simple-firewall' );
214
+ $summary = __( 'Protect WordPress Account Access Requests With CAPTCHA', 'wp-simple-firewall' );
215
+ $desc = __( 'Use CAPTCHA on the user account forms such as login, register, etc.', 'wp-simple-firewall' ).'<br />'
216
+ .sprintf( __( 'Use of any theme other than "%s", requires a Pro license.', 'wp-simple-firewall' ), __( 'Light Theme', 'wp-simple-firewall' ) )
217
+ .'<br/>'.sprintf( '%s - %s', __( 'Note', 'wp-simple-firewall' ), __( "You'll need to setup your CAPTCHA API Keys in 'General' settings.", '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 'bot_protection_locations' :
222
+ $name = __( 'Protection Locations', 'wp-simple-firewall' );
223
+ $summary = __( 'Which Forms Should Be Protected', 'wp-simple-firewall' );
224
+ $desc = __( 'Choose the forms for which bot protection measures will be deployed.', 'wp-simple-firewall' ).'<br />'
225
+ .sprintf( '%s - %s', __( 'Note', 'wp-simple-firewall' ), sprintf( __( "Use with 3rd party systems such as %s, requires a Pro license.", 'wp-simple-firewall' ), 'WooCommerce' ) );
226
  break;
227
 
228
  case 'enable_login_gasp_check' :
229
+ $name = __( 'Bot Protection', 'wp-simple-firewall' );
230
+ $summary = __( 'Protect WP Login From Automated Login Attempts By Bots', 'wp-simple-firewall' );
231
+ $desc = __( 'Adds a dynamically (Javascript) generated checkbox to the login form that prevents bots using automated login techniques.', 'wp-simple-firewall' )
232
+ .'<br />'.sprintf( '%s: %s', __( 'Recommendation', 'wp-simple-firewall' ), __( 'ON', 'wp-simple-firewall' ) );
233
  break;
234
 
235
  case 'antibot_form_ids' :
236
+ $name = __( 'AntiBot Forms', 'wp-simple-firewall' );
237
+ $summary = __( 'Enter The Selectors Of The 3rd Party Login Forms For Use With AntiBot JS', 'wp-simple-firewall' );
238
+ $desc = [
239
  __( 'Provide DOM selectors to attached AntiBot protection to any form.', 'wp-simple-firewall' ),
240
  __( 'IDs are prefixed with "#".', 'wp-simple-firewall' ),
241
  __( 'Classes are prefixed with ".".', 'wp-simple-firewall' ),
244
  break;
245
 
246
  case 'login_limit_interval' :
247
+ $name = __( 'Cooldown Period', 'wp-simple-firewall' );
248
+ $summary = __( 'Limit account access requests to every X seconds', 'wp-simple-firewall' );
249
+ $desc = __( 'WordPress will process only ONE account access attempt per number of seconds specified.', 'wp-simple-firewall' )
250
+ .'<br />'.__( 'Zero (0) turns this off.', 'wp-simple-firewall' )
251
+ .' '.sprintf( '%s: %s', __( 'Default', 'wp-simple-firewall' ), $this->getOptions()
252
+ ->getOptDefault( 'login_limit_interval' ) );
253
  break;
254
 
255
  case 'enable_user_register_checking' :
256
+ $name = __( 'User Registration', 'wp-simple-firewall' );
257
+ $summary = __( 'Apply Brute Force Protection To User Registration And Lost Passwords', 'wp-simple-firewall' );
258
+ $desc = __( 'When enabled, settings in this section will also apply to new user registration and users trying to reset passwords.', 'wp-simple-firewall' );
259
  break;
260
 
261
  case 'enable_u2f' :
262
+ $name = __( 'Allow U2F', 'wp-simple-firewall' );
263
+ $summary = __( 'Allow Registration Of U2F Devices', 'wp-simple-firewall' );
264
+ $desc = [
265
  __( 'Allow users to register U2F devices to complete their login.', 'wp-simple-firewall' ),
266
  __( "Currently only U2F keys are supported. Built-in fingerprint scanners aren't supported (yet).", 'wp-simple-firewall' ),
267
  __( "Beta! This may only be used when at least 1 other 2FA option is enabled on a user account.", 'wp-simple-firewall' ),
268
  ];
 
 
 
269
  break;
270
 
271
  case 'enable_yubikey' :
272
+ $name = __( 'Allow Yubikey OTP', 'wp-simple-firewall' );
273
+ $summary = __( 'Allow Yubikey Registration For One Time Passwords', 'wp-simple-firewall' );
274
+ $desc = __( 'Combined with your Yubikey API details this will form the basis of your Yubikey Authentication', 'wp-simple-firewall' );
275
  break;
276
 
277
  case 'yubikey_app_id' :
278
+ $name = __( 'Yubikey App ID', 'wp-simple-firewall' );
279
+ $summary = __( 'Your Unique Yubikey App ID', 'wp-simple-firewall' );
280
+ $desc = [
281
  __( 'Combined with your Yubikey API Key this will form the basis of your Yubikey Authentication', 'wp-simple-firewall' ),
282
  __( 'Please review the info link on how to obtain your own Yubikey App ID and API Key.', 'wp-simple-firewall' )
283
  ];
284
  break;
285
 
286
  case 'yubikey_api_key' :
287
+ $name = __( 'Yubikey API Key', 'wp-simple-firewall' );
288
+ $summary = __( 'Your Unique Yubikey App API Key', 'wp-simple-firewall' );
289
+ $desc = __( 'Combined with your Yubikey App ID this will form the basis of your Yubikey Authentication.', 'wp-simple-firewall' )
290
+ .'<br />'.__( 'Please review the info link on how to get your own Yubikey App ID and API Key.', 'wp-simple-firewall' );
291
  break;
292
 
293
  case 'yubikey_unique_keys' :
294
+ $name = __( 'Yubikey Unique Keys', 'wp-simple-firewall' );
295
+ $summary = __( 'This method for Yubikeys is no longer supported. Please see your user profile', 'wp-simple-firewall' );
296
+ $desc = '<strong>'.sprintf( '%s: %s', __( 'Format', 'wp-simple-firewall' ), 'Username,Yubikey' ).'</strong>'
297
+ .'<br />- '.__( 'Provide Username<->Yubikey Pairs that are usable for this site.', 'wp-simple-firewall' )
298
+ .'<br />- '.__( 'If a Username is not assigned a Yubikey, Yubikey Authentication is OFF for that user.', 'wp-simple-firewall' )
299
+ .'<br />- '.__( 'Each [Username,Key] pair should be separated by a new line: you only need to provide the first 12 characters of the yubikey.', 'wp-simple-firewall' );
300
  break;
301
 
302
  case 'text_imahuman' :
303
+ $name = __( 'GASP Checkbox Text', 'wp-simple-firewall' );
304
+ $summary = __( 'The User Message Displayed Next To The GASP Checkbox', 'wp-simple-firewall' );
305
+ $desc = __( "You can change the text displayed to the user beside the checkbox if you need a custom message.", 'wp-simple-firewall' )
306
+ .'<br />'.sprintf( '%s: %s', __( 'Default', 'wp-simple-firewall' ), $mod->getTextOptDefault( 'text_imahuman' ) );
307
  break;
308
 
309
  case 'text_pleasecheckbox' :
310
+ $name = __( 'GASP Alert Text', 'wp-simple-firewall' );
311
+ $summary = __( "The Message Displayed If The User Doesn't Check The Box", 'wp-simple-firewall' );
312
+ $desc = __( "You can change the text displayed to the user in the alert message if they don't check the box.", 'wp-simple-firewall' )
313
+ .'<br />'.sprintf( '%s: %s', __( 'Default', 'wp-simple-firewall' ), $mod->getTextOptDefault( 'text_pleasecheckbox' ) );
314
  break;
315
 
316
  // removed 9.0
317
  case 'enable_antibot_js' :
318
+ $name = __( 'AntiBot JS', 'wp-simple-firewall' );
319
+ $summary = __( 'Use AntiBot JS Includes For Custom 3rd Party Forms', 'wp-simple-firewall' );
320
+ $desc = __( 'Important: This is experimental. Please contact support for further assistance.', 'wp-simple-firewall' );
321
  break;
322
 
323
  default:
325
  }
326
 
327
  return [
328
+ 'name' => $name,
329
+ 'summary' => $summary,
330
+ 'description' => $desc,
331
  ];
332
  }
333
  }
src/lib/src/Modules/LoginGuard/UI.php CHANGED
@@ -2,9 +2,9 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
 
7
- class UI extends Base\ShieldUI {
8
 
9
  protected function getSectionWarnings( string $section ) :array {
10
  $aWarnings = [];
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
 
7
+ class UI extends BaseShield\UI {
8
 
9
  protected function getSectionWarnings( string $section ) :array {
10
  $aWarnings = [];
src/lib/src/Modules/ModConsumer.php CHANGED
@@ -24,7 +24,7 @@ trait ModConsumer {
24
  }
25
 
26
  /**
27
- * @return \ICWP_WPSF_FeatureHandler_Base|Modules\Base\BaseModCon
28
  */
29
  public function getMod() {
30
  return $this->oMod;
@@ -47,7 +47,7 @@ trait ModConsumer {
47
  }
48
 
49
  /**
50
- * @param \ICWP_WPSF_FeatureHandler_Base|Modules\Base\BaseModCon $oMod
51
  * @return $this
52
  */
53
  public function setMod( $oMod ) {
24
  }
25
 
26
  /**
27
+ * @return \ICWP_WPSF_FeatureHandler_Base|Modules\Base\ModCon
28
  */
29
  public function getMod() {
30
  return $this->oMod;
47
  }
48
 
49
  /**
50
+ * @param \ICWP_WPSF_FeatureHandler_Base|Modules\Base\ModCon $oMod
51
  * @return $this
52
  */
53
  public function setMod( $oMod ) {
src/lib/src/Modules/Plugin/AdminNotices.php CHANGED
@@ -4,102 +4,88 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
 
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class AdminNotices extends Shield\Modules\Base\AdminNotices {
10
 
11
  /**
12
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
13
- * @throws \Exception
14
  */
15
- protected function processNotice( $oNotice ) {
16
 
17
- switch ( $oNotice->id ) {
18
 
19
  case 'php7':
20
- $this->buildNotice_Php7( $oNotice );
21
  break;
22
 
23
  case 'override-forceoff':
24
- $this->buildNotice_OverrideForceoff( $oNotice );
25
  break;
26
 
27
  case 'plugin-disabled':
28
- $this->buildNotice_PluginDisabled( $oNotice );
29
  break;
30
 
31
  case 'update-available':
32
- $this->buildNotice_UpdateAvailable( $oNotice );
33
  break;
34
 
35
  case 'compat-sgoptimize':
36
- $this->buildNotice_CompatSgOptimize( $oNotice );
37
- break;
38
-
39
- case 'cloudflare-apo':
40
- $this->buildNotice_CloudflareAPO( $oNotice );
41
  break;
42
 
43
  case 'plugin-mailing-list-signup':
44
- $this->buildNotice_PluginMailingListSignup( $oNotice );
45
  break;
46
 
47
  case 'wizard_welcome':
48
- $this->buildNotice_WelcomeWizard( $oNotice );
49
  break;
50
 
51
  case 'allow-tracking':
52
- $this->buildNotice_AllowTracking( $oNotice );
53
  break;
54
 
55
  case 'rate-plugin':
56
- $this->buildNotice_RatePlugin( $oNotice );
57
  break;
58
 
59
  default:
60
- parent::processNotice( $oNotice );
61
  break;
62
  }
63
  }
64
 
65
- /**
66
- * @param array $aAjaxResponse
67
- * @return array
68
- */
69
- public function handleAuthAjax( $aAjaxResponse ) {
70
 
71
- if ( empty( $aAjaxResponse ) ) {
72
  switch ( Services::Request()->request( 'exec' ) ) {
73
 
74
  case 'set_plugin_tracking':
75
- $aAjaxResponse = $this->ajaxExec_SetPluginTrackingPerm();
76
  break;
77
 
78
  default:
79
- $aAjaxResponse = parent::handleAuthAjax( $aAjaxResponse );
80
  break;
81
  }
82
  }
83
- return $aAjaxResponse;
84
  }
85
 
86
- /**
87
- * @return array
88
- */
89
- private function ajaxExec_SetPluginTrackingPerm() {
90
- /** @var Options $oOpts */
91
- $oOpts = $this->getOptions();
92
- $oOpts->setPluginTrackingPermission( (bool)Services::Request()->query( 'agree', false ) );
93
  return $this->ajaxExec_DismissAdminNotice();
94
  }
95
 
96
- /**
97
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
98
- */
99
- private function buildNotice_Php7( $oNotice ) {
100
  $sName = $this->getCon()->getHumanName();
101
 
102
- $oNotice->render_data = [
103
  'notice_attributes' => [],
104
  'strings' => [
105
  'title' => sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ),
@@ -120,13 +106,10 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
120
  ];
121
  }
122
 
123
- /**
124
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
125
- */
126
- private function buildNotice_OverrideForceoff( $oNotice ) {
127
  $sName = $this->getCon()->getHumanName();
128
 
129
- $oNotice->render_data = [
130
  'notice_attributes' => [],
131
  'strings' => [
132
  'title' => sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ), sprintf( __( '%s is not protecting your site', 'wp-simple-firewall' ), $sName ) ),
@@ -143,16 +126,13 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
143
  ];
144
  }
145
 
146
- /**
147
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
148
- */
149
- private function buildNotice_PluginDisabled( $oNotice ) {
150
- $sName = $this->getCon()->getHumanName();
151
 
152
- $oNotice->render_data = [
153
  'notice_attributes' => [],
154
  'strings' => [
155
- 'title' => sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ), sprintf( __( '%s is not protecting your site', 'wp-simple-firewall' ), $sName ) ),
156
  'message' => implode( ' ', [
157
  __( 'The plugin is currently switched-off completely.', 'wp-simple-firewall' ),
158
  __( 'All features and any security protection they provide are disabled.', 'wp-simple-firewall' ),
@@ -165,13 +145,10 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
165
  ];
166
  }
167
 
168
- /**
169
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
170
- */
171
- private function buildNotice_CompatSgOptimize( $oNotice ) {
172
  $sName = $this->getCon()->getHumanName();
173
 
174
- $oNotice->render_data = [
175
  'notice_attributes' => [],
176
  'strings' => [
177
  'title' => sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ),
@@ -190,34 +167,14 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
190
  ];
191
  }
192
 
193
- /**
194
- * @param Shield\Utilities\AdminNotices\NoticeVO $notice
195
- */
196
- private function buildNotice_CloudflareAPO( $notice ) {
197
- $notice->render_data = [
198
- 'notice_attributes' => [],
199
- 'strings' => [
200
- 'title' => sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ),
201
- __( "CloudFlare APO Conflict/Bug", 'wp-simple-firewall' ) ),
202
- 'message' => [
203
- __( "CloudFlare's Automatic Platform Optimisation for WordPress breaks the ability to correctly detect visitor IP addresses.", 'wp-simple-firewall' ),
204
- __( 'Until they fix this, please switch off APO for this domain on your CloudFlare control panel.', 'wp-simple-firewall' ),
205
- ],
206
- ],
207
- ];
208
- }
209
-
210
- /**
211
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
212
- */
213
- private function buildNotice_PluginMailingListSignup( $oNotice ) {
214
  /** @var Options $oOpts */
215
  $oOpts = $this->getOptions();
216
 
217
  $sName = $this->getCon()->getHumanName();
218
  $oUser = Services::WpUsers()->getCurrentWpUser();
219
 
220
- $oNotice->render_data = [
221
  'notice_attributes' => [],
222
  'strings' => [
223
  'yes' => "Yes please! I'd love to join in and learn more",
@@ -241,17 +198,14 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
241
  'vars' => [
242
  'name' => $oUser->first_name,
243
  'user_email' => $oUser->user_email,
244
- 'drip_form_id' => $oNotice->drip_form_id
245
  ]
246
  ];
247
  }
248
 
249
- /**
250
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
251
- */
252
- private function buildNotice_UpdateAvailable( $oNotice ) {
253
  $sName = $this->getCon()->getHumanName();
254
- $oNotice->render_data = [
255
  'notice_attributes' => [],
256
  'strings' => [
257
  'title' => sprintf( __( 'Update available for the %s plugin', 'wp-simple-firewall' ), $sName ),
@@ -264,12 +218,9 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
264
  ];
265
  }
266
 
267
- /**
268
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
269
- */
270
- private function buildNotice_WelcomeWizard( $oNotice ) {
271
  $sName = $this->getCon()->getHumanName();
272
- $oNotice->render_data = [
273
  'notice_attributes' => [],
274
  'strings' => [
275
  'dismiss' => __( "I don't need the setup wizard just now", 'wp-simple-firewall' ),
@@ -283,19 +234,16 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
283
  ];
284
  }
285
 
286
- /**
287
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
288
- */
289
- private function buildNotice_AllowTracking( $oNotice ) {
290
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $oMod */
291
- $oMod = $this->getMod();
292
- $sName = $this->getCon()->getHumanName();
293
 
294
- $oNotice->render_data = [
295
  'notice_attributes' => [],
296
  'strings' => [
297
- 'title' => sprintf( __( "Make %s even better by sharing usage info?", 'wp-simple-firewall' ), $sName ),
298
- 'want_to_track' => sprintf( __( "We're hoping to understand how %s is configured and used.", 'wp-simple-firewall' ), $sName ),
299
  'what_we_collect' => __( "We'd like to understand how effective it is on a global scale.", 'wp-simple-firewall' ),
300
  'data_anon' => __( 'The data sent is always completely anonymous and we can never track you or your site.', 'wp-simple-firewall' ),
301
  'can_turn_off' => __( 'It can be turned-off at any time within the plugin options.', 'wp-simple-firewall' ),
@@ -308,22 +256,19 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
308
  'no_help' => __( "No, I don't want to help", 'wp-simple-firewall' ),
309
  ],
310
  'ajax' => [
311
- 'set_plugin_tracking' => $oMod->getAjaxActionData( 'set_plugin_tracking', true ),
312
  ],
313
  'hrefs' => [
314
  'learn_more' => 'https://translate.fernleafsystems.com',
315
- 'link_to_see' => $oMod->getLinkToTrackingDataDump(),
316
  'link_to_moreinfo' => 'https://shsec.io/shieldtrackinginfo',
317
 
318
  ]
319
  ];
320
  }
321
 
322
- /**
323
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
324
- */
325
- private function buildNotice_RatePlugin( $oNotice ) {
326
- $oNotice->render_data = [
327
  'notice_attributes' => [],
328
  'strings' => [
329
  'title' => __( 'Can You Help Us With A Quick Review?', 'wp-simple-firewall' ),
@@ -335,49 +280,41 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
335
  ];
336
  }
337
 
338
- /**
339
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
340
- * @return bool
341
- */
342
- protected function isDisplayNeeded( $oNotice ) {
343
  $oCon = $this->getCon();
344
  /** @var Options $oOpts */
345
  $oOpts = $this->getOptions();
346
 
347
- switch ( $oNotice->id ) {
348
 
349
  case 'override-forceoff':
350
- $bNeeded = $oCon->getIfForceOffActive();
351
  break;
352
 
353
  case 'php7':
354
- $bNeeded = !Services::Data()->getPhpVersionIsAtLeast( '7.0' );
355
  break;
356
 
357
  case 'plugin-disabled':
358
- $bNeeded = $oOpts->isPluginGloballyDisabled();
359
  break;
360
 
361
  case 'update-available':
362
- $bNeeded = Services::WpPlugins()->isUpdateAvailable( $oCon->getPluginBaseFile() );
363
  break;
364
 
365
  case 'compat-sgoptimize':
366
- $bNeeded = ( new Plugin\Components\SiteGroundPluginCompatibility() )->testIsIncompatible();
367
- break;
368
-
369
- case 'cloudflare-apo':
370
- $bNeeded = ( new Plugin\Components\TestForCloudflareAPO() )->run();
371
  break;
372
 
373
  case 'allow-tracking':
374
- $bNeeded = !$oOpts->isTrackingPermissionSet();
375
  break;
376
 
377
  default:
378
- $bNeeded = parent::isDisplayNeeded( $oNotice );
379
  break;
380
  }
381
- return $bNeeded;
382
  }
383
  }
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\AdminNotices\NoticeVO;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
10
  class AdminNotices extends Shield\Modules\Base\AdminNotices {
11
 
12
  /**
13
+ * @inheritDoc
 
14
  */
15
+ protected function processNotice( NoticeVO $notice ) {
16
 
17
+ switch ( $notice->id ) {
18
 
19
  case 'php7':
20
+ $this->buildNotice_Php7( $notice );
21
  break;
22
 
23
  case 'override-forceoff':
24
+ $this->buildNotice_OverrideForceoff( $notice );
25
  break;
26
 
27
  case 'plugin-disabled':
28
+ $this->buildNotice_PluginDisabled( $notice );
29
  break;
30
 
31
  case 'update-available':
32
+ $this->buildNotice_UpdateAvailable( $notice );
33
  break;
34
 
35
  case 'compat-sgoptimize':
36
+ $this->buildNotice_CompatSgOptimize( $notice );
 
 
 
 
37
  break;
38
 
39
  case 'plugin-mailing-list-signup':
40
+ $this->buildNotice_PluginMailingListSignup( $notice );
41
  break;
42
 
43
  case 'wizard_welcome':
44
+ $this->buildNotice_WelcomeWizard( $notice );
45
  break;
46
 
47
  case 'allow-tracking':
48
+ $this->buildNotice_AllowTracking( $notice );
49
  break;
50
 
51
  case 'rate-plugin':
52
+ $this->buildNotice_RatePlugin( $notice );
53
  break;
54
 
55
  default:
56
+ parent::processNotice( $notice );
57
  break;
58
  }
59
  }
60
 
61
+ public function handleAuthAjax( array $ajaxResponse ) :array {
 
 
 
 
62
 
63
+ if ( empty( $ajaxResponse ) ) {
64
  switch ( Services::Request()->request( 'exec' ) ) {
65
 
66
  case 'set_plugin_tracking':
67
+ $ajaxResponse = $this->ajaxExec_SetPluginTrackingPerm();
68
  break;
69
 
70
  default:
71
+ $ajaxResponse = parent::handleAuthAjax( $ajaxResponse );
72
  break;
73
  }
74
  }
75
+ return $ajaxResponse;
76
  }
77
 
78
+ private function ajaxExec_SetPluginTrackingPerm() :array {
79
+ /** @var Options $opts */
80
+ $opts = $this->getOptions();
81
+ $opts->setPluginTrackingPermission( (bool)Services::Request()->query( 'agree', false ) );
 
 
 
82
  return $this->ajaxExec_DismissAdminNotice();
83
  }
84
 
85
+ private function buildNotice_Php7( NoticeVO $notice ) {
 
 
 
86
  $sName = $this->getCon()->getHumanName();
87
 
88
+ $notice->render_data = [
89
  'notice_attributes' => [],
90
  'strings' => [
91
  'title' => sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ),
106
  ];
107
  }
108
 
109
+ private function buildNotice_OverrideForceoff( NoticeVO $notice ) {
 
 
 
110
  $sName = $this->getCon()->getHumanName();
111
 
112
+ $notice->render_data = [
113
  'notice_attributes' => [],
114
  'strings' => [
115
  'title' => sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ), sprintf( __( '%s is not protecting your site', 'wp-simple-firewall' ), $sName ) ),
126
  ];
127
  }
128
 
129
+ private function buildNotice_PluginDisabled( NoticeVO $notice ) {
130
+ $name = $this->getCon()->getHumanName();
 
 
 
131
 
132
+ $notice->render_data = [
133
  'notice_attributes' => [],
134
  'strings' => [
135
+ 'title' => sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ), sprintf( __( '%s is not protecting your site', 'wp-simple-firewall' ), $name ) ),
136
  'message' => implode( ' ', [
137
  __( 'The plugin is currently switched-off completely.', 'wp-simple-firewall' ),
138
  __( 'All features and any security protection they provide are disabled.', 'wp-simple-firewall' ),
145
  ];
146
  }
147
 
148
+ private function buildNotice_CompatSgOptimize( NoticeVO $notice ) {
 
 
 
149
  $sName = $this->getCon()->getHumanName();
150
 
151
+ $notice->render_data = [
152
  'notice_attributes' => [],
153
  'strings' => [
154
  'title' => sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ),
167
  ];
168
  }
169
 
170
+ private function buildNotice_PluginMailingListSignup( NoticeVO $notice ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  /** @var Options $oOpts */
172
  $oOpts = $this->getOptions();
173
 
174
  $sName = $this->getCon()->getHumanName();
175
  $oUser = Services::WpUsers()->getCurrentWpUser();
176
 
177
+ $notice->render_data = [
178
  'notice_attributes' => [],
179
  'strings' => [
180
  'yes' => "Yes please! I'd love to join in and learn more",
198
  'vars' => [
199
  'name' => $oUser->first_name,
200
  'user_email' => $oUser->user_email,
201
+ 'drip_form_id' => $notice->drip_form_id
202
  ]
203
  ];
204
  }
205
 
206
+ private function buildNotice_UpdateAvailable( NoticeVO $notice ) {
 
 
 
207
  $sName = $this->getCon()->getHumanName();
208
+ $notice->render_data = [
209
  'notice_attributes' => [],
210
  'strings' => [
211
  'title' => sprintf( __( 'Update available for the %s plugin', 'wp-simple-firewall' ), $sName ),
218
  ];
219
  }
220
 
221
+ private function buildNotice_WelcomeWizard( NoticeVO $notice ) {
 
 
 
222
  $sName = $this->getCon()->getHumanName();
223
+ $notice->render_data = [
224
  'notice_attributes' => [],
225
  'strings' => [
226
  'dismiss' => __( "I don't need the setup wizard just now", 'wp-simple-firewall' ),
234
  ];
235
  }
236
 
237
+ private function buildNotice_AllowTracking( NoticeVO $notice ) {
238
+ /** @var ModCon $mod */
239
+ $mod = $this->getMod();
240
+ $name = $this->getCon()->getHumanName();
 
 
 
241
 
242
+ $notice->render_data = [
243
  'notice_attributes' => [],
244
  'strings' => [
245
+ 'title' => sprintf( __( "Make %s even better by sharing usage info?", 'wp-simple-firewall' ), $name ),
246
+ 'want_to_track' => sprintf( __( "We're hoping to understand how %s is configured and used.", 'wp-simple-firewall' ), $name ),
247
  'what_we_collect' => __( "We'd like to understand how effective it is on a global scale.", 'wp-simple-firewall' ),
248
  'data_anon' => __( 'The data sent is always completely anonymous and we can never track you or your site.', 'wp-simple-firewall' ),
249
  'can_turn_off' => __( 'It can be turned-off at any time within the plugin options.', 'wp-simple-firewall' ),
256
  'no_help' => __( "No, I don't want to help", 'wp-simple-firewall' ),
257
  ],
258
  'ajax' => [
259
+ 'set_plugin_tracking' => $mod->getAjaxActionData( 'set_plugin_tracking', true ),
260
  ],
261
  'hrefs' => [
262
  'learn_more' => 'https://translate.fernleafsystems.com',
263
+ 'link_to_see' => $mod->getLinkToTrackingDataDump(),
264
  'link_to_moreinfo' => 'https://shsec.io/shieldtrackinginfo',
265
 
266
  ]
267
  ];
268
  }
269
 
270
+ private function buildNotice_RatePlugin( NoticeVO $notice ) {
271
+ $notice->render_data = [
 
 
 
272
  'notice_attributes' => [],
273
  'strings' => [
274
  'title' => __( 'Can You Help Us With A Quick Review?', 'wp-simple-firewall' ),
280
  ];
281
  }
282
 
283
+ protected function isDisplayNeeded( NoticeVO $notice ) :bool {
 
 
 
 
284
  $oCon = $this->getCon();
285
  /** @var Options $oOpts */
286
  $oOpts = $this->getOptions();
287
 
288
+ switch ( $notice->id ) {
289
 
290
  case 'override-forceoff':
291
+ $needed = $oCon->getIfForceOffActive();
292
  break;
293
 
294
  case 'php7':
295
+ $needed = !Services::Data()->getPhpVersionIsAtLeast( '7.0' );
296
  break;
297
 
298
  case 'plugin-disabled':
299
+ $needed = $oOpts->isPluginGloballyDisabled();
300
  break;
301
 
302
  case 'update-available':
303
+ $needed = Services::WpPlugins()->isUpdateAvailable( $oCon->getPluginBaseFile() );
304
  break;
305
 
306
  case 'compat-sgoptimize':
307
+ $needed = ( new Plugin\Components\SiteGroundPluginCompatibility() )->testIsIncompatible();
 
 
 
 
308
  break;
309
 
310
  case 'allow-tracking':
311
+ $needed = !$oOpts->isTrackingPermissionSet();
312
  break;
313
 
314
  default:
315
+ $needed = parent::isDisplayNeeded( $notice );
316
  break;
317
  }
318
+ return $needed;
319
  }
320
  }
src/lib/src/Modules/Plugin/AjaxHandler.php CHANGED
@@ -7,7 +7,7 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
7
  use FernleafSystems\Wordpress\Services\Services;
8
  use FernleafSystems\Wordpress\Services\Utilities\Net\FindSourceFromIp;
9
 
10
- class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
11
 
12
  protected function processAjaxAction( string $action ) :array {
13
  switch ( $action ) {
@@ -66,92 +66,77 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
66
  return $aResponse;
67
  }
68
 
69
- /**
70
- * @return array
71
- */
72
- private function ajaxExec_SendDeactivateSurvey() {
73
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $oMod */
74
- $oMod = $this->getMod();
75
- $aResults = [];
76
  foreach ( $_POST as $sKey => $sValue ) {
77
  if ( strpos( $sKey, 'reason_' ) === 0 ) {
78
- $aResults[] = str_replace( 'reason_', '', $sKey ).': '.$sValue;
79
  }
80
  }
81
- $oMod->getEmailProcessor()
82
- ->send(
83
- $oMod->getSurveyEmail(),
84
- 'Shield Deactivation Survey',
85
- implode( "\n<br/>", $aResults )
86
- );
87
  return [ 'success' => true ];
88
  }
89
 
90
- /**
91
- * @return array
92
- */
93
- private function ajaxExec_PluginBadgeClose() {
94
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $oMod */
95
- $oMod = $this->getMod();
96
- $bSuccess = $oMod->getPluginBadgeCon()->setBadgeStateClosed();
97
  return [
98
- 'success' => $bSuccess,
99
- 'message' => $bSuccess ? 'Badge Closed' : 'Badge Not Closed'
100
  ];
101
  }
102
 
103
- /**
104
- * @return array
105
- */
106
- private function ajaxExec_SetPluginTrackingPerm() {
107
- /** @var Options $oOpts */
108
- $oOpts = $this->getOptions();
109
- if ( !$oOpts->isTrackingPermissionSet() ) {
110
- $oOpts->setPluginTrackingPermission( (bool)Services::Request()->query( 'agree', false ) );
111
  }
112
  return [ 'success' => true ];
113
  }
114
 
115
- /**
116
- * @return array
117
- */
118
- private function ajaxExec_BulkItemAction() {
119
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $oMod */
120
- $oMod = $this->getMod();
121
- $oReq = Services::Request();
122
 
123
- $bSuccess = false;
124
 
125
- $aIds = $oReq->post( 'ids' );
126
  if ( empty( $aIds ) || !is_array( $aIds ) ) {
127
- $bSuccess = false;
128
- $sMessage = __( 'No items selected.', 'wp-simple-firewall' );
129
  }
130
- elseif ( !in_array( $oReq->post( 'bulk_action' ), [ 'delete' ] ) ) {
131
- $sMessage = __( 'Not a supported action.', 'wp-simple-firewall' );
132
  }
133
  else {
134
  /** @var Shield\Databases\AdminNotes\Delete $oDel */
135
- $oDel = $oMod->getDbHandler_Notes()->getQueryDeleter();
136
  foreach ( $aIds as $nId ) {
137
  if ( is_numeric( $nId ) ) {
138
  $oDel->deleteById( $nId );
139
  }
140
  }
141
- $bSuccess = true;
142
- $sMessage = __( 'Selected items were deleted.', 'wp-simple-firewall' );
143
  }
144
 
145
  return [
146
- 'success' => $bSuccess,
147
- 'message' => $sMessage,
148
  ];
149
  }
150
 
151
- /**
152
- * @return array
153
- */
154
- private function ajaxExec_DeleteForceOff() {
155
  $bStillActive = $this->getCon()
156
  ->deleteForceOffFile()
157
  ->getIfForceOffActive();
@@ -162,61 +147,52 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
162
  return [ 'success' => !$bStillActive ];
163
  }
164
 
165
- /**
166
- * @return array
167
- */
168
- private function ajaxExec_RenderTableAdminNotes() {
169
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $oMod */
170
- $oMod = $this->getMod();
171
  return [
172
  'success' => true,
173
  'html' => ( new Shield\Tables\Build\AdminNotes() )
174
- ->setMod( $oMod )
175
- ->setDbHandler( $oMod->getDbHandler_Notes() )
176
  ->render()
177
  ];
178
  }
179
 
180
- /**
181
- * @return array
182
- */
183
- private function ajaxExec_AdminNotesDelete() {
184
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $oMod */
185
- $oMod = $this->getMod();
186
 
187
  $sItemId = Services::Request()->post( 'rid' );
188
  if ( empty( $sItemId ) ) {
189
- $sMessage = __( 'Note not found.', 'wp-simple-firewall' );
190
  }
191
  else {
192
  try {
193
- $bSuccess = $oMod->getDbHandler_Notes()
194
- ->getQueryDeleter()
195
- ->deleteById( $sItemId );
196
 
197
  if ( $bSuccess ) {
198
- $sMessage = __( 'Note deleted', 'wp-simple-firewall' );
199
  }
200
  else {
201
- $sMessage = __( "Note couldn't be deleted", 'wp-simple-firewall' );
202
  }
203
  }
204
  catch ( \Exception $oE ) {
205
- $sMessage = $oE->getMessage();
206
  }
207
  }
208
 
209
  return [
210
  'success' => true,
211
- 'message' => $sMessage
212
  ];
213
  }
214
 
215
- /**
216
- * @return array
217
- */
218
- private function ajaxExec_ImportFromSite() {
219
- $bSuccess = false;
220
  $aFormParams = array_merge(
221
  [
222
  'confirm' => 'N'
@@ -244,60 +220,51 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
244
  catch ( \Exception $oE ) {
245
  $nCode = $oE->getCode();
246
  }
247
- $bSuccess = $nCode == 0;
248
- $sMessage = $bSuccess ? __( 'Options imported successfully', 'wp-simple-firewall' ) : __( 'Options failed to import', 'wp-simple-firewall' );
249
  }
250
 
251
  return [
252
- 'success' => $bSuccess,
253
  'message' => $sMessage
254
  ];
255
  }
256
 
257
- /**
258
- * @return array
259
- */
260
- private function ajaxExec_AdminNotesInsert() {
261
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $mod */
262
  $mod = $this->getMod();
263
- $bSuccess = false;
264
  $aFormParams = $this->getAjaxFormParams();
265
 
266
  $sNote = isset( $aFormParams[ 'admin_note' ] ) ? $aFormParams[ 'admin_note' ] : '';
267
  if ( !$mod->getCanAdminNotes() ) {
268
- $sMessage = __( "Sorry, the Admin Notes feature isn't available.", 'wp-simple-firewall' );
269
  }
270
  elseif ( empty( $sNote ) ) {
271
- $sMessage = __( 'Sorry, but it appears your note was empty.', 'wp-simple-firewall' );
272
  }
273
  else {
274
  /** @var Shield\Databases\AdminNotes\Insert $oInserter */
275
  $oInserter = $mod->getDbHandler_Notes()->getQueryInserter();
276
- $bSuccess = $oInserter->create( $sNote );
277
- $sMessage = $bSuccess ? __( 'Note created successfully.', 'wp-simple-firewall' ) : __( 'Note could not be created.', 'wp-simple-firewall' );
278
  }
279
  return [
280
- 'success' => $bSuccess,
281
- 'message' => $sMessage
282
  ];
283
  }
284
 
285
- /**
286
- * @return array
287
- */
288
- private function ajaxExec_TurnOffSiteGroundOptions() {
289
- $bSuccess = ( new Plugin\Components\SiteGroundPluginCompatibility() )->switchOffOptions();
290
  return [
291
- 'success' => $bSuccess,
292
- 'message' => $bSuccess ? __( 'Switching-off conflicting options appears to have been successful.', 'wp-simple-firewall' )
293
  : __( 'Switching-off conflicting options appears to have failed.', 'wp-simple-firewall' )
294
  ];
295
  }
296
 
297
- /**
298
- * @return array
299
- */
300
- private function ajaxExec_IpDetect() {
301
  /** @var Options $opts */
302
  $opts = $this->getOptions();
303
  $source = ( new FindSourceFromIp() )->run( Services::Request()->post( 'ip' ) );
@@ -310,13 +277,10 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
310
  ];
311
  }
312
 
313
- /**
314
- * @return array
315
- */
316
- private function ajaxExec_MarkTourFinished() {
317
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $oMod */
318
- $oMod = $this->getMod();
319
- $oMod->getTourManager()->setCompleted( Services::Request()->post( 'tour_key' ) );
320
  return [
321
  'success' => true,
322
  'message' => 'Tour Finished'
7
  use FernleafSystems\Wordpress\Services\Services;
8
  use FernleafSystems\Wordpress\Services\Utilities\Net\FindSourceFromIp;
9
 
10
+ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
11
 
12
  protected function processAjaxAction( string $action ) :array {
13
  switch ( $action ) {
66
  return $aResponse;
67
  }
68
 
69
+ private function ajaxExec_SendDeactivateSurvey() :array {
70
+ /** @var ModCon $mod */
71
+ $mod = $this->getMod();
72
+ $results = [];
 
 
 
73
  foreach ( $_POST as $sKey => $sValue ) {
74
  if ( strpos( $sKey, 'reason_' ) === 0 ) {
75
+ $results[] = str_replace( 'reason_', '', $sKey ).': '.$sValue;
76
  }
77
  }
78
+ $mod->getEmailProcessor()
79
+ ->send(
80
+ $mod->getSurveyEmail(),
81
+ 'Shield Deactivation Survey',
82
+ implode( "\n<br/>", $results )
83
+ );
84
  return [ 'success' => true ];
85
  }
86
 
87
+ private function ajaxExec_PluginBadgeClose() :array {
88
+ /** @var ModCon $mod */
89
+ $mod = $this->getMod();
90
+ $success = $mod->getPluginBadgeCon()->setBadgeStateClosed();
 
 
 
91
  return [
92
+ 'success' => $success,
93
+ 'message' => $success ? 'Badge Closed' : 'Badge Not Closed'
94
  ];
95
  }
96
 
97
+ private function ajaxExec_SetPluginTrackingPerm() :array {
98
+ /** @var Options $opts */
99
+ $opts = $this->getOptions();
100
+ if ( !$opts->isTrackingPermissionSet() ) {
101
+ $opts->setPluginTrackingPermission( (bool)Services::Request()->query( 'agree', false ) );
 
 
 
102
  }
103
  return [ 'success' => true ];
104
  }
105
 
106
+ private function ajaxExec_BulkItemAction() :array {
107
+ /** @var ModCon $mod */
108
+ $mod = $this->getMod();
109
+ $req = Services::Request();
 
 
 
110
 
111
+ $success = false;
112
 
113
+ $aIds = $req->post( 'ids' );
114
  if ( empty( $aIds ) || !is_array( $aIds ) ) {
115
+ $success = false;
116
+ $msg = __( 'No items selected.', 'wp-simple-firewall' );
117
  }
118
+ elseif ( !in_array( $req->post( 'bulk_action' ), [ 'delete' ] ) ) {
119
+ $msg = __( 'Not a supported action.', 'wp-simple-firewall' );
120
  }
121
  else {
122
  /** @var Shield\Databases\AdminNotes\Delete $oDel */
123
+ $oDel = $mod->getDbHandler_Notes()->getQueryDeleter();
124
  foreach ( $aIds as $nId ) {
125
  if ( is_numeric( $nId ) ) {
126
  $oDel->deleteById( $nId );
127
  }
128
  }
129
+ $success = true;
130
+ $msg = __( 'Selected items were deleted.', 'wp-simple-firewall' );
131
  }
132
 
133
  return [
134
+ 'success' => $success,
135
+ 'message' => $msg,
136
  ];
137
  }
138
 
139
+ private function ajaxExec_DeleteForceOff() :array {
 
 
 
140
  $bStillActive = $this->getCon()
141
  ->deleteForceOffFile()
142
  ->getIfForceOffActive();
147
  return [ 'success' => !$bStillActive ];
148
  }
149
 
150
+ private function ajaxExec_RenderTableAdminNotes() :array {
151
+ /** @var ModCon $mod */
152
+ $mod = $this->getMod();
 
 
 
153
  return [
154
  'success' => true,
155
  'html' => ( new Shield\Tables\Build\AdminNotes() )
156
+ ->setMod( $mod )
157
+ ->setDbHandler( $mod->getDbHandler_Notes() )
158
  ->render()
159
  ];
160
  }
161
 
162
+ private function ajaxExec_AdminNotesDelete() :array {
163
+ /** @var ModCon $mod */
164
+ $mod = $this->getMod();
 
 
 
165
 
166
  $sItemId = Services::Request()->post( 'rid' );
167
  if ( empty( $sItemId ) ) {
168
+ $msg = __( 'Note not found.', 'wp-simple-firewall' );
169
  }
170
  else {
171
  try {
172
+ $bSuccess = $mod->getDbHandler_Notes()
173
+ ->getQueryDeleter()
174
+ ->deleteById( $sItemId );
175
 
176
  if ( $bSuccess ) {
177
+ $msg = __( 'Note deleted', 'wp-simple-firewall' );
178
  }
179
  else {
180
+ $msg = __( "Note couldn't be deleted", 'wp-simple-firewall' );
181
  }
182
  }
183
  catch ( \Exception $oE ) {
184
+ $msg = $oE->getMessage();
185
  }
186
  }
187
 
188
  return [
189
  'success' => true,
190
+ 'message' => $msg
191
  ];
192
  }
193
 
194
+ private function ajaxExec_ImportFromSite() :array {
195
+ $success = false;
 
 
 
196
  $aFormParams = array_merge(
197
  [
198
  'confirm' => 'N'
220
  catch ( \Exception $oE ) {
221
  $nCode = $oE->getCode();
222
  }
223
+ $success = $nCode == 0;
224
+ $sMessage = $success ? __( 'Options imported successfully', 'wp-simple-firewall' ) : __( 'Options failed to import', 'wp-simple-firewall' );
225
  }
226
 
227
  return [
228
+ 'success' => $success,
229
  'message' => $sMessage
230
  ];
231
  }
232
 
233
+ private function ajaxExec_AdminNotesInsert() :array {
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 [
253
+ 'success' => $success,
254
+ 'message' => $msg
255
  ];
256
  }
257
 
258
+ private function ajaxExec_TurnOffSiteGroundOptions() :array {
259
+ $success = ( new Plugin\Components\SiteGroundPluginCompatibility() )->switchOffOptions();
 
 
 
260
  return [
261
+ 'success' => $success,
262
+ 'message' => $success ? __( 'Switching-off conflicting options appears to have been successful.', 'wp-simple-firewall' )
263
  : __( 'Switching-off conflicting options appears to have failed.', 'wp-simple-firewall' )
264
  ];
265
  }
266
 
267
+ private function ajaxExec_IpDetect() :array {
 
 
 
268
  /** @var Options $opts */
269
  $opts = $this->getOptions();
270
  $source = ( new FindSourceFromIp() )->run( Services::Request()->post( 'ip' ) );
277
  ];
278
  }
279
 
280
+ private function ajaxExec_MarkTourFinished() :array {
281
+ /** @var ModCon $mod */
282
+ $mod = $this->getMod();
283
+ $mod->getTourManager()->setCompleted( Services::Request()->post( 'tour_key' ) );
 
 
 
284
  return [
285
  'success' => true,
286
  'message' => 'Tour Finished'
src/lib/src/Modules/Plugin/Components/BadgeWidget.php CHANGED
@@ -2,19 +2,21 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Components;
4
 
 
 
5
  class BadgeWidget extends \WP_Widget {
6
 
7
  use \FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
 
9
  /**
10
  * BadgeWidget constructor.
11
- * @param \ICWP_WPSF_FeatureHandler_Base $oMod
12
  */
13
- public function __construct( $oMod ) {
14
- if ( empty( $oMod ) ) {
15
  return;
16
  }
17
- $this->setMod( $oMod );
18
  $oCon = $this->getCon();
19
 
20
  $sName = $oCon->getHumanName();
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Components;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\ModCon;
6
+
7
  class BadgeWidget extends \WP_Widget {
8
 
9
  use \FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
10
 
11
  /**
12
  * BadgeWidget constructor.
13
+ * @param ModCon $mod
14
  */
15
+ public function __construct( $mod ) {
16
+ if ( empty( $mod ) ) {
17
  return;
18
  }
19
+ $this->setMod( $mod );
20
  $oCon = $this->getCon();
21
 
22
  $sName = $oCon->getHumanName();
src/lib/src/Modules/Plugin/Components/PluginBadge.php CHANGED
@@ -3,7 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Components;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Options;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  /**
@@ -15,7 +15,7 @@ class PluginBadge {
15
  use Modules\ModConsumer;
16
 
17
  public function run() {
18
- /** @var Options $opts */
19
  $opts = $this->getOptions();
20
  $bDisplay = $opts->isOpt( 'display_plugin_badge', 'Y' )
21
  && ( Services::Request()->cookie( $this->getCookieIdBadgeState() ) != 'closed' );
@@ -37,18 +37,15 @@ class PluginBadge {
37
  * https://wordpress.org/support/topic/fatal-errors-after-update-to-7-0-2/#post-11169820
38
  */
39
  public function addPluginBadgeWidget() {
40
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $oMod */
41
- $oMod = $this->getMod();
42
- if ( !empty( $oMod ) && Services::WpGeneral()->getWordpressIsAtLeastVersion( '4.6.0' )
43
  && !class_exists( 'Tribe_WP_Widget_Factory' ) ) {
44
- register_widget( new BadgeWidget( $oMod ) );
45
  }
46
  }
47
 
48
- /**
49
- * @return string
50
- */
51
- private function getCookieIdBadgeState() {
52
  return $this->getCon()->prefix( 'badgeState' );
53
  }
54
 
@@ -61,75 +58,86 @@ class PluginBadge {
61
  }
62
 
63
  /**
64
- * @param bool $bFloating
65
  * @return string
66
  */
67
- public function render( $bFloating = false ) {
68
  $con = $this->getCon();
69
- $sName = $con->getHumanName();
70
-
71
- $badgeUrl = 'https://shsec.io/wpsecurityfirewall';
72
  /** @var Modules\SecurityAdmin\Options $secAdminOpts */
73
  $secAdminOpts = $con->getModule_SecAdmin()->getOptions();
74
- if ( $secAdminOpts->isEnabledWhitelabel() && $secAdminOpts->isReplaceBadgeUrl() ) {
 
75
  $badgeUrl = $secAdminOpts->getOpt( 'wl_homeurl' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  }
77
 
78
- $lic = $con->getModule_License()
79
- ->getLicenseHandler()
80
- ->getLicense();
81
- if ( !empty( $lic->aff_ref ) ) {
82
- $badgeUrl = add_query_arg( [ 'ref' => $lic->aff_ref ], $badgeUrl );
 
 
 
 
 
 
 
83
  }
84
 
85
- $aData = [
86
  'ajax' => [
87
  'plugin_badge_close' => $this->getMod()->getAjaxActionData( 'plugin_badge_close', true ),
88
  ],
 
 
 
89
  'flags' => [
90
  'nofollow' => apply_filters( 'icwp_shield_badge_relnofollow', false ),
91
- 'is_floating' => $bFloating
92
  ],
93
  'hrefs' => [
94
- 'badge' => $badgeUrl,
95
- 'logo' => $con->getPluginUrl_Image( 'shield/shield-security-logo-colour-32px.png' ),
96
  ],
97
  'strings' => [
98
- 'protected' => apply_filters( 'icwp_shield_plugin_badge_text',
99
- sprintf( __( 'This Site Is Protected By %s', 'wp-simple-firewall' ),
100
- '<br/><span class="plugin-badge-name">'.$sName.'</span>' )
101
- ),
102
- 'name' => $sName,
103
  ],
104
  ];
105
 
106
  try {
107
- $sRender = $this->getMod()->renderTemplate( 'snippets/plugin_badge_widget', $aData, true );
108
  }
109
  catch ( \Exception $oE ) {
110
- $sRender = 'Could not generate badge: '.$oE->getMessage();
111
  }
112
-
113
- return $sRender;
114
  }
115
 
116
- /**
117
- * @return bool
118
- */
119
- public function setBadgeStateClosed() {
120
- return Services::Response()
121
- ->cookieSet(
122
- $this->getCookieIdBadgeState(),
123
- 'closed',
124
- DAY_IN_SECONDS
125
- );
126
  }
127
 
128
- /**
129
- * @param bool $bDisplay
130
- * @return void
131
- */
132
- public function setIsDisplayPluginBadge( $bDisplay ) {
133
- $this->getOptions()->setOpt( 'display_plugin_badge', $bDisplay ? 'Y' : 'N' );
134
  }
135
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Components;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  /**
15
  use Modules\ModConsumer;
16
 
17
  public function run() {
18
+ /** @var Plugin\Options $opts */
19
  $opts = $this->getOptions();
20
  $bDisplay = $opts->isOpt( 'display_plugin_badge', 'Y' )
21
  && ( Services::Request()->cookie( $this->getCookieIdBadgeState() ) != 'closed' );
37
  * https://wordpress.org/support/topic/fatal-errors-after-update-to-7-0-2/#post-11169820
38
  */
39
  public function addPluginBadgeWidget() {
40
+ /** @var Plugin\ModCon $mod */
41
+ $mod = $this->getMod();
42
+ if ( !empty( $mod ) && Services::WpGeneral()->getWordpressIsAtLeastVersion( '4.6.0' )
43
  && !class_exists( 'Tribe_WP_Widget_Factory' ) ) {
44
+ register_widget( new BadgeWidget( $mod ) );
45
  }
46
  }
47
 
48
+ private function getCookieIdBadgeState() :string {
 
 
 
49
  return $this->getCon()->prefix( 'badgeState' );
50
  }
51
 
58
  }
59
 
60
  /**
61
+ * @param bool $isFloating
62
  * @return string
63
  */
64
+ public function render( $isFloating = false ) {
65
  $con = $this->getCon();
 
 
 
66
  /** @var Modules\SecurityAdmin\Options $secAdminOpts */
67
  $secAdminOpts = $con->getModule_SecAdmin()->getOptions();
68
+
69
+ if ( $secAdminOpts->isEnabledWhitelabel() && $secAdminOpts->isReplacePluginBadge() ) {
70
  $badgeUrl = $secAdminOpts->getOpt( 'wl_homeurl' );
71
+ $name = $secAdminOpts->getOpt( 'wl_pluginnamemain' );
72
+ $logo = $secAdminOpts->getOpt( 'wl_dashboardlogourl' );
73
+ }
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()
81
+ ->getLicense();
82
+ if ( !empty( $lic->aff_ref ) ) {
83
+ $badgeUrl = add_query_arg( [ 'ref' => $lic->aff_ref ], $badgeUrl );
84
+ }
85
  }
86
 
87
+ $badgeAttrs = [
88
+ 'name' => $name,
89
+ 'url' => $badgeUrl,
90
+ 'logo' => $logo,
91
+ 'protected_by' => apply_filters( 'icwp_shield_plugin_badge_text',
92
+ sprintf( __( 'This Site Is Protected By %s', 'wp-simple-firewall' ),
93
+ '<br/><span class="plugin-badge-name">'.$name.'</span>' )
94
+ ),
95
+ 'custom_css' => '',
96
+ ];
97
+ if ( $con->isPremiumActive() ) {
98
+ $badgeAttrs = apply_filters( 'icwp_shield_plugin_badge_attributes', $badgeAttrs, $isFloating );
99
  }
100
 
101
+ $data = [
102
  'ajax' => [
103
  'plugin_badge_close' => $this->getMod()->getAjaxActionData( 'plugin_badge_close', true ),
104
  ],
105
+ 'content' => [
106
+ 'custom_css' => esc_js( $badgeAttrs[ 'custom_css' ] ),
107
+ ],
108
  'flags' => [
109
  'nofollow' => apply_filters( 'icwp_shield_badge_relnofollow', false ),
110
+ 'is_floating' => $isFloating
111
  ],
112
  'hrefs' => [
113
+ 'badge' => $badgeAttrs[ 'url' ],
114
+ 'logo' => $badgeAttrs[ 'logo' ],
115
  ],
116
  'strings' => [
117
+ 'protected' => $badgeAttrs[ 'protected_by' ],
118
+ 'name' => $badgeAttrs[ 'name' ],
 
 
 
119
  ],
120
  ];
121
 
122
  try {
123
+ $render = $this->getMod()->renderTemplate( 'snippets/plugin_badge_widget', $data, true );
124
  }
125
  catch ( \Exception $oE ) {
126
+ $render = 'Could not generate badge: '.$oE->getMessage();
127
  }
128
+ return $render;
 
129
  }
130
 
131
+ public function setBadgeStateClosed() :bool {
132
+ return (bool)Services::Response()
133
+ ->cookieSet(
134
+ $this->getCookieIdBadgeState(),
135
+ 'closed',
136
+ DAY_IN_SECONDS
137
+ );
 
 
 
138
  }
139
 
140
+ public function setIsDisplayPluginBadge( bool $isDisplay ) {
141
+ $this->getOptions()->setOpt( 'display_plugin_badge', $isDisplay ? 'Y' : 'N' );
 
 
 
 
142
  }
143
  }
src/lib/src/Modules/Plugin/Components/TestForCloudflareAPO.php CHANGED
@@ -6,7 +6,7 @@ use FernleafSystems\Wordpress\Services\Services;
6
 
7
  class TestForCloudflareAPO {
8
 
9
- public function run() {
10
  $req = Services::Request();
11
  $srvIP = Services::IP();
12
  $visitorIP = $srvIP->getRequestIp();
6
 
7
  class TestForCloudflareAPO {
8
 
9
+ public function run() :bool {
10
  $req = Services::Request();
11
  $srvIP = Services::IP();
12
  $visitorIP = $srvIP->getRequestIp();
src/lib/src/Modules/Plugin/Debug.php CHANGED
@@ -1,11 +1,20 @@
1
- <?php
2
 
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
  }
11
  }
1
+ <?php declare( strict_types=1 );
2
 
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
  }
src/lib/src/Modules/Plugin/Insights/AdminNotes.php CHANGED
@@ -3,12 +3,13 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Insights;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
 
6
 
7
  class AdminNotes {
8
 
9
  use ModConsumer;
10
 
11
- public function build() :string {
12
  return $this->getMod()
13
  ->renderTemplate(
14
  '/wpadmin_pages/insights/notes/admin_notes.twig',
@@ -18,8 +19,7 @@ class AdminNotes {
18
  }
19
 
20
  private function buildData() :array {
21
- $con = $this->getCon();
22
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $mod */
23
  $mod = $this->getMod();
24
 
25
  return [
@@ -30,9 +30,6 @@ class AdminNotes {
30
  'item_insert' => $mod->getAjaxActionData( 'note_insert', true ),
31
  'bulk_action' => $mod->getAjaxActionData( 'bulk_action', true ),
32
  ],
33
- 'flags' => [
34
- 'can_adminnotes' => $con->isPremiumActive(),
35
- ],
36
  'strings' => [
37
  'note_title' => __( 'Administrator Notes', 'wp-simple-firewall' ),
38
  'use_this_area' => __( 'Use this feature to make ongoing notes and to-dos', 'wp-simple-firewall' ),
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Insights;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\ModCon;
7
 
8
  class AdminNotes {
9
 
10
  use ModConsumer;
11
 
12
+ public function render() :string {
13
  return $this->getMod()
14
  ->renderTemplate(
15
  '/wpadmin_pages/insights/notes/admin_notes.twig',
19
  }
20
 
21
  private function buildData() :array {
22
+ /** @var ModCon $mod */
 
23
  $mod = $this->getMod();
24
 
25
  return [
30
  'item_insert' => $mod->getAjaxActionData( 'note_insert', true ),
31
  'bulk_action' => $mod->getAjaxActionData( 'bulk_action', true ),
32
  ],
 
 
 
33
  'strings' => [
34
  'note_title' => __( 'Administrator Notes', 'wp-simple-firewall' ),
35
  'use_this_area' => __( 'Use this feature to make ongoing notes and to-dos', 'wp-simple-firewall' ),
src/lib/src/Modules/Plugin/Insights/DashboardCards.php ADDED
@@ -0,0 +1,410 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Insights;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases\AdminNotes;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
8
+
9
+ class DashboardCards {
10
+
11
+ use Shield\Modules\ModConsumer;
12
+
13
+ /**
14
+ * @return array
15
+ * @throws \Exception
16
+ */
17
+ public function renderAll() :array {
18
+ $cards = array_merge(
19
+ [
20
+ 'settings' => $this->renderSettingsCard()
21
+ ],
22
+ array_map(
23
+ function ( $card ) {
24
+ return $this->renderStandardCard( $card );
25
+ },
26
+ array_filter(
27
+ $this->buildStandardCards(),
28
+ function ( $card ) {
29
+ return empty( $card[ 'hidden' ] );
30
+ }
31
+ )
32
+ )
33
+ );
34
+
35
+ if ( !empty( array_diff_key( $cards, array_flip( $this->getAllCardSlugs() ) ) ) ) {
36
+ throw new \Exception( 'Card(s) with unrecognised slug' );
37
+ }
38
+
39
+ // Merge ensures the order is as we want it, and the intersect ensure hidden cards are not included
40
+ return array_merge(
41
+ array_flip( array_intersect( $this->getAllCardSlugs(), array_keys( $cards ) ) ),
42
+ $cards
43
+ );
44
+ }
45
+
46
+ public function renderSettingsCard() :string {
47
+ $con = $this->getCon();
48
+ /** @var Plugin\ModCon $mod */
49
+ $mod = $this->getMod();
50
+
51
+ 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' => [
62
+ [
63
+ 'text' => __( "Go To General Settings", 'wp-simple-firewall' ),
64
+ 'href' => $mod->getUrl_AdminPage(),
65
+ ],
66
+ [
67
+ 'text' => __( "Scans & Hack Guard Settings", 'wp-simple-firewall' ),
68
+ 'href' => $con->getModule_HackGuard()->getUrl_AdminPage(),
69
+ ],
70
+ ],
71
+ ],
72
+ 'strings' => [
73
+ 'select' => __( "Select Module", 'wp-simple-firewall' )
74
+ ],
75
+ 'vars' => [
76
+ 'mods' => $mod->getUIHandler()->buildSelectData_ModuleSettings(),
77
+ 'search_select' => $mod->getUIHandler()->buildSelectData_OptionsSearch()
78
+ ]
79
+ ],
80
+ true
81
+ );
82
+ }
83
+
84
+ protected function renderStandardCard( $card ) {
85
+ /** @var Plugin\ModCon $mod */
86
+ $mod = $this->getMod();
87
+ return $mod->renderTemplate(
88
+ '/wpadmin_pages/insights/dashboard/card_std.twig',
89
+ [ 'c' => $card ],
90
+ true
91
+ );
92
+ }
93
+
94
+ private function buildStandardCards() :array {
95
+ $con = $this->getCon();
96
+ $modComments = $con->getModule_Comments();
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 ?
103
+ sprintf( 'Your most recent note: "%s"', $note->note ) :
104
+ __( 'No notes made yet.', 'wp-simple-firewall' );
105
+
106
+ return [
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
+ [
116
+ 'text' => __( "See My Security Overview", 'wp-simple-firewall' ),
117
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'overview' ),
118
+ ],
119
+ ]
120
+ ],
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
+ [
131
+ 'text' => __( "Run Scans", 'wp-simple-firewall' ),
132
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'scans' ),
133
+ ],
134
+ [
135
+ 'text' => __( "Scans & Hack Guard Settings", 'wp-simple-firewall' ),
136
+ 'href' => $con->getModule_HackGuard()->getUrl_AdminPage(),
137
+ ],
138
+ ]
139
+ ],
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
+ [
149
+ 'text' => __( "Security Admin Settings", 'wp-simple-firewall' ),
150
+ 'href' => $con->getModule_SecAdmin()->getUrl_AdminPage(),
151
+ ],
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' )
161
+ ],
162
+ 'actions' => [
163
+ [
164
+ 'text' => __( "Get The Free Trial", 'wp-simple-firewall' ),
165
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'free_trial' ),
166
+ ],
167
+ ],
168
+ 'classes' => $con->isPremiumActive() ? [] : [ 'highlighted', 'text-white', 'bg-primary' ],
169
+ 'hidden' => $con->isPremiumActive()
170
+ ],
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' ),
178
+ ],
179
+ 'actions' => [
180
+ [
181
+ 'text' => __( "Analyse & Manage IPs", 'wp-simple-firewall' ),
182
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'ips' ),
183
+ ],
184
+ [
185
+ 'text' => __( "IP Blocking Settings", 'wp-simple-firewall' ),
186
+ 'href' => $con->getModule_IPs()->getUrl_AdminPage(),
187
+ ],
188
+ ]
189
+ ],
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
+ ],
197
+ 'actions' => [
198
+ [
199
+ 'text' => __( "View Audit Log", 'wp-simple-firewall' ),
200
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'audit' ),
201
+ ],
202
+ [
203
+ 'text' => __( "Audit Trail Settings", 'wp-simple-firewall' ),
204
+ 'href' => $con->getModule_AuditTrail()->getUrl_AdminPage(),
205
+ ],
206
+ ]
207
+ ],
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' ),
215
+ ],
216
+ 'actions' => [
217
+ [
218
+ 'text' => __( "View Traffic Log", 'wp-simple-firewall' ),
219
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'traffic' ),
220
+ ],
221
+ [
222
+ 'text' => __( "Traffic Log Settings", 'wp-simple-firewall' ),
223
+ 'href' => $con->getModule_Traffic()->getUrl_AdminPage(),
224
+ ],
225
+ ]
226
+ ],
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' ),
252
+ sprintf( '%s: %s',
253
+ __( "Privacy Note", 'wp-simple-firewall' ),
254
+ __( "Unlike Akismet, your comments and data are never sent off-site for analysis.", 'wp-simple-firewall' )
255
+ )
256
+ ],
257
+ 'actions' => [
258
+ [
259
+ 'text' => __( "Bot SPAM Settings", 'wp-simple-firewall' ),
260
+ 'href' => $modComments->getUrl_DirectLinkToSection( 'section_bot_comment_spam_protection_filter' ),
261
+ ],
262
+ [
263
+ 'text' => __( "Human SPAM Settings", 'wp-simple-firewall' ),
264
+ 'href' => $modComments->getUrl_DirectLinkToSection( 'section_human_spam_filter' ),
265
+ ],
266
+ ]
267
+ ],
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' ),
275
+ ],
276
+ 'actions' => [
277
+ [
278
+ 'text' => __( "Run Import/Export", 'wp-simple-firewall' ),
279
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'importexport' ),
280
+ ],
281
+ [
282
+ 'text' => __( "Import/Export Settings", 'wp-simple-firewall' ),
283
+ 'href' => $modPlugin->getUrl_DirectLinkToSection( 'section_importexport' ),
284
+ ],
285
+ ]
286
+ ],
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
+ ],
294
+ 'actions' => [
295
+ [
296
+ 'text' => $con->isPremiumActive() ? __( "Manage PRO", 'wp-simple-firewall' ) : __( "Go PRO!", 'wp-simple-firewall' ),
297
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'license' ),
298
+ ],
299
+ [
300
+ 'text' => __( "See Exclusive ShieldPRO Features", 'wp-simple-firewall' ),
301
+ 'href' => 'https://shsec.io/gp',
302
+ 'new' => true,
303
+ ],
304
+ ],
305
+ 'classes' => $con->isPremiumActive() ? [] : [ 'highlighted', 'text-white', 'bg-success' ]
306
+ ],
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
314
+ ],
315
+ 'actions' => [
316
+ [
317
+ 'text' => __( "Manage Admin Notes", 'wp-simple-firewall' ),
318
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'notes' ),
319
+ ],
320
+ ]
321
+ ],
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' ),
329
+ ],
330
+ 'actions' => [
331
+ [
332
+ 'text' => __( "Manage White Label", 'wp-simple-firewall' ),
333
+ 'href' => $con->getModule_SecAdmin()
334
+ ->getUrl_DirectLinkToSection( 'section_whitelabel' ),
335
+ ],
336
+ ]
337
+ ],
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' ),
345
+ ],
346
+ 'actions' => [
347
+ [
348
+ 'text' => __( "Manage Integrations", 'wp-simple-firewall' ),
349
+ 'href' => $con->getModule_Integrations()->getUrl_AdminPage(),
350
+ ],
351
+ ]
352
+ ],
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
+ [
362
+ 'text' => __( "View Docs", 'wp-simple-firewall' ),
363
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'docs' ),
364
+ ],
365
+ ]
366
+ ],
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' ),
374
+ ],
375
+ 'actions' => [
376
+ [
377
+ 'text' => __( "View Debug Info", 'wp-simple-firewall' ),
378
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'debug' ),
379
+ ],
380
+ ]
381
+ ],
382
+
383
+ ];
384
+ }
385
+
386
+ /**
387
+ * Allows us to order Dashboard cards
388
+ */
389
+ private function getAllCardSlugs() :array {
390
+ return [
391
+ 'overview',
392
+ 'settings',
393
+ 'scans',
394
+ 'free_trial',
395
+ 'sec_admin',
396
+ 'ips',
397
+ 'audit_trail',
398
+ 'traffic',
399
+ 'users',
400
+ 'comments',
401
+ 'import',
402
+ 'license',
403
+ 'integrations',
404
+ 'notes',
405
+ 'whitelabel',
406
+ 'docs',
407
+ 'debug',
408
+ ];
409
+ }
410
+ }
src/lib/src/Modules/Plugin/Insights/OverviewCards.php CHANGED
@@ -3,15 +3,16 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Insights;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
 
6
  use FernleafSystems\Wordpress\Services\Services;
7
  use FernleafSystems\Wordpress\Services\Utilities\Ssl;
8
 
9
  class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
10
 
11
  public function build() :array {
12
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $mod */
13
  $mod = $this->getMod();
14
- /** @var Shield\Modules\Plugin\Options $opts */
15
  $opts = $this->getOptions();
16
 
17
  $cardSection = [
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Insights;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
7
  use FernleafSystems\Wordpress\Services\Services;
8
  use FernleafSystems\Wordpress\Services\Utilities\Ssl;
9
 
10
  class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
11
 
12
  public function build() :array {
13
+ /** @var Plugin\ModCon $mod */
14
  $mod = $this->getMod();
15
+ /** @var Plugin\Options $opts */
16
  $opts = $this->getOptions();
17
 
18
  $cardSection = [
src/lib/src/Modules/Plugin/Lib/Captcha/CheckCaptchaSettings.php CHANGED
@@ -24,11 +24,11 @@ class CheckCaptchaSettings {
24
  }
25
 
26
  public function verifyKeys() {
27
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $oMod */
28
- $oMod = $this->getMod();
29
  /** @var Plugin\Options $oOpts */
30
  $oOpts = $this->getOptions();
31
- $oCfg = $oMod->getCaptchaCfg();
32
 
33
  $nAt = -1;
34
  if ( $oCfg->ready && $oOpts->getOpt( 'captcha_checked_at' ) <= 0 ) {
@@ -50,9 +50,9 @@ class CheckCaptchaSettings {
50
  * @return bool
51
  */
52
  private function verifyHcaptcha() {
53
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $oMod */
54
- $oMod = $this->getMod();
55
- $oCfg = $oMod->getCaptchaCfg();
56
  return substr_count( $oCfg->key, '-' ) > 1
57
  && strpos( $oCfg->secret, '0x' ) === 0;
58
  }
@@ -60,24 +60,24 @@ class CheckCaptchaSettings {
60
  /**
61
  * @return bool
62
  */
63
- private function verifyRecaptcha() {
64
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $oMod */
65
- $oMod = $this->getMod();
66
 
67
  $sResponse = Services::HttpRequest()->getContent( add_query_arg(
68
  [
69
- 'secret' => $oMod->getCaptchaCfg()->secret,
70
  'response' => rand(),
71
  ],
72
  'https://www.google.com/recaptcha/api/siteverify'
73
  ) );
74
 
75
- $bValid = false;
76
  if ( !empty( $sResponse ) ) {
77
  $aDec = json_decode( $sResponse, true );
78
- $bValid = is_array( $aDec ) && is_array( $aDec[ 'error-codes' ] )
79
- && !in_array( 'invalid-input-secret', $aDec[ 'error-codes' ] );
80
  }
81
- return $bValid;
82
  }
83
  }
24
  }
25
 
26
  public function verifyKeys() {
27
+ /** @var Plugin\ModCon $mod */
28
+ $mod = $this->getMod();
29
  /** @var Plugin\Options $oOpts */
30
  $oOpts = $this->getOptions();
31
+ $oCfg = $mod->getCaptchaCfg();
32
 
33
  $nAt = -1;
34
  if ( $oCfg->ready && $oOpts->getOpt( 'captcha_checked_at' ) <= 0 ) {
50
  * @return bool
51
  */
52
  private function verifyHcaptcha() {
53
+ /** @var Plugin\ModCon $mod */
54
+ $mod = $this->getMod();
55
+ $oCfg = $mod->getCaptchaCfg();
56
  return substr_count( $oCfg->key, '-' ) > 1
57
  && strpos( $oCfg->secret, '0x' ) === 0;
58
  }
60
  /**
61
  * @return bool
62
  */
63
+ private function verifyRecaptcha() :bool {
64
+ /** @var Plugin\ModCon $mod */
65
+ $mod = $this->getMod();
66
 
67
  $sResponse = Services::HttpRequest()->getContent( add_query_arg(
68
  [
69
+ 'secret' => $mod->getCaptchaCfg()->secret,
70
  'response' => rand(),
71
  ],
72
  'https://www.google.com/recaptcha/api/siteverify'
73
  ) );
74
 
75
+ $valid = false;
76
  if ( !empty( $sResponse ) ) {
77
  $aDec = json_decode( $sResponse, true );
78
+ $valid = is_array( $aDec ) && is_array( $aDec[ 'error-codes' ] )
79
+ && !in_array( 'invalid-input-secret', $aDec[ 'error-codes' ] );
80
  }
81
+ return $valid;
82
  }
83
  }
src/lib/src/Modules/Plugin/Lib/ImportExport/Export.php CHANGED
@@ -32,17 +32,17 @@ class Export {
32
  }
33
 
34
  public function toJson() {
35
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $oMod */
36
- $oMod = $this->getMod();
37
- $oReq = Services::Request();
38
 
39
- $sSecretKey = $oReq->query( 'secret', '' );
40
 
41
- $sNetworkOpt = $oReq->query( 'network', '' );
42
  $bDoNetwork = !empty( $sNetworkOpt );
43
- $sUrl = Services::Data()->validateSimpleHttpUrl( $oReq->query( 'url', '' ) );
44
 
45
- if ( !$oMod->isImportExportSecretKey( $sSecretKey ) && !$this->isUrlOnWhitelist( $sUrl ) ) {
46
  return; // we show no signs of responding to invalid secret keys or unwhitelisted URLs
47
  }
48
 
@@ -66,14 +66,14 @@ class Export {
66
 
67
  if ( $bDoNetwork ) {
68
  if ( $sNetworkOpt === 'Y' ) {
69
- $oMod->addUrlToImportExportWhitelistUrls( $sUrl );
70
  $this->getCon()->fireEvent(
71
  'whitelist_site_added',
72
  [ 'audit' => [ 'site' => $sUrl ] ]
73
  );
74
  }
75
  else {
76
- $oMod->removeUrlFromImportExportWhitelistUrls( $sUrl );
77
  $this->getCon()->fireEvent(
78
  'whitelist_site_removed',
79
  [ 'audit' => [ 'site' => $sUrl ] ]
32
  }
33
 
34
  public function toJson() {
35
+ /** @var Plugin\ModCon $mod */
36
+ $mod = $this->getMod();
37
+ $req = Services::Request();
38
 
39
+ $sSecretKey = $req->query( 'secret', '' );
40
 
41
+ $sNetworkOpt = $req->query( 'network', '' );
42
  $bDoNetwork = !empty( $sNetworkOpt );
43
+ $sUrl = Services::Data()->validateSimpleHttpUrl( $req->query( 'url', '' ) );
44
 
45
+ if ( !$mod->isImportExportSecretKey( $sSecretKey ) && !$this->isUrlOnWhitelist( $sUrl ) ) {
46
  return; // we show no signs of responding to invalid secret keys or unwhitelisted URLs
47
  }
48
 
66
 
67
  if ( $bDoNetwork ) {
68
  if ( $sNetworkOpt === 'Y' ) {
69
+ $mod->addUrlToImportExportWhitelistUrls( $sUrl );
70
  $this->getCon()->fireEvent(
71
  'whitelist_site_added',
72
  [ 'audit' => [ 'site' => $sUrl ] ]
73
  );
74
  }
75
  else {
76
+ $mod->removeUrlFromImportExportWhitelistUrls( $sUrl );
77
  $this->getCon()->fireEvent(
78
  'whitelist_site_removed',
79
  [ 'audit' => [ 'site' => $sUrl ] ]
src/lib/src/Modules/Plugin/Lib/ImportExport/Import.php CHANGED
@@ -99,18 +99,18 @@ class Import {
99
  * @throws \Exception
100
  */
101
  public function fromSite( $sMasterSiteUrl = '', $sSecretKey = '', $bEnableNetwork = null ) {
102
- /** @var Plugin\Options $oOpts */
103
- $oOpts = $this->getOptions();
104
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $mod */
105
  $mod = $this->getMod();
106
- $oDP = Services::Data();
107
 
108
  if ( empty( $sMasterSiteUrl ) ) {
109
- $sMasterSiteUrl = $oOpts->getImportExportMasterImportUrl();
110
  }
111
 
112
- $sOriginalMasterSiteUrl = $oOpts->getImportExportMasterImportUrl();
113
- $bHadMasterSiteUrl = $oOpts->hasImportExportMasterImportUrl();
114
  $bCheckKeyFormat = !$bHadMasterSiteUrl;
115
  $sSecretKey = sanitize_key( $sSecretKey );
116
 
@@ -135,13 +135,13 @@ class Import {
135
  if ( !$bHasParts ) {
136
  throw new \Exception( "Couldn't parse the URL into its parts", 4 );
137
  }
138
- $sMasterSiteUrl = $oDP->validateSimpleHttpUrl( $sMasterSiteUrl ); // final clean
139
  if ( empty( $sMasterSiteUrl ) ) {
140
  throw new \Exception( "Couldn't validate the URL.", 4 );
141
  }
142
 
143
  // Begin the handshake process.
144
- $oOpts->setOpt(
145
  'importexport_handshake_expires_at',
146
  Services::Request()->ts() + 30
147
  );
@@ -186,7 +186,7 @@ class Import {
186
  // Fix for the overwriting of the Master Site URL with an empty string.
187
  // Only do so if we're not turning it off. i.e on or no-change
188
  if ( is_null( $bEnableNetwork ) ) {
189
- if ( $bHadMasterSiteUrl && !$oOpts->hasImportExportMasterImportUrl() ) {
190
  $mod->setImportExportMasterImportUrl( $sOriginalMasterSiteUrl );
191
  }
192
  }
99
  * @throws \Exception
100
  */
101
  public function fromSite( $sMasterSiteUrl = '', $sSecretKey = '', $bEnableNetwork = null ) {
102
+ /** @var Plugin\Options $opts */
103
+ $opts = $this->getOptions();
104
+ /** @var Plugin\ModCon $mod */
105
  $mod = $this->getMod();
106
+ $DP = Services::Data();
107
 
108
  if ( empty( $sMasterSiteUrl ) ) {
109
+ $sMasterSiteUrl = $opts->getImportExportMasterImportUrl();
110
  }
111
 
112
+ $sOriginalMasterSiteUrl = $opts->getImportExportMasterImportUrl();
113
+ $bHadMasterSiteUrl = $opts->hasImportExportMasterImportUrl();
114
  $bCheckKeyFormat = !$bHadMasterSiteUrl;
115
  $sSecretKey = sanitize_key( $sSecretKey );
116
 
135
  if ( !$bHasParts ) {
136
  throw new \Exception( "Couldn't parse the URL into its parts", 4 );
137
  }
138
+ $sMasterSiteUrl = $DP->validateSimpleHttpUrl( $sMasterSiteUrl ); // final clean
139
  if ( empty( $sMasterSiteUrl ) ) {
140
  throw new \Exception( "Couldn't validate the URL.", 4 );
141
  }
142
 
143
  // Begin the handshake process.
144
+ $opts->setOpt(
145
  'importexport_handshake_expires_at',
146
  Services::Request()->ts() + 30
147
  );
186
  // Fix for the overwriting of the Master Site URL with an empty string.
187
  // Only do so if we're not turning it off. i.e on or no-change
188
  if ( is_null( $bEnableNetwork ) ) {
189
+ if ( $bHadMasterSiteUrl && !$opts->hasImportExportMasterImportUrl() ) {
190
  $mod->setImportExportMasterImportUrl( $sOriginalMasterSiteUrl );
191
  }
192
  }
src/lib/src/Modules/Plugin/Lib/ImportExport/ImportExportController.php CHANGED
@@ -109,19 +109,22 @@ class ImportExportController {
109
  }
110
  }
111
 
112
- /**
113
- * @return array
114
- */
115
- public function buildInsightsVars() {
116
- /** @var \ICWP_WPSF_FeatureHandler_HackProtect $oMod */
117
- $oMod = $this->getMod();
118
  return [
119
  'vars' => [
120
- 'file_upload_nonce' => $oMod->getNonceActionData( 'import_file_upload' ),
121
- 'form_action' => $oMod->getUrl_AdminPage()
 
 
 
 
 
 
122
  ],
123
  'ajax' => [
124
- 'import_from_site' => $oMod->getAjaxActionData( 'import_from_site', true ),
125
  ],
126
  'flags' => [
127
  'can_importexport' => $this->getCon()->isPremiumActive(),
@@ -130,8 +133,8 @@ class ImportExportController {
130
  'export_file_download' => $this->createExportFileDownloadLink()
131
  ],
132
  'strings' => [
133
- 'tab_by_file' => __( 'Import From File', 'wp-simple-firewall' ),
134
- 'tab_by_site' => __( 'Import From Another Site', 'wp-simple-firewall' ),
135
  'title_import_file' => __( 'Import From File', 'wp-simple-firewall' ),
136
  'subtitle_import_file' => __( 'Upload an exported options file you downloaded from another site', 'wp-simple-firewall' ),
137
  'select_import_file' => __( 'Select file to import options from', 'wp-simple-firewall' ),
109
  }
110
  }
111
 
112
+ public function buildInsightsVars() :array {
113
+ /** @var Plugin\ModCon $mod */
114
+ $mod = $this->getMod();
 
 
 
115
  return [
116
  'vars' => [
117
+ 'file_upload_nonce' => $mod->getNonceActionData( 'import_file_upload' ),
118
+ 'form_action' => $mod->getUrl_AdminPage(),
119
+ 'related_hrefs' => [
120
+ [
121
+ 'href' => $mod->getUrl_DirectLinkToSection( 'section_importexport' ),
122
+ 'title' => __( 'Import/Export Settings', 'wp-simple-firewall' ),
123
+ ]
124
+ ]
125
  ],
126
  'ajax' => [
127
+ 'import_from_site' => $mod->getAjaxActionData( 'import_from_site', true ),
128
  ],
129
  'flags' => [
130
  'can_importexport' => $this->getCon()->isPremiumActive(),
133
  'export_file_download' => $this->createExportFileDownloadLink()
134
  ],
135
  'strings' => [
136
+ 'tab_by_file' => __( 'Import From File', 'wp-simple-firewall' ),
137
+ 'tab_by_site' => __( 'Import From Another Site', 'wp-simple-firewall' ),
138
  'title_import_file' => __( 'Import From File', 'wp-simple-firewall' ),
139
  'subtitle_import_file' => __( 'Upload an exported options file you downloaded from another site', 'wp-simple-firewall' ),
140
  'select_import_file' => __( 'Select file to import options from', 'wp-simple-firewall' ),
src/lib/src/Modules/Plugin/Lib/ImportExport/NotifyWhitelist.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\ImportExport;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
 
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class NotifyWhitelist {
@@ -10,17 +11,17 @@ class NotifyWhitelist {
10
  use ModConsumer;
11
 
12
  public function run() {
13
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $oMod */
14
- $oMod = $this->getMod();
15
  $oHttpReq = Services::HttpRequest();
16
 
17
- if ( $oMod->hasImportExportWhitelistSites() ) {
18
 
19
  $aQuery = [
20
  'blocking' => false,
21
  'body' => [ 'shield_action' => 'importexport_updatenotified' ]
22
  ];
23
- foreach ( $oMod->getImportExportWhitelist() as $sUrl ) {
24
  $oHttpReq->get( $sUrl, $aQuery );
25
  }
26
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\ImportExport;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\ModCon;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class NotifyWhitelist {
11
  use ModConsumer;
12
 
13
  public function run() {
14
+ /** @var ModCon $mod */
15
+ $mod = $this->getMod();
16
  $oHttpReq = Services::HttpRequest();
17
 
18
+ if ( $mod->hasImportExportWhitelistSites() ) {
19
 
20
  $aQuery = [
21
  'blocking' => false,
22
  'body' => [ 'shield_action' => 'importexport_updatenotified' ]
23
  ];
24
+ foreach ( $mod->getImportExportWhitelist() as $sUrl ) {
25
  $oHttpReq->get( $sUrl, $aQuery );
26
  }
27
 
src/lib/src/Modules/Plugin/Lib/PluginTelemetry.php ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib;
4
+
5
+ use FernleafSystems\Utilities\Logic\OneTimeExecute;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\ModCon;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
9
+ use FernleafSystems\Wordpress\Services\Services;
10
+
11
+ class PluginTelemetry {
12
+
13
+ use ModConsumer;
14
+ use OneTimeExecute;
15
+
16
+ protected function canRun() {
17
+ /** @var Plugin\Options $opts */
18
+ $opts = $this->getOptions();
19
+ return $opts->isTrackingEnabled() || !$opts->isTrackingPermissionSet();
20
+ }
21
+
22
+ protected function run() {
23
+ $con = $this->getCon();
24
+ switch ( $con->getShieldAction() ) {
25
+ case 'dump_tracking_data':
26
+ add_action( 'wp_loaded', function () {
27
+ if ( $this->getCon()->isPluginAdmin() ) {
28
+ echo sprintf( '<pre><code>%s</code></pre>',
29
+ print_r( $this->collectTrackingData(), true ) );
30
+ die();
31
+ }
32
+ } );
33
+ break;
34
+ default:
35
+ break;
36
+ }
37
+
38
+ add_action( $this->getCon()->prefix( 'daily_cron' ), [ $this, 'runDailyCron' ] );
39
+ }
40
+
41
+ public function runDailyCron() {
42
+ $this->sendTrackingData();
43
+ }
44
+
45
+ private function sendTrackingData() {
46
+ /** @var Plugin\Options $opts */
47
+ $opts = $this->getOptions();
48
+
49
+ $success = false;
50
+
51
+ $bCanSend = Services::Request()
52
+ ->carbon()
53
+ ->subWeek()->timestamp
54
+ > (int)$opts->getOpt( 'tracking_last_sent_at', 0 );
55
+ if ( $bCanSend && $opts->isTrackingEnabled() ) {
56
+
57
+ $data = $this->collectTrackingData();
58
+ if ( !empty( $data ) ) {
59
+ $opts->setOpt( 'tracking_last_sent_at', Services::Request()->ts() );
60
+ $success = Services::HttpRequest()->post(
61
+ $opts->getDef( 'tracking_post_url' ),
62
+ [
63
+ 'timeout' => 20,
64
+ 'redirection' => 5,
65
+ 'httpversion' => '1.1',
66
+ 'blocking' => true,
67
+ 'body' => [ 'tracking_data' => $data ],
68
+ 'user-agent' => 'SHIELD/'.$this->getCon()->getVersion().';'
69
+ ]
70
+ );
71
+ }
72
+ }
73
+
74
+ return $success;
75
+ }
76
+
77
+ /**
78
+ * @return array[]
79
+ */
80
+ public function collectTrackingData() :array {
81
+ $con = $this->getCon();
82
+
83
+ $data = $this->getBaseTrackingData();
84
+ foreach ( $con->modules as $mod ) {
85
+ $data[ $mod->getSlug() ] = $this->buildOptionsDataForMod( $mod );
86
+ }
87
+
88
+ if ( !empty( $data[ 'events' ] ) ) {
89
+ $data[ 'events' ][ 'stats' ] = $con->getModule_Events()
90
+ ->getDbHandler_Events()
91
+ ->getQuerySelector()
92
+ ->sumAllEvents();
93
+ }
94
+ if ( !empty( $data[ 'login_protect' ] ) ) {
95
+ $data[ 'login_protect' ][ 'options' ][ 'email_can_send_verified_at' ] =
96
+ $data[ 'login_protect' ][ 'options' ][ 'email_can_send_verified_at' ] > 0 ? 1 : 0;
97
+ }
98
+ if ( !empty( $data[ 'admin_access_restriction' ] ) ) {
99
+ $keys= [
100
+ 'admin_access_restrict_plugins',
101
+ 'admin_access_restrict_themes',
102
+ 'admin_access_restrict_posts'
103
+ ];
104
+ foreach ( $keys as $key ) {
105
+ $data[ 'admin_access_restriction' ][ 'options' ][ $key ]
106
+ = empty( $data[ 'admin_access_restriction' ][ 'options' ][ $key ] ) ? 0 : 1;
107
+ }
108
+ }
109
+ if ( !empty( $data[ 'plugin' ] ) ) {
110
+ /** @var Plugin\ModCon $mod */
111
+ $mod = $this->getMod();
112
+ $data[ 'plugin' ][ 'options' ][ 'unique_installation_id' ] = $mod->getPluginInstallationId();
113
+ $data[ 'plugin' ][ 'options' ][ 'new_unique_installation_id' ] = $con->getSiteInstallationId();
114
+ }
115
+
116
+ return $data;
117
+ }
118
+
119
+ /**
120
+ * @param ModCon $mod
121
+ * @return array
122
+ */
123
+ private function buildOptionsDataForMod( $mod ) :array {
124
+ $data = [];
125
+
126
+ $opts = $mod->getOptions();
127
+ $optionsData = $opts->getOptionsForTracking();
128
+ foreach ( $optionsData as $opt => $mValue ) {
129
+ unset( $optionsData[ $opt ] );
130
+ // some cleaning to ensure we don't have disallowed characters
131
+ $opt = preg_replace( '#[^_a-z]#', '', strtolower( $opt ) );
132
+ if ( $opts->getOptionType( $opt ) == 'checkbox' ) { // only want a boolean 1 or 0
133
+ $optionsData[ $opt ] = (int)( $mValue == 'Y' );
134
+ }
135
+ else {
136
+ $optionsData[ $opt ] = $mValue;
137
+ }
138
+ }
139
+
140
+ $data[ 'options' ] = $optionsData;
141
+
142
+ return $data;
143
+ }
144
+
145
+ private function getBaseTrackingData() :array {
146
+ $WP = Services::WpGeneral();
147
+ $WPP = Services::WpPlugins();
148
+ return [
149
+ 'env' => [
150
+ 'options' => [
151
+ 'php' => Services::Data()->getPhpVersionCleaned(),
152
+ 'wordpress' => $WP->getVersion(),
153
+ 'slug' => $this->getCon()->getPluginSlug(),
154
+ 'version' => $this->getCon()->getVersion(),
155
+ 'is_wpms' => $WP->isMultisite() ? 1 : 0,
156
+ 'is_cp' => $WP->isClassicPress() ? 1 : 0,
157
+ 'ssl' => is_ssl() ? 1 : 0,
158
+ 'locale' => get_locale(),
159
+ 'plugins_total' => count( $WPP->getPlugins() ),
160
+ 'plugins_active' => count( $WPP->getActivePlugins() ),
161
+ 'plugins_updates' => count( $WPP->getUpdates() )
162
+ ]
163
+ ]
164
+ ];
165
+ }
166
+ }
src/lib/src/Modules/Plugin/ModCon.php ADDED
@@ -0,0 +1,549 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+ use FernleafSystems\Wordpress\Services\Utilities\Net\VisitorIpDetection;
9
+
10
+ class ModCon extends BaseShield\ModCon {
11
+
12
+ /**
13
+ * @var Lib\ImportExport\ImportExportController
14
+ */
15
+ private $importExportCon;
16
+
17
+ /**
18
+ * @var Components\PluginBadge
19
+ */
20
+ private $pluginBadgeCon;
21
+
22
+ /**
23
+ * @var Shield\Utilities\ReCaptcha\Enqueue
24
+ */
25
+ private $oCaptchaEnqueue;
26
+
27
+ /**
28
+ * @var Shield\ShieldNetApi\ShieldNetApiController
29
+ */
30
+ private $shieldNetCon;
31
+
32
+ public function getImpExpController() :Lib\ImportExport\ImportExportController {
33
+ if ( !isset( $this->importExportCon ) ) {
34
+ $this->importExportCon = ( new Lib\ImportExport\ImportExportController() )
35
+ ->setMod( $this );
36
+ }
37
+ return $this->importExportCon;
38
+ }
39
+
40
+ public function getPluginBadgeCon() :Components\PluginBadge {
41
+ if ( !isset( $this->pluginBadgeCon ) ) {
42
+ $this->pluginBadgeCon = ( new Components\PluginBadge() )
43
+ ->setMod( $this );
44
+ }
45
+ return $this->pluginBadgeCon;
46
+ }
47
+
48
+ public function getShieldNetApiController() :Shield\ShieldNetApi\ShieldNetApiController {
49
+ if ( !isset( $this->shieldNetCon ) ) {
50
+ $this->shieldNetCon = ( new Shield\ShieldNetApi\ShieldNetApiController() )
51
+ ->setMod( $this );
52
+ }
53
+ return $this->shieldNetCon;
54
+ }
55
+
56
+ protected function doPostConstruction() {
57
+ $this->setVisitorIpSource();
58
+ }
59
+
60
+ protected function preProcessOptions() {
61
+ ( new Lib\Captcha\CheckCaptchaSettings() )
62
+ ->setMod( $this )
63
+ ->checkAll();
64
+ }
65
+
66
+ public function deleteAllPluginCrons() {
67
+ $con = $this->getCon();
68
+ $wpCrons = Services::WpCron();
69
+
70
+ foreach ( $wpCrons->getCrons() as $nKey => $aCronArgs ) {
71
+ foreach ( $aCronArgs as $sHook => $aCron ) {
72
+ if ( strpos( $sHook, $con->prefix() ) === 0
73
+ || strpos( $sHook, $con->prefixOption() ) === 0 ) {
74
+ $wpCrons->deleteCronJob( $sHook );
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ public function onPluginShutdown() {
81
+ $preferred = Services::IP()->getIpDetector()->getLastSuccessfulSource();
82
+ if ( !empty( $preferred ) ) {
83
+ $this->getOptions()->setOpt( 'last_ip_detect_source', $preferred );
84
+ }
85
+ parent::onPluginShutdown();
86
+ }
87
+
88
+ public function onWpInit() {
89
+ parent::onWpInit();
90
+ $this->getImportExportSecretKey();
91
+ }
92
+
93
+ /**
94
+ * Forcefully sets preferred Visitor IP source in the Data component for use throughout the plugin
95
+ */
96
+ private function setVisitorIpSource() {
97
+ /** @var Options $opts */
98
+ $opts = $this->getOptions();
99
+ if ( !$opts->isIpSourceAutoDetect() ) {
100
+ Services::IP()->setIpDetector(
101
+ ( new VisitorIpDetection() )->setPreferredSource( $opts->getIpSource() )
102
+ );
103
+ }
104
+ }
105
+
106
+ protected function handleModAction( string $action ) {
107
+ switch ( $action ) {
108
+
109
+ case 'export_file_download':
110
+ header( 'Set-Cookie: fileDownload=true; path=/' );
111
+ ( new Lib\ImportExport\Export() )
112
+ ->setMod( $this )
113
+ ->toFile();
114
+ break;
115
+
116
+ case 'import_file_upload':
117
+ try {
118
+ ( new Lib\ImportExport\Import() )
119
+ ->setMod( $this )
120
+ ->fromFileUpload();
121
+ $bSuccess = true;
122
+ $sMessage = __( 'Options imported successfully', 'wp-simple-firewall' );
123
+ }
124
+ catch ( \Exception $oE ) {
125
+ $bSuccess = false;
126
+ $sMessage = $oE->getMessage();
127
+ }
128
+ $this->setFlashAdminNotice( $sMessage, !$bSuccess );
129
+ Services::Response()->redirect(
130
+ $this->getCon()->getModule_Insights()->getUrl_SubInsightsPage( 'importexport' )
131
+ );
132
+ break;
133
+
134
+ default:
135
+ break;
136
+ }
137
+ }
138
+
139
+ public function getCanSiteCallToItself() :bool {
140
+ $oHttp = Services::HttpRequest();
141
+ return $oHttp->get( Services::WpGeneral()->getHomeUrl(), [ 'timeout' => 20 ] )
142
+ && $oHttp->lastResponse->getCode() < 400;
143
+ }
144
+
145
+ public function getActivePluginFeatures() :array {
146
+ $aActiveFeatures = $this->getDef( 'active_plugin_features' );
147
+
148
+ $aPluginFeatures = [];
149
+ if ( !empty( $aActiveFeatures ) && is_array( $aActiveFeatures ) ) {
150
+
151
+ foreach ( $aActiveFeatures as $nPosition => $aFeature ) {
152
+ if ( isset( $aFeature[ 'hidden' ] ) && $aFeature[ 'hidden' ] ) {
153
+ continue;
154
+ }
155
+ $aPluginFeatures[ $aFeature[ 'slug' ] ] = $aFeature;
156
+ }
157
+ }
158
+ return $aPluginFeatures;
159
+ }
160
+
161
+ public function getLinkToTrackingDataDump() :string {
162
+ return add_query_arg(
163
+ [ 'shield_action' => 'dump_tracking_data' ],
164
+ Services::WpGeneral()->getAdminUrl()
165
+ );
166
+ }
167
+
168
+ public function getPluginReportEmail() :string {
169
+ $e = (string)$this->getOptions()->getOpt( 'block_send_email_address' );
170
+ if ( $this->isPremium() ) {
171
+ $e = apply_filters( $this->getCon()->prefix( 'report_email' ), $e );
172
+ }
173
+ $e = trim( $e );
174
+ return Services::Data()->validEmail( $e ) ? $e : Services::WpGeneral()->getSiteAdminEmail();
175
+ }
176
+
177
+ /**
178
+ * This is the point where you would want to do any options verification
179
+ */
180
+ protected function doPrePluginOptionsSave() {
181
+ /** @var Options $oOpts */
182
+ $oOpts = $this->getOptions();
183
+
184
+ $this->storeRealInstallDate();
185
+
186
+ if ( $oOpts->isTrackingEnabled() && !$oOpts->isTrackingPermissionSet() ) {
187
+ $oOpts->setOpt( 'tracking_permission_set_at', Services::Request()->ts() );
188
+ }
189
+
190
+ $this->cleanRecaptchaKey( 'google_recaptcha_site_key' );
191
+ $this->cleanRecaptchaKey( 'google_recaptcha_secret_key' );
192
+
193
+ $this->cleanImportExportWhitelistUrls();
194
+ $this->cleanImportExportMasterImportUrl();
195
+
196
+ $this->setPluginInstallationId();
197
+ }
198
+
199
+ public function getFirstInstallDate() :int {
200
+ return (int)Services::WpGeneral()->getOption( $this->getCon()->prefixOption( 'install_date' ) );
201
+ }
202
+
203
+ public function getInstallDate() :int {
204
+ return (int)$this->getOptions()->getOpt( 'installation_time', 0 );
205
+ }
206
+
207
+ public function isShowAdvanced() :bool {
208
+ return $this->getOptions()->isOpt( 'show_advanced', 'Y' );
209
+ }
210
+
211
+ /**
212
+ * @return string
213
+ */
214
+ public function getOpenSslPrivateKey() {
215
+ $opts = $this->getOptions();
216
+ $key = null;
217
+ $oEnc = Services::Encrypt();
218
+ if ( $oEnc->isSupportedOpenSslDataEncryption() ) {
219
+ $key = $opts->getOpt( 'openssl_private_key' );
220
+ if ( empty( $key ) ) {
221
+ try {
222
+ $aKeys = $oEnc->createNewPrivatePublicKeyPair();
223
+ if ( !empty( $aKeys[ 'private' ] ) ) {
224
+ $key = $aKeys[ 'private' ];
225
+ $opts->setOpt( 'openssl_private_key', base64_encode( $key ) );
226
+ $this->saveModOptions();
227
+ }
228
+ }
229
+ catch ( \Exception $e ) {
230
+ }
231
+ }
232
+ else {
233
+ $key = base64_decode( $key );
234
+ }
235
+ }
236
+ return $key;
237
+ }
238
+
239
+ /**
240
+ * @return string|null
241
+ */
242
+ public function getOpenSslPublicKey() {
243
+ $key = null;
244
+ if ( $this->hasOpenSslPrivateKey() ) {
245
+ try {
246
+ $key = Services::Encrypt()->getPublicKeyFromPrivateKey( $this->getOpenSslPrivateKey() );
247
+ }
248
+ catch ( \Exception $e ) {
249
+ }
250
+ }
251
+ return $key;
252
+ }
253
+
254
+ public function hasOpenSslPrivateKey() :bool {
255
+ return !empty( $this->getOpenSslPrivateKey() );
256
+ }
257
+
258
+ /**
259
+ * @return int - the real install timestamp
260
+ */
261
+ public function storeRealInstallDate() {
262
+ $WP = Services::WpGeneral();
263
+ $ts = Services::Request()->ts();
264
+
265
+ $sOptKey = $this->getCon()->prefixOption( 'install_date' );
266
+
267
+ $nWpDate = $WP->getOption( $sOptKey );
268
+ if ( empty( $nWpDate ) ) {
269
+ $nWpDate = $ts;
270
+ }
271
+
272
+ $nPluginDate = $this->getInstallDate();
273
+ if ( $nPluginDate == 0 ) {
274
+ $nPluginDate = $ts;
275
+ }
276
+
277
+ $nFinal = min( $nPluginDate, $nWpDate );
278
+ $WP->updateOption( $sOptKey, $nFinal );
279
+ $this->getOptions()->setOpt( 'installation_time', $nPluginDate );
280
+
281
+ return $nFinal;
282
+ }
283
+
284
+ /**
285
+ * @param string $optionKey
286
+ */
287
+ protected function cleanRecaptchaKey( $optionKey ) {
288
+ $opts = $this->getOptions();
289
+ $sCaptchaKey = trim( (string)$opts->getOpt( $optionKey, '' ) );
290
+ $nSpacePos = strpos( $sCaptchaKey, ' ' );
291
+ if ( $nSpacePos !== false ) {
292
+ $sCaptchaKey = substr( $sCaptchaKey, 0, $nSpacePos + 1 ); // cut off the string if there's spaces
293
+ }
294
+ $sCaptchaKey = preg_replace( '#[^0-9a-zA-Z_-]#', '', $sCaptchaKey ); // restrict character set
295
+ // if ( strlen( $sCaptchaKey ) != 40 ) {
296
+ // $sCaptchaKey = ''; // need to verify length is 40.
297
+ // }
298
+ $opts->setOpt( $optionKey, $sCaptchaKey );
299
+ }
300
+
301
+ /**
302
+ * Ensure we always a valid installation ID.
303
+ *
304
+ * @return string
305
+ * @deprecated but still used because it aligns with stats collection
306
+ */
307
+ public function getPluginInstallationId() {
308
+ $ID = $this->getOptions()->getOpt( 'unique_installation_id', '' );
309
+
310
+ if ( !$this->isValidInstallId( $ID ) ) {
311
+ $ID = $this->setPluginInstallationId();
312
+ }
313
+ return $ID;
314
+ }
315
+
316
+ public function getActivateLength() :int {
317
+ return Services::Request()->ts() - (int)$this->getOptions()->getOpt( 'activated_at', 0 );
318
+ }
319
+
320
+ /**
321
+ * hidden 20200121
322
+ * @return bool
323
+ */
324
+ public function getIfShowIntroVideo() {
325
+ return false && ( $this->getActivateLength() < 8 )
326
+ && ( Services::Request()->ts() - $this->getInstallDate() < 15 );
327
+ }
328
+
329
+ public function getTourManager() :Lib\TourManager {
330
+ return ( new Lib\TourManager() )->setMod( $this );
331
+ }
332
+
333
+ public function setActivatedAt() {
334
+ $this->getOptions()->setOpt( 'activated_at', Services::Request()->ts() );
335
+ }
336
+
337
+ /**
338
+ * @param string $newID - leave empty to reset if the current isn't valid
339
+ * @return string
340
+ */
341
+ protected function setPluginInstallationId( $newID = null ) {
342
+ // only reset if it's not of the correct type
343
+ if ( !$this->isValidInstallId( $newID ) ) {
344
+ $newID = $this->genInstallId();
345
+ }
346
+ $this->getOptions()->setOpt( 'unique_installation_id', $newID );
347
+ return $newID;
348
+ }
349
+
350
+ protected function genInstallId() :string {
351
+ return sha1(
352
+ $this->getInstallDate()
353
+ .Services::WpGeneral()->getWpUrl()
354
+ .Services::WpDb()->getPrefix()
355
+ );
356
+ }
357
+
358
+ public function hasImportExportWhitelistSites() :bool {
359
+ return count( $this->getImportExportWhitelist() ) > 0;
360
+ }
361
+
362
+ /**
363
+ * @return string[]
364
+ */
365
+ public function getImportExportWhitelist() :array {
366
+ $list = $this->getOptions()->getOpt( 'importexport_whitelist', [] );
367
+ return is_array( $list ) ? $list : [];
368
+ }
369
+
370
+ /**
371
+ * @return string
372
+ */
373
+ protected function getImportExportSecretKey() {
374
+ $opts = $this->getOptions();
375
+ $ID = $opts->getOpt( 'importexport_secretkey', '' );
376
+ if ( empty( $ID ) || $this->isImportExportSecretKeyExpired() ) {
377
+ $ID = sha1( $this->getCon()->getSiteInstallationId().wp_rand( 0, PHP_INT_MAX ) );
378
+ $opts->setOpt( 'importexport_secretkey', $ID )
379
+ ->setOpt( 'importexport_secretkey_expires_at', Services::Request()->ts() + HOUR_IN_SECONDS );
380
+ }
381
+ return $ID;
382
+ }
383
+
384
+ protected function isImportExportSecretKeyExpired() :bool {
385
+ return Services::Request()->ts() >
386
+ $this->getOptions()->getOpt( 'importexport_secretkey_expires_at' );
387
+ }
388
+
389
+ public function isImportExportWhitelistNotify() :bool {
390
+ return $this->getOptions()->isOpt( 'importexport_whitelist_notify', 'Y' );
391
+ }
392
+
393
+ /**
394
+ * @param string $sUrl
395
+ * @return $this
396
+ */
397
+ public function addUrlToImportExportWhitelistUrls( $sUrl ) {
398
+ $sUrl = Services::Data()->validateSimpleHttpUrl( $sUrl );
399
+ if ( $sUrl !== false ) {
400
+ $aWhitelistUrls = $this->getImportExportWhitelist();
401
+ $aWhitelistUrls[] = $sUrl;
402
+ $this->getOptions()->setOpt( 'importexport_whitelist', $aWhitelistUrls );
403
+ $this->saveModOptions();
404
+ }
405
+ return $this;
406
+ }
407
+
408
+ /**
409
+ * @param string $url
410
+ * @return $this
411
+ */
412
+ public function removeUrlFromImportExportWhitelistUrls( $url ) {
413
+ $url = Services::Data()->validateSimpleHttpUrl( $url );
414
+ if ( $url !== false ) {
415
+ $aWhitelistUrls = $this->getImportExportWhitelist();
416
+ $sKey = array_search( $url, $aWhitelistUrls );
417
+ if ( $sKey !== false ) {
418
+ unset( $aWhitelistUrls[ $sKey ] );
419
+ }
420
+ $this->getOptions()->setOpt( 'importexport_whitelist', $aWhitelistUrls );
421
+ $this->saveModOptions();
422
+ }
423
+ return $this;
424
+ }
425
+
426
+ /**
427
+ * @param string $sKey
428
+ * @return bool
429
+ */
430
+ public function isImportExportSecretKey( $sKey ) :bool {
431
+ return !empty( $sKey ) && $this->getImportExportSecretKey() == $sKey;
432
+ }
433
+
434
+ protected function cleanImportExportWhitelistUrls() {
435
+ $oDP = Services::Data();
436
+
437
+ $aCleaned = [];
438
+ $aWhitelistUrls = $this->getImportExportWhitelist();
439
+ foreach ( $aWhitelistUrls as $nKey => $sUrl ) {
440
+
441
+ $sUrl = $oDP->validateSimpleHttpUrl( $sUrl );
442
+ if ( $sUrl !== false ) {
443
+ $aCleaned[] = $sUrl;
444
+ }
445
+ }
446
+ $this->getOptions()->setOpt( 'importexport_whitelist', array_unique( $aCleaned ) );
447
+ }
448
+
449
+ protected function cleanImportExportMasterImportUrl() {
450
+ /** @var Options $oOpts */
451
+ $oOpts = $this->getOptions();
452
+ $url = Services::Data()->validateSimpleHttpUrl( $oOpts->getImportExportMasterImportUrl() );
453
+ if ( $url === false ) {
454
+ $url = '';
455
+ }
456
+ $this->getOptions()->setOpt( 'importexport_masterurl', $url );
457
+ }
458
+
459
+ /**
460
+ * @param string $url
461
+ * @return $this
462
+ */
463
+ public function setImportExportMasterImportUrl( $url ) {
464
+ $this->getOptions()->setOpt( 'importexport_masterurl', $url ); //saving will clean the URL
465
+ return $this->saveModOptions();
466
+ }
467
+
468
+ /**
469
+ * @param string $sId
470
+ * @return bool
471
+ */
472
+ protected function isValidInstallId( $sId ) {
473
+ return !empty( $sId ) && is_string( $sId ) && strlen( $sId ) == 40;
474
+ }
475
+
476
+ public function isXmlrpcBypass() :bool {
477
+ return $this->getOptions()->isOpt( 'enable_xmlrpc_compatibility', 'Y' );
478
+ }
479
+
480
+ public function getCanAdminNotes() :bool {
481
+ return Services::WpUsers()->isUserAdmin();
482
+ }
483
+
484
+ public function insertCustomJsVars_Admin() {
485
+ parent::insertCustomJsVars_Admin();
486
+
487
+ $con = $this->getCon();
488
+ if ( Services::WpPost()->isCurrentPage( 'plugins.php' ) ) {
489
+ $sFile = $con->getPluginBaseFile();
490
+ wp_localize_script(
491
+ $con->prefix( 'global-plugin' ),
492
+ 'icwp_wpsf_vars_plugin',
493
+ [
494
+ 'file' => $sFile,
495
+ 'ajax' => [
496
+ 'send_deactivate_survey' => $this->getAjaxActionData( 'send_deactivate_survey' ),
497
+ ],
498
+ 'hrefs' => [
499
+ 'deactivate' => Services::WpPlugins()->getUrl_Deactivate( $sFile ),
500
+ ],
501
+ ]
502
+ );
503
+ wp_enqueue_script( 'jquery-ui-dialog' ); // jquery and jquery-ui should be dependencies, didn't check though...
504
+ wp_enqueue_style( 'wp-jquery-ui-dialog' );
505
+ }
506
+
507
+ wp_localize_script(
508
+ $con->prefix( 'plugin' ),
509
+ 'icwp_wpsf_vars_tourmanager',
510
+ [ 'ajax' => $this->getAjaxActionData( 'mark_tour_finished' ) ]
511
+ );
512
+ wp_localize_script(
513
+ $con->prefix( 'plugin' ),
514
+ 'icwp_wpsf_vars_plugin',
515
+ [
516
+ 'strings' => [
517
+ 'downloading_file' => __( 'Downloading file, please wait...', 'wp-simple-firewall' ),
518
+ 'problem_downloading_file' => __( 'There was a problem downloading the file.', 'wp-simple-firewall' ),
519
+ ],
520
+ ]
521
+ );
522
+ }
523
+
524
+ public function getDbHandler_GeoIp() :Shield\Databases\GeoIp\Handler {
525
+ return $this->getDbH( 'geoip' );
526
+ }
527
+
528
+ public function getDbHandler_Notes() :Shield\Databases\AdminNotes\Handler {
529
+ return $this->getDbH( 'notes' );
530
+ }
531
+
532
+ public function getCaptchaEnqueue() :Shield\Utilities\ReCaptcha\Enqueue {
533
+ if ( !isset( $this->oCaptchaEnqueue ) ) {
534
+ $this->oCaptchaEnqueue = ( new Shield\Utilities\ReCaptcha\Enqueue() )->setMod( $this );
535
+ }
536
+ return $this->oCaptchaEnqueue;
537
+ }
538
+
539
+ protected function getNamespaceBase() :string {
540
+ return 'Plugin';
541
+ }
542
+
543
+ /**
544
+ * @return string
545
+ */
546
+ public function getSurveyEmail() {
547
+ return base64_decode( $this->getDef( 'survey_email' ) );
548
+ }
549
+ }
src/lib/src/Modules/Plugin/Options.php CHANGED
@@ -2,10 +2,10 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
- class Options extends Base\ShieldOptions {
9
 
10
  public function getCaptchaConfig() :array {
11
  return [
@@ -47,17 +47,11 @@ class Options extends Base\ShieldOptions {
47
  return $this->getIpSource() == 'AUTO_DETECT_IP';
48
  }
49
 
50
- /**
51
- * @return bool
52
- */
53
- public function isPluginGloballyDisabled() {
54
  return !$this->isOpt( 'global_enable_plugin_features', 'Y' );
55
  }
56
 
57
- /**
58
- * @return bool
59
- */
60
- public function isTrackingEnabled() {
61
  return $this->isOpt( 'enable_tracking', 'Y' );
62
  }
63
 
@@ -97,28 +91,4 @@ class Options extends Base\ShieldOptions {
97
  public function setVisitorAddressSource( $sSource ) {
98
  return $this->setOpt( 'visitor_address_source', $sSource );
99
  }
100
-
101
- /**
102
- * @return bool
103
- * @deprecated 10.0
104
- */
105
- public function isOnFloatingPluginBadge() {
106
- return $this->isOpt( 'display_plugin_badge', 'Y' );
107
- }
108
-
109
- /**
110
- * @return string
111
- * @deprecated 10.0
112
- */
113
- public function getDbTable_GeoIp() :string {
114
- return $this->getCon()->prefixOption( $this->getDef( 'geoip_table_name' ) );
115
- }
116
-
117
- /**
118
- * @return string
119
- * @deprecated 10.0
120
- */
121
- public function getDbTable_Notes() :string {
122
- return $this->getCon()->prefixOption( $this->getDef( 'db_notes_name' ) );
123
- }
124
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
+ class Options extends BaseShield\Options {
9
 
10
  public function getCaptchaConfig() :array {
11
  return [
47
  return $this->getIpSource() == 'AUTO_DETECT_IP';
48
  }
49
 
50
+ public function isPluginGloballyDisabled() :bool {
 
 
 
51
  return !$this->isOpt( 'global_enable_plugin_features', 'Y' );
52
  }
53
 
54
+ public function isTrackingEnabled() :bool {
 
 
 
55
  return $this->isOpt( 'enable_tracking', 'Y' );
56
  }
57
 
91
  public function setVisitorAddressSource( $sSource ) {
92
  return $this->setOpt( 'visitor_address_source', $sSource );
93
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  }
src/lib/src/Modules/Plugin/Processor.php ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\PluginTelemetry;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Options\CleanStorage;
8
+ use FernleafSystems\Wordpress\Services\Services;
9
+
10
+ class Processor extends BaseShield\Processor {
11
+
12
+ protected function run() {
13
+ $con = $this->getCon();
14
+ /** @var ModCon $mod */
15
+ $mod = $this->getMod();
16
+ /** @var Options $opts */
17
+ $opts = $this->getOptions();
18
+
19
+ $this->removePluginConflicts();
20
+ ( new Lib\OverrideLocale() )
21
+ ->setMod( $this->getMod() )
22
+ ->run();
23
+
24
+ $mod->getPluginBadgeCon()->run();
25
+
26
+ ( new PluginTelemetry() )
27
+ ->setMod( $mod )
28
+ ->execute();
29
+
30
+ if ( $opts->isImportExportPermitted() ) {
31
+ $mod->getImpExpController()->run();
32
+ }
33
+
34
+ add_filter( $con->prefix( 'delete_on_deactivate' ), function ( $isDelete ) use ( $opts ) {
35
+ return $isDelete || $opts->isOpt( 'delete_on_deactivate', 'Y' );
36
+ } );
37
+
38
+ add_action( $con->prefix( 'dashboard_widget_content' ), function () {
39
+ $this->printDashboardWidget();
40
+ }, 11 );
41
+ }
42
+
43
+ private function printDashboardWidget() {
44
+ $con = $this->getCon();
45
+ /** @var Options $opts */
46
+ $opts = $this->getOptions();
47
+ $labels = $con->getLabels();
48
+
49
+ echo $this->getMod()->renderTemplate(
50
+ 'snippets/widget_dashboard_plugin.php',
51
+ [
52
+ 'install_days' => sprintf( __( 'Days Installed: %s', 'wp-simple-firewall' ), $opts->getInstallationDays() ),
53
+ 'footer' => sprintf( __( '%s is provided by %s', 'wp-simple-firewall' ), $con->getHumanName(),
54
+ sprintf( '<a href="%s" target="_blank">%s</a>', $labels[ 'AuthorURI' ], $labels[ 'Author' ] )
55
+ ),
56
+ 'ip_address' => sprintf( __( 'Your IP address is: %s', 'wp-simple-firewall' ),
57
+ Services::IP()->getRequestIp() )
58
+ ]
59
+ );
60
+ }
61
+
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
+ }
75
+ }
76
+
77
+ ( new CleanStorage() )
78
+ ->setCon( $this->getCon() )
79
+ ->run();
80
+ }
81
+
82
+ /**
83
+ * Lets you remove certain plugin conflicts that might interfere with this plugin
84
+ */
85
+ protected function removePluginConflicts() {
86
+ if ( class_exists( 'AIO_WP_Security' ) && isset( $GLOBALS[ 'aio_wp_security' ] ) ) {
87
+ remove_action( 'init', [ $GLOBALS[ 'aio_wp_security' ], 'wp_security_plugin_init' ], 0 );
88
+ }
89
+ }
90
+ }
src/lib/src/Modules/Plugin/Strings.php CHANGED
@@ -104,6 +104,11 @@ class Strings extends Base\Strings {
104
  $titleShort = __( 'General Options', 'wp-simple-firewall' );
105
  break;
106
 
 
 
 
 
 
107
  case 'section_third_party_captcha' :
108
  $title = __( 'CAPTCHA', 'wp-simple-firewall' );
109
  $titleShort = __( 'CAPTCHA', 'wp-simple-firewall' );
@@ -143,19 +148,19 @@ class Strings extends Base\Strings {
143
  * @throws \Exception
144
  */
145
  public function getOptionStrings( string $key ) :array {
146
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $oMod */
147
- $oMod = $this->getMod();
148
- /** @var Options $oOpts */
149
- $oOpts = $this->getOptions();
150
- $sPlugName = $this->getCon()->getHumanName();
151
 
152
  switch ( $key ) {
153
 
154
  case 'global_enable_plugin_features' :
155
- $name = sprintf( __( 'Enable %s Protection', 'wp-simple-firewall' ), $sPlugName );
156
  $summary = __( 'Switch Off To Disable All Security Protection', 'wp-simple-firewall' );
157
  $desc = [
158
- sprintf( __( "You can keep the security plugin activated, but temporarily disable all protection it provides.", 'wp-simple-firewall' ), $sPlugName ),
159
  sprintf( '<a href="%s">%s</a>',
160
  $this->getCon()->getModule_Insights()->getUrl_SubInsightsPage( 'debug' ),
161
  'Launch Debug Info Page'
@@ -163,13 +168,22 @@ class Strings extends Base\Strings {
163
  ];
164
  break;
165
 
 
 
 
 
 
 
 
 
 
166
  case 'enable_tracking' :
167
  $name = __( 'Anonymous Usage Statistics', 'wp-simple-firewall' );
168
  $summary = __( 'Permit Anonymous Usage Information Gathering', 'wp-simple-firewall' );
169
  $desc = [
170
  __( 'Allows us to gather information on statistics and features in-use across our client installations.', 'wp-simple-firewall' )
171
  .' '.__( 'This information is strictly anonymous and contains no personally, or otherwise, identifiable data.', 'wp-simple-firewall' ),
172
- sprintf( '<a href="%s" target="_blank">%s</a>', $oMod->getLinkToTrackingDataDump(), __( 'Click to see the exact data that would be sent.', 'wp-simple-firewall' ) )
173
  ];
174
  break;
175
 
@@ -180,7 +194,7 @@ class Strings extends Base\Strings {
180
  .'<br />'.__( 'If the option you select becomes unavailable, we will revert to auto detection.', 'wp-simple-firewall' )
181
  .'<br />'.sprintf(
182
  __( 'Current source is: %s (%s)', 'wp-simple-firewall' ),
183
- '<strong>'.$oOpts->getIpSource().'</strong>',
184
  Services::IP()->getRequestIp()
185
  )
186
  .sprintf(
104
  $titleShort = __( 'General Options', 'wp-simple-firewall' );
105
  break;
106
 
107
+ case 'section_integrations' :
108
+ $title = __( '3rd Party Integrations', 'wp-simple-firewall' );
109
+ $titleShort = __( 'Integrations', 'wp-simple-firewall' );
110
+ break;
111
+
112
  case 'section_third_party_captcha' :
113
  $title = __( 'CAPTCHA', 'wp-simple-firewall' );
114
  $titleShort = __( 'CAPTCHA', 'wp-simple-firewall' );
148
  * @throws \Exception
149
  */
150
  public function getOptionStrings( string $key ) :array {
151
+ /** @var ModCon $mod */
152
+ $mod = $this->getMod();
153
+ /** @var Options $opts */
154
+ $opts = $this->getOptions();
155
+ $plugName = $this->getCon()->getHumanName();
156
 
157
  switch ( $key ) {
158
 
159
  case 'global_enable_plugin_features' :
160
+ $name = sprintf( __( 'Enable %s Protection', 'wp-simple-firewall' ), $plugName );
161
  $summary = __( 'Switch Off To Disable All Security Protection', 'wp-simple-firewall' );
162
  $desc = [
163
+ sprintf( __( "You can keep the security plugin activated, but temporarily disable all protection it provides.", 'wp-simple-firewall' ), $plugName ),
164
  sprintf( '<a href="%s">%s</a>',
165
  $this->getCon()->getModule_Insights()->getUrl_SubInsightsPage( 'debug' ),
166
  'Launch Debug Info Page'
168
  ];
169
  break;
170
 
171
+ case 'show_advanced' :
172
+ $name = __( 'Show All Options', 'wp-simple-firewall' );
173
+ $summary = __( 'Show All Options Including Those Marked As Advanced', 'wp-simple-firewall' );
174
+ $desc = [
175
+ __( 'Shield hides advanced options from view to simplify display.', 'wp-simple-firewall' ),
176
+ __( 'Turn this option on to display advanced options at all times.', 'wp-simple-firewall' )
177
+ ];
178
+ break;
179
+
180
  case 'enable_tracking' :
181
  $name = __( 'Anonymous Usage Statistics', 'wp-simple-firewall' );
182
  $summary = __( 'Permit Anonymous Usage Information Gathering', 'wp-simple-firewall' );
183
  $desc = [
184
  __( 'Allows us to gather information on statistics and features in-use across our client installations.', 'wp-simple-firewall' )
185
  .' '.__( 'This information is strictly anonymous and contains no personally, or otherwise, identifiable data.', 'wp-simple-firewall' ),
186
+ sprintf( '<a href="%s" target="_blank">%s</a>', $mod->getLinkToTrackingDataDump(), __( 'Click to see the exact data that would be sent.', 'wp-simple-firewall' ) )
187
  ];
188
  break;
189
 
194
  .'<br />'.__( 'If the option you select becomes unavailable, we will revert to auto detection.', 'wp-simple-firewall' )
195
  .'<br />'.sprintf(
196
  __( 'Current source is: %s (%s)', 'wp-simple-firewall' ),
197
+ '<strong>'.$opts->getIpSource().'</strong>',
198
  Services::IP()->getRequestIp()
199
  )
200
  .sprintf(
src/lib/src/Modules/Plugin/UI.php CHANGED
@@ -2,14 +2,23 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Insights\AdminNotes;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Captcha\CheckCaptchaSettings;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Debug\Collate;
9
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Debug\RecentEvents;
10
  use FernleafSystems\Wordpress\Services\Services;
11
 
12
- class UI extends Base\ShieldUI {
 
 
 
 
 
 
 
 
 
 
13
 
14
  public function buildInsightsVars_Debug() :array {
15
  return [
@@ -25,9 +34,6 @@ class UI extends Base\ShieldUI {
25
  'recent_events' => ( new RecentEvents() )
26
  ->setMod( $this->getMod() )
27
  ->build(),
28
- 'admin_notes' => ( new AdminNotes() )
29
- ->setMod( $this->getMod() )
30
- ->build()
31
  ]
32
  ];
33
  }
@@ -57,10 +63,10 @@ class UI extends Base\ShieldUI {
57
  }
58
 
59
  protected function getSectionWarnings( string $section ) :array {
60
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $mod */
61
  $mod = $this->getMod();
62
  $opts = $this->getOptions();
63
- $aWarnings = [];
64
 
65
  switch ( $section ) {
66
  case 'section_third_party_captcha':
@@ -71,7 +77,7 @@ class UI extends Base\ShieldUI {
71
  ->checkAll();
72
  }
73
  if ( $opts->getOpt( 'captcha_checked_at' ) == 0 ) {
74
- $aWarnings[] = sprintf(
75
  __( "Your captcha key and secret haven't been verified.", 'wp-simple-firewall' ).' '
76
  .__( "Please double-check and make sure you haven't mixed them about, and then re-save.", 'wp-simple-firewall' )
77
  );
@@ -80,6 +86,16 @@ class UI extends Base\ShieldUI {
80
  break;
81
  }
82
 
83
- return $aWarnings;
 
 
 
 
 
 
 
 
 
 
84
  }
85
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Captcha\CheckCaptchaSettings;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Debug\Collate;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Debug\RecentEvents;
9
  use FernleafSystems\Wordpress\Services\Services;
10
 
11
+ class UI extends BaseShield\UI {
12
+
13
+ public function buildInsightsVars_Dashboard() :array {
14
+ return [
15
+ 'content' => [
16
+ 'dashboard_cards' => ( new Insights\DashboardCards() )
17
+ ->setMod( $this->getMod() )
18
+ ->renderAll(),
19
+ ],
20
+ ];
21
+ }
22
 
23
  public function buildInsightsVars_Debug() :array {
24
  return [
34
  'recent_events' => ( new RecentEvents() )
35
  ->setMod( $this->getMod() )
36
  ->build(),
 
 
 
37
  ]
38
  ];
39
  }
63
  }
64
 
65
  protected function getSectionWarnings( string $section ) :array {
66
+ /** @var ModCon $mod */
67
  $mod = $this->getMod();
68
  $opts = $this->getOptions();
69
+ $warnings = [];
70
 
71
  switch ( $section ) {
72
  case 'section_third_party_captcha':
77
  ->checkAll();
78
  }
79
  if ( $opts->getOpt( 'captcha_checked_at' ) == 0 ) {
80
+ $warnings[] = sprintf(
81
  __( "Your captcha key and secret haven't been verified.", 'wp-simple-firewall' ).' '
82
  .__( "Please double-check and make sure you haven't mixed them about, and then re-save.", 'wp-simple-firewall' )
83
  );
86
  break;
87
  }
88
 
89
+ return $warnings;
90
+ }
91
+
92
+ protected function getSettingsRelatedLinks() :array {
93
+ $modInsights = $this->getCon()->getModule_Insights();
94
+ return [
95
+ [
96
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'importexport' ),
97
+ 'title' => __( 'Run Import/Export', 'wp-simple-firewall' ),
98
+ ]
99
+ ];
100
  }
101
  }
src/lib/src/Modules/Plugin/Upgrade.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
4
 
@@ -7,8 +7,8 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
7
  class Upgrade extends Base\Upgrade {
8
 
9
  protected function runEveryUpgrade() {
10
- /** @var \ICWP_WPSF_FeatureHandler_Plugin $oMod */
11
- $oMod = $this->getMod();
12
- $oMod->deleteAllPluginCrons();
13
  }
14
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
4
 
7
  class Upgrade extends Base\Upgrade {
8
 
9
  protected function runEveryUpgrade() {
10
+ /** @var ModCon $mod */
11
+ $mod = $this->getMod();
12
+ $mod->deleteAllPluginCrons();
13
  }
14
  }
src/lib/src/Modules/Plugin/WpCli.php CHANGED
@@ -10,7 +10,7 @@ class WpCli extends Base\WpCli {
10
  /**
11
  * @inheritDoc
12
  */
13
- protected function getCmdHandlers() {
14
  return [
15
  new Plugin\WpCli\ForceOff(),
16
  new Plugin\WpCli\Reset(),
10
  /**
11
  * @inheritDoc
12
  */
13
+ protected function getCmdHandlers() :array {
14
  return [
15
  new Plugin\WpCli\ForceOff(),
16
  new Plugin\WpCli\Reset(),
src/lib/src/Modules/Reporting/Debug.php CHANGED
@@ -7,5 +7,8 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules;
7
  class Debug extends Modules\Base\Debug {
8
 
9
  public function run() {
 
 
 
10
  }
11
  }
7
  class Debug extends Modules\Base\Debug {
8
 
9
  public function run() {
10
+ /** @var ModCon $mod */
11
+ $mod = $this->getMod();
12
+ $mod->getReportingController()->runHourlyCron();
13
  }
14
  }
src/lib/src/Modules/Reporting/Lib/ReportingController.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Lib;
4
 
5
  use FernleafSystems\Utilities\Logic\OneTimeExecute;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Reports as DBReports;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Lib\Reports\Build;
@@ -12,6 +13,7 @@ class ReportingController {
12
 
13
  use Modules\ModConsumer;
14
  use OneTimeExecute;
 
15
 
16
  /**
17
  * @return bool
@@ -23,7 +25,7 @@ class ReportingController {
23
  }
24
 
25
  protected function run() {
26
- add_action( $this->getCon()->prefix( 'hourly_cron' ), [ $this, 'runHourlyCron' ] );
27
  }
28
 
29
  public function runHourlyCron() {
@@ -75,7 +77,7 @@ class ReportingController {
75
  $record->frequency = $report->interval;
76
  $record->interval_end_at = $report->interval_end_at;
77
 
78
- /** @var \ICWP_WPSF_FeatureHandler_Reporting $mod */
79
  $mod = $this->getMod();
80
  return $mod->getDbHandler_Reports()
81
  ->getQueryInserter()
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;
9
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Lib\Reports\Build;
13
 
14
  use Modules\ModConsumer;
15
  use OneTimeExecute;
16
+ use PluginCronsConsumer;
17
 
18
  /**
19
  * @return bool
25
  }
26
 
27
  protected function run() {
28
+ $this->setupCronHooks();
29
  }
30
 
31
  public function runHourlyCron() {
77
  $record->frequency = $report->interval;
78
  $record->interval_end_at = $report->interval_end_at;
79
 
80
+ /** @var Modules\Reporting\ModCon $mod */
81
  $mod = $this->getMod();
82
  return $mod->getDbHandler_Reports()
83
  ->getQueryInserter()
src/lib/src/Modules/Reporting/Lib/Reports/BaseReporter.php CHANGED
@@ -28,11 +28,11 @@ abstract class BaseReporter {
28
  }
29
 
30
  /**
31
- * @param ReportVO $oRep
32
  * @return $this
33
  */
34
- public function setReport( ReportVO $oRep ) {
35
- $this->rep = $oRep;
36
  return $this;
37
  }
38
  }
28
  }
29
 
30
  /**
31
+ * @param ReportVO $rep
32
  * @return $this
33
  */
34
+ public function setReport( ReportVO $rep ) {
35
+ $this->rep = $rep;
36
  return $this;
37
  }
38
  }
src/lib/src/Modules/Reporting/Lib/Reports/Build/BaseBuilder.php CHANGED
@@ -25,17 +25,14 @@ abstract class BaseBuilder {
25
  */
26
  public function build() {
27
  if ( $this->isReadyToSend() ) {
28
- $aData = $this->gather();
29
- if ( !empty( $aData ) ) {
30
- $this->rep->content = $this->render( $aData );
31
  }
32
  }
33
  }
34
 
35
- /**
36
- * @return bool
37
- */
38
- protected function isReadyToSend() {
39
  return !Services::WpGeneral()->isCron()
40
  || empty( $this->rep->previous )
41
  || Services::Request()->ts() > $this->rep->interval_end_at;
@@ -57,6 +54,9 @@ abstract class BaseBuilder {
57
  $oCEnd = Services::Request()->carbon( true )->setTimestamp( $this->rep->interval_end_at );
58
 
59
  switch ( $this->rep->interval ) {
 
 
 
60
  case 'hourly':
61
  $sTime = sprintf( 'The full hour from %s until %s on %s.',
62
  $oCStart->format( 'H:i' ),
25
  */
26
  public function build() {
27
  if ( $this->isReadyToSend() ) {
28
+ $data = $this->gather();
29
+ if ( !empty( $data ) ) {
30
+ $this->rep->content = $this->render( $data );
31
  }
32
  }
33
  }
34
 
35
+ protected function isReadyToSend() :bool {
 
 
 
36
  return !Services::WpGeneral()->isCron()
37
  || empty( $this->rep->previous )
38
  || Services::Request()->ts() > $this->rep->interval_end_at;
54
  $oCEnd = Services::Request()->carbon( true )->setTimestamp( $this->rep->interval_end_at );
55
 
56
  switch ( $this->rep->interval ) {
57
+ case 'no_time': // TODO
58
+ $sTime = __( 'No Time Interval', 'wp-simple-firewall' );
59
+ break;
60
  case 'hourly':
61
  $sTime = sprintf( 'The full hour from %s until %s on %s.',
62
  $oCStart->format( 'H:i' ),
src/lib/src/Modules/Reporting/Lib/Reports/Build/BuilderAlerts.php CHANGED
@@ -3,7 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Lib\Reports\Build;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\BaseReporting;
7
 
8
  class BuilderAlerts extends BaseBuilder {
9
 
@@ -11,20 +11,19 @@ class BuilderAlerts extends BaseBuilder {
11
  * @return string[]
12
  */
13
  protected function gather() :array {
14
- $aReports = [];
15
- foreach ( $this->getCon()->modules as $oMod ) {
16
- $oRepCon = $oMod->getReportingHandler();
17
- if ( $oRepCon instanceof BaseReporting ) {
18
- foreach ( $oRepCon->getAlertReporters() as $oReporter ) {
19
- $aReports = array_merge(
20
- $aReports,
21
- $oReporter->setReport( $this->rep )
22
- ->build()
23
  );
24
  }
25
  }
26
  }
27
- return $aReports;
28
  }
29
 
30
  protected function render( array $aGatheredData ) :string {
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Lib\Reports\Build;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Reporting;
7
 
8
  class BuilderAlerts extends BaseBuilder {
9
 
11
  * @return string[]
12
  */
13
  protected function gather() :array {
14
+ $reports = [];
15
+ foreach ( $this->getCon()->modules as $mod ) {
16
+ $repCon = $mod->getReportingHandler();
17
+ if ( $repCon instanceof Reporting ) {
18
+ foreach ( $repCon->getAlertReporters() as $reporter ) {
19
+ $reports = array_merge(
20
+ $reports,
21
+ $reporter->setReport( $this->rep )->build()
 
22
  );
23
  }
24
  }
25
  }
26
+ return $reports;
27
  }
28
 
29
  protected function render( array $aGatheredData ) :string {
src/lib/src/Modules/Reporting/Lib/Reports/Build/BuilderInfo.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Lib\Reports\Build;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\BaseReporting;
6
 
7
  class BuilderInfo extends BaseBuilder {
8
 
@@ -10,20 +10,19 @@ class BuilderInfo extends BaseBuilder {
10
  * @return string[]
11
  */
12
  protected function gather() :array {
13
- $aReports = [];
14
- foreach ( $this->getCon()->modules as $oMod ) {
15
- $oRepCon = $oMod->getReportingHandler();
16
- if ( $oRepCon instanceof BaseReporting ) {
17
- foreach ( $oRepCon->getInfoReporters() as $oReporter ) {
18
- $aReports = array_merge(
19
- $aReports,
20
- $oReporter->setReport( $this->rep )
21
- ->build()
22
  );
23
  }
24
  }
25
  }
26
- return $aReports;
27
  }
28
 
29
  protected function render( array $aGatheredData ) :string {
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Lib\Reports\Build;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Reporting;
6
 
7
  class BuilderInfo extends BaseBuilder {
8
 
10
  * @return string[]
11
  */
12
  protected function gather() :array {
13
+ $reports = [];
14
+ foreach ( $this->getCon()->modules as $mod ) {
15
+ $repCon = $mod->getReportingHandler();
16
+ if ( $repCon instanceof Reporting ) {
17
+ foreach ( $repCon->getInfoReporters() as $reporter ) {
18
+ $reports = array_merge(
19
+ $reports,
20
+ $reporter->setReport( $this->rep )->build()
 
21
  );
22
  }
23
  }
24
  }
25
+ return $reports;
26
  }
27
 
28
  protected function render( array $aGatheredData ) :string {
src/lib/src/Modules/Reporting/Lib/Reports/CreateReportVO.php CHANGED
@@ -17,12 +17,11 @@ class CreateReportVO {
17
  private $rep;
18
 
19
  /**
20
- * CreateReportVO constructor.
21
- * @param string $sReportType
22
  */
23
- public function __construct( $sReportType ) {
24
  $this->rep = new ReportVO();
25
- $this->rep->type = $sReportType;
26
  }
27
 
28
  /**
@@ -42,19 +41,18 @@ class CreateReportVO {
42
  * @throws \Exception
43
  */
44
  private function setReportInterval() {
45
- /** @var Reporting\Options $oOpts */
46
- $oOpts = $this->getOptions();
47
 
48
  switch ( $this->rep->type ) {
49
  case Reports\Handler::TYPE_ALERT:
50
- $this->rep->interval = $oOpts->getFrequencyAlert();
51
  break;
52
  case Reports\Handler::TYPE_INFO:
53
- $this->rep->interval = $oOpts->getFrequencyInfo();
54
  break;
55
  default:
56
  throw new \Exception( 'Not a supported report type: '.$this->rep->type );
57
- break;
58
  }
59
  return $this;
60
  }
@@ -63,15 +61,14 @@ class CreateReportVO {
63
  * @return $this
64
  */
65
  private function setPreviousReport() {
66
- /** @var \ICWP_WPSF_FeatureHandler_Reporting $oMod */
67
- $oMod = $this->getMod();
68
- /** @var Reports\Select $oSel */
69
- $oSel = $oMod->getDbHandler_Reports()->getQuerySelector();
70
- /** @var Reports\EntryVO $oLast */
71
- $this->rep->previous = $oSel->filterByType( $this->rep->type )
72
- ->filterByFrequency( $this->rep->interval )
73
- ->setOrderBy( 'sent_at', 'DESC' )
74
- ->first();
75
  return $this;
76
  }
77
 
@@ -84,49 +81,52 @@ class CreateReportVO {
84
  */
85
  private function setIntervalBoundaries() {
86
 
87
- $oC = Services::Request()->carbon( true );
88
  $nAddition = -1; // the previous hour, day, week, month
89
 
90
  switch ( $this->rep->interval ) {
91
  // case 'realtime':
92
  // break;
 
 
 
 
93
  case 'hourly':
94
- $oC->addHours( $nAddition );
95
- $nStart = $oC->startOfHour()->timestamp;
96
- $nEnd = $oC->endOfHour()->timestamp;
97
  break;
98
  case 'daily':
99
- $oC->addDays( $nAddition );
100
- $nStart = $oC->startOfDay()->timestamp;
101
- $nEnd = $oC->endOfDay()->timestamp;
102
  break;
103
  case 'weekly':
104
- $oC->addWeeks( $nAddition );
105
- $nStart = $oC->startOfWeek()->timestamp;
106
- $nEnd = $oC->endOfWeek()->timestamp;
107
  break;
108
  case 'monthly':
109
- $oC->addMonths( $nAddition );
110
- $nStart = $oC->startOfMonth()->timestamp;
111
- $nEnd = $oC->endOfMonth()->timestamp;
112
  break;
113
  case 'yearly':
114
- $oC->addYears( $nAddition );
115
- $nStart = $oC->startOfYear()->timestamp;
116
- $nEnd = $oC->endOfYear()->timestamp;
117
  break;
118
  default:
119
  throw new \Exception( 'Not a supported frequency' );
120
- break;
121
  }
122
 
123
  if ( $this->rep->previous instanceof Reports\EntryVO
124
- && $nEnd <= $this->rep->previous->interval_end_at ) {
125
  throw new \Exception( 'Attempting to create a duplicate report based on interval.' );
126
  }
127
 
128
- $this->rep->interval_start_at = $nStart;
129
- $this->rep->interval_end_at = $nEnd;
130
 
131
  return $this;
132
  }
@@ -136,10 +136,10 @@ class CreateReportVO {
136
  * @throws \Exception
137
  */
138
  private function setReportId() {
139
- /** @var \ICWP_WPSF_FeatureHandler_Reporting $oMod */
140
- $oMod = $this->getMod();
141
  /** @var Reports\Select $oSel */
142
- $oSel = $oMod->getDbHandler_Reports()->getQuerySelector();
143
  $nPrevID = $oSel->getLastReportId();
144
  $this->rep->rid = is_numeric( $nPrevID ) ? $nPrevID + 1 : 1;
145
  return $this;
17
  private $rep;
18
 
19
  /**
20
+ * @param string $reportType
 
21
  */
22
+ public function __construct( string $reportType ) {
23
  $this->rep = new ReportVO();
24
+ $this->rep->type = $reportType;
25
  }
26
 
27
  /**
41
  * @throws \Exception
42
  */
43
  private function setReportInterval() {
44
+ /** @var Reporting\Options $opts */
45
+ $opts = $this->getOptions();
46
 
47
  switch ( $this->rep->type ) {
48
  case Reports\Handler::TYPE_ALERT:
49
+ $this->rep->interval = $opts->getFrequencyAlert();
50
  break;
51
  case Reports\Handler::TYPE_INFO:
52
+ $this->rep->interval = $opts->getFrequencyInfo();
53
  break;
54
  default:
55
  throw new \Exception( 'Not a supported report type: '.$this->rep->type );
 
56
  }
57
  return $this;
58
  }
61
  * @return $this
62
  */
63
  private function setPreviousReport() {
64
+ /** @var Reporting\ModCon $mod */
65
+ $mod = $this->getMod();
66
+ /** @var Reports\Select $sel */
67
+ $sel = $mod->getDbHandler_Reports()->getQuerySelector();
68
+ $this->rep->previous = $sel->filterByType( $this->rep->type )
69
+ ->filterByFrequency( $this->rep->interval )
70
+ ->setOrderBy( 'sent_at', 'DESC' )
71
+ ->first();
 
72
  return $this;
73
  }
74
 
81
  */
82
  private function setIntervalBoundaries() {
83
 
84
+ $carbon = Services::Request()->carbon( true );
85
  $nAddition = -1; // the previous hour, day, week, month
86
 
87
  switch ( $this->rep->interval ) {
88
  // case 'realtime':
89
  // break;
90
+ case 'no_time': // TODO
91
+ $start = 0;
92
+ $end = $carbon->timestamp;
93
+ break;
94
  case 'hourly':
95
+ $carbon->addHours( $nAddition );
96
+ $start = $carbon->startOfHour()->timestamp;
97
+ $end = $carbon->endOfHour()->timestamp;
98
  break;
99
  case 'daily':
100
+ $carbon->addDays( $nAddition );
101
+ $start = $carbon->startOfDay()->timestamp;
102
+ $end = $carbon->endOfDay()->timestamp;
103
  break;
104
  case 'weekly':
105
+ $carbon->addWeeks( $nAddition );
106
+ $start = $carbon->startOfWeek()->timestamp;
107
+ $end = $carbon->endOfWeek()->timestamp;
108
  break;
109
  case 'monthly':
110
+ $carbon->addMonths( $nAddition );
111
+ $start = $carbon->startOfMonth()->timestamp;
112
+ $end = $carbon->endOfMonth()->timestamp;
113
  break;
114
  case 'yearly':
115
+ $carbon->addYears( $nAddition );
116
+ $start = $carbon->startOfYear()->timestamp;
117
+ $end = $carbon->endOfYear()->timestamp;
118
  break;
119
  default:
120
  throw new \Exception( 'Not a supported frequency' );
 
121
  }
122
 
123
  if ( $this->rep->previous instanceof Reports\EntryVO
124
+ && $end <= $this->rep->previous->interval_end_at ) {
125
  throw new \Exception( 'Attempting to create a duplicate report based on interval.' );
126
  }
127
 
128
+ $this->rep->interval_start_at = $start;
129
+ $this->rep->interval_end_at = $end;
130
 
131
  return $this;
132
  }
136
  * @throws \Exception
137
  */
138
  private function setReportId() {
139
+ /** @var Reporting\ModCon $mod */
140
+ $mod = $this->getMod();
141
  /** @var Reports\Select $oSel */
142
+ $oSel = $mod->getDbHandler_Reports()->getQuerySelector();
143
  $nPrevID = $oSel->getLastReportId();
144
  $this->rep->rid = is_numeric( $nPrevID ) ? $nPrevID + 1 : 1;
145
  return $this;
src/lib/src/Modules/Reporting/ModCon.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
+
8
+ class ModCon extends BaseShield\ModCon {
9
+
10
+ /**
11
+ * @var Lib\ReportingController
12
+ */
13
+ private $reportsCon;
14
+
15
+ public function getDbHandler_Reports() :Databases\Reports\Handler {
16
+ return $this->getDbH( 'reports' );
17
+ }
18
+
19
+ public function getReportingController() :Lib\ReportingController {
20
+ if ( !isset( $this->reportsCon ) ) {
21
+ $this->reportsCon = ( new Lib\ReportingController() )->setMod( $this );
22
+ }
23
+ return $this->reportsCon;
24
+ }
25
+ }
src/lib/src/Modules/Reporting/Options.php CHANGED
@@ -2,9 +2,9 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
 
7
- class Options extends Base\ShieldOptions {
8
 
9
  public function getFrequencyAlert() :string {
10
  return $this->getFrequency( 'alert' );
@@ -16,17 +16,9 @@ class Options extends Base\ShieldOptions {
16
 
17
  private function getFrequency( string $type ) :string {
18
  $key = 'frequency_'.$type;
19
- $sDefault = $this->getOptDefault( $key );
20
- return ( $this->isPremium() || in_array( $this->getOpt( $key ), [ 'disabled', $sDefault ] ) )
21
  ? $this->getOpt( $key )
22
- : $sDefault;
23
- }
24
-
25
- /**
26
- * @return string
27
- * @deprecated 10.0
28
- */
29
- public function getDbTable_Reports() :string {
30
- return $this->getCon()->prefixOption( $this->getDef( 'reports_table_name' ) );
31
  }
32
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
 
7
+ class Options extends BaseShield\Options {
8
 
9
  public function getFrequencyAlert() :string {
10
  return $this->getFrequency( 'alert' );
16
 
17
  private function getFrequency( string $type ) :string {
18
  $key = 'frequency_'.$type;
19
+ $default = $this->getOptDefault( $key );
20
+ return ( $this->isPremium() || in_array( $this->getOpt( $key ), [ 'disabled', $default ] ) )
21
  ? $this->getOpt( $key )
22
+ : $default;
 
 
 
 
 
 
 
 
23
  }
24
  }
src/lib/src/Modules/Reporting/Processor.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+
7
+ class Processor extends BaseShield\Processor {
8
+
9
+ protected function run() {
10
+ /** @var ModCon $mod */
11
+ $mod = $this->getMod();
12
+ $mod->getReportingController()->execute();
13
+ }
14
+ }
src/lib/src/Modules/Reporting/UI.php CHANGED
@@ -3,10 +3,10 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Events;
8
 
9
- class UI extends Base\ShieldUI {
10
 
11
  public function renderSummaryStats() :string {
12
  $con = $this->getCon();
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting;
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 UI extends BaseShield\UI {
10
 
11
  public function renderSummaryStats() :string {
12
  $con = $this->getCon();
src/lib/src/Modules/SecurityAdmin/AdminNotices.php CHANGED
@@ -3,40 +3,37 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
 
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class AdminNotices extends Shield\Modules\Base\AdminNotices {
9
 
10
  /**
11
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
12
- * @throws \Exception
13
  */
14
- protected function processNotice( $oNotice ) {
15
 
16
- switch ( $oNotice->id ) {
17
 
18
  case 'admin-users-restricted':
19
- $this->buildNotice_AdminUsersRestricted( $oNotice );
20
  break;
21
 
22
  case 'certain-options-restricted':
23
- $this->buildNotice_CertainOptionsRestricted( $oNotice );
24
  break;
25
 
26
  default:
27
- parent::processNotice( $oNotice );
28
  break;
29
  }
30
  }
31
 
32
- /**
33
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
34
- */
35
- private function buildNotice_CertainOptionsRestricted( $oNotice ) {
36
  $oMod = $this->getMod();
37
  $sName = $this->getCon()->getHumanName();
38
 
39
- $oNotice->render_data = [
40
  'notice_attributes' => [],
41
  'strings' => [
42
  'title' => sprintf( __( '%s Security Restrictions Applied', 'wp-simple-firewall' ), $sName ),
@@ -54,14 +51,11 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
54
  ];
55
  }
56
 
57
- /**
58
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
59
- */
60
- private function buildNotice_AdminUsersRestricted( $oNotice ) {
61
  $oMod = $this->getMod();
62
  $sName = $this->getCon()->getHumanName();
63
 
64
- $oNotice->render_data = [
65
  'notice_attributes' => [], // TODO
66
  'strings' => [
67
  'title' => sprintf( __( '%s Security Restrictions Applied', 'wp-simple-firewall' ), $sName ),
@@ -85,31 +79,27 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
85
  ];
86
  }
87
 
88
- /**
89
- * @param Shield\Utilities\AdminNotices\NoticeVO $oNotice
90
- * @return bool
91
- */
92
- protected function isDisplayNeeded( $oNotice ) {
93
  /** @var Options $oOpts */
94
  $oOpts = $this->getOptions();
95
 
96
  $sCurrentPage = Services::WpPost()->getCurrentPage();
97
 
98
- switch ( $oNotice->id ) {
99
 
100
  case 'admin-users-restricted':
101
- $bNeeded = in_array( $sCurrentPage, $oOpts->getDef( 'restricted_pages_users' ) );
102
  break;
103
 
104
  case 'certain-options-restricted':
105
  $sCurrentGetPage = Services::Request()->query( 'page' );
106
- $bNeeded = empty( $sCurrentGetPage ) && in_array( $sCurrentPage, $oOpts->getOptionsPagesToRestrict() );
107
  break;
108
 
109
  default:
110
- $bNeeded = parent::isDisplayNeeded( $oNotice );
111
  break;
112
  }
113
- return $bNeeded;
114
  }
115
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\AdminNotices\NoticeVO;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class AdminNotices extends Shield\Modules\Base\AdminNotices {
10
 
11
  /**
12
+ * @inheritDoc
 
13
  */
14
+ protected function processNotice( NoticeVO $notice ) {
15
 
16
+ switch ( $notice->id ) {
17
 
18
  case 'admin-users-restricted':
19
+ $this->buildNotice_AdminUsersRestricted( $notice );
20
  break;
21
 
22
  case 'certain-options-restricted':
23
+ $this->buildNotice_CertainOptionsRestricted( $notice );
24
  break;
25
 
26
  default:
27
+ parent::processNotice( $notice );
28
  break;
29
  }
30
  }
31
 
32
+ private function buildNotice_CertainOptionsRestricted( NoticeVO $notice ) {
 
 
 
33
  $oMod = $this->getMod();
34
  $sName = $this->getCon()->getHumanName();
35
 
36
+ $notice->render_data = [
37
  'notice_attributes' => [],
38
  'strings' => [
39
  'title' => sprintf( __( '%s Security Restrictions Applied', 'wp-simple-firewall' ), $sName ),
51
  ];
52
  }
53
 
54
+ private function buildNotice_AdminUsersRestricted( NoticeVO $notice ) {
 
 
 
55
  $oMod = $this->getMod();
56
  $sName = $this->getCon()->getHumanName();
57
 
58
+ $notice->render_data = [
59
  'notice_attributes' => [], // TODO
60
  'strings' => [
61
  'title' => sprintf( __( '%s Security Restrictions Applied', 'wp-simple-firewall' ), $sName ),
79
  ];
80
  }
81
 
82
+ protected function isDisplayNeeded( NoticeVO $notice ) :bool {
 
 
 
 
83
  /** @var Options $oOpts */
84
  $oOpts = $this->getOptions();
85
 
86
  $sCurrentPage = Services::WpPost()->getCurrentPage();
87
 
88
+ switch ( $notice->id ) {
89
 
90
  case 'admin-users-restricted':
91
+ $needed = in_array( $sCurrentPage, $oOpts->getDef( 'restricted_pages_users' ) );
92
  break;
93
 
94
  case 'certain-options-restricted':
95
  $sCurrentGetPage = Services::Request()->query( 'page' );
96
+ $needed = empty( $sCurrentGetPage ) && in_array( $sCurrentPage, $oOpts->getOptionsPagesToRestrict() );
97
  break;
98
 
99
  default:
100
+ $needed = parent::isDisplayNeeded( $notice );
101
  break;
102
  }
103
+ return $needed;
104
  }
105
  }
src/lib/src/Modules/SecurityAdmin/AjaxHandler.php CHANGED
@@ -5,7 +5,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin;
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
- class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
9
 
10
  protected function processAjaxAction( string $action ) :array {
11
 
@@ -38,26 +38,23 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
38
  * @return array
39
  */
40
  private function ajaxExec_SecAdminCheck() {
41
- /** @var \ICWP_WPSF_FeatureHandler_AdminAccessRestriction $oMod */
42
- $oMod = $this->getMod();
43
  return [
44
- 'timeleft' => $oMod->getSecAdminTimeLeft(),
45
- 'success' => $oMod->isSecAdminSessionValid()
46
  ];
47
  }
48
 
49
- /**
50
- * @return array
51
- */
52
- private function ajaxExec_SecAdminLogin() {
53
- /** @var \ICWP_WPSF_FeatureHandler_AdminAccessRestriction $oMod */
54
- $oMod = $this->getMod();
55
  $bSuccess = false;
56
  $sHtml = '';
57
 
58
- if ( $oMod->testSecAccessKeyRequest() ) {
59
 
60
- if ( $oMod->setSecurityAdminStatusOnOff( true ) ) {
61
  $bSuccess = true;
62
  $sMsg = __( 'Security Admin PIN Accepted.', 'wp-simple-firewall' )
63
  .' '.__( 'Please wait', 'wp-simple-firewall' ).' ...';
@@ -117,17 +114,15 @@ class AjaxHandler extends Shield\Modules\Base\AjaxHandlerShield {
117
  * @return string
118
  */
119
  private function renderAdminAccessAjaxLoginForm( $sMessage = '' ) {
120
- /** @var \ICWP_WPSF_FeatureHandler_AdminAccessRestriction $oMod */
121
- $oMod = $this->getMod();
122
-
123
- $aData = [
124
  'ajax' => [
125
- 'sec_admin_login' => json_encode( $oMod->getSecAdminLoginAjaxData() )
126
  ],
127
  'strings' => [
128
  'access_message' => empty( $sMessage ) ? __( 'Enter your Security Admin PIN', 'wp-simple-firewall' ) : $sMessage
129
  ]
130
- ];
131
- return $oMod->renderTemplate( 'snippets/admin_access_login', $aData );
132
  }
133
  }
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
+ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
9
 
10
  protected function processAjaxAction( string $action ) :array {
11
 
38
  * @return array
39
  */
40
  private function ajaxExec_SecAdminCheck() {
41
+ /** @var ModCon $mod */
42
+ $mod = $this->getMod();
43
  return [
44
+ 'timeleft' => $mod->getSecAdminTimeLeft(),
45
+ 'success' => $mod->isSecAdminSessionValid()
46
  ];
47
  }
48
 
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' ).' ...';
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', [
 
120
  'ajax' => [
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
  }
128
  }
src/lib/src/Modules/SecurityAdmin/Insights/OverviewCards.php CHANGED
@@ -7,7 +7,7 @@ use FernleafSystems\Wordpress\Plugin\Shield;
7
  class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
8
 
9
  public function build() :array {
10
- /** @var \ICWP_WPSF_FeatureHandler_AdminAccessRestriction $mod */
11
  $mod = $this->getMod();
12
  /** @var Shield\Modules\SecurityAdmin\Options $opts */
13
  $opts = $this->getOptions();
7
  class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
8
 
9
  public function build() :array {
10
+ /** @var Shield\Modules\SecurityAdmin\ModCon $mod */
11
  $mod = $this->getMod();
12
  /** @var Shield\Modules\SecurityAdmin\Options $opts */
13
  $opts = $this->getOptions();
src/lib/src/Modules/SecurityAdmin/Lib/Actions/RemoveSecAdmin.php CHANGED
@@ -21,56 +21,47 @@ class RemoveSecAdmin {
21
  }
22
 
23
  public function sendConfirmationEmail() {
24
- /** @var \ICWP_WPSF_FeatureHandler_AdminAccessRestriction $oMod */
25
- $oMod = $this->getMod();
26
- $sEmail = $oMod->getPluginReportEmail();
27
- if ( !Services::Data()->validEmail( $sEmail ) ) {
28
- $sEmail = Services::WpGeneral()->getSiteAdminEmail();
29
- }
30
-
31
- $aMessage = [
32
- sprintf( __( 'A WordPress user (%s) has requested to remove the Security Admin restriction.', 'wp-simple-firewall' ),
33
- Services::WpUsers()->getCurrentWpUsername() ).' '.
34
- __( 'The purpose of this email is to confirm this action.', 'wp-simple-firewall' ),
35
- __( 'Please click the link below to confirm the removal of the Security Admin restriction.', 'wp-simple-firewall' ),
36
- '',
37
- '<strong>'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ),
38
- __( 'This link must be opened in the same browser that was used to make this original request.', 'wp-simple-firewall' )
39
- ).'</strong>',
40
- '',
41
- sprintf( '%s: %s', __( 'Confirmation link', 'wp-simple-firewall' ),
42
- $oMod->buildAdminActionNonceUrl( 'remove_secadmin_confirm' ) ),
43
- '',
44
- __( "Please understand that to reinstate the Security Admin features, you'll need to provide a new Security Admin password.", 'wp-simple-firewall' ),
45
- '',
46
- __( "Thank you.", 'wp-simple-firewall' )
47
- ];
48
-
49
- $sEmailSubject = __( 'Please Confirm Security Admin Removal', 'wp-simple-firewall' );
50
- return $this->getMod()
51
- ->getEmailProcessor()
52
- ->sendEmailWithWrap( $sEmail, $sEmailSubject, $aMessage );
53
  }
54
 
55
  private function sendNotificationEmail() {
56
- $sEmail = $this->getMod()->getPluginReportEmail();
57
- if ( !Services::Data()->validEmail( $sEmail ) ) {
58
- $sEmail = Services::WpGeneral()->getSiteAdminEmail();
59
- }
60
-
61
- $aMessage = [
62
- __( 'This is an email notification to inform you that the Security Admin restriction has been removed.', 'wp-simple-firewall' ),
63
- __( 'This was done using a confirmation email sent to the Security Administrator email address.', 'wp-simple-firewall' ),
64
- __( 'All restrictions imposed by the Security Admin module have been lifted.', 'wp-simple-firewall' ),
65
- '',
66
- __( "Please understand that to reinstate the Security Admin features, you'll need to provide a new Security Admin password.", 'wp-simple-firewall' ),
67
- '',
68
- __( "Thank you.", 'wp-simple-firewall' )
69
- ];
70
-
71
- $sEmailSubject = __( 'Security Admin restrictions have been removed', 'wp-simple-firewall' );
72
  return $this->getMod()
73
  ->getEmailProcessor()
74
- ->sendEmailWithWrap( $sEmail, $sEmailSubject, $aMessage );
 
 
 
 
 
 
 
 
 
 
 
 
75
  }
76
  }
21
  }
22
 
23
  public function sendConfirmationEmail() {
24
+ /** @var SecurityAdmin\ModCon $mod */
25
+ $mod = $this->getMod();
26
+ return $mod->getEmailProcessor()
27
+ ->sendEmailWithWrap(
28
+ $mod->getPluginReportEmail(),
29
+ __( 'Please Confirm Security Admin Removal', 'wp-simple-firewall' ),
30
+ [
31
+ sprintf( __( 'A WordPress user (%s) has requested to remove the Security Admin restriction.', 'wp-simple-firewall' ),
32
+ Services::WpUsers()->getCurrentWpUsername() ).' '.
33
+ __( 'The purpose of this email is to confirm this action.', 'wp-simple-firewall' ),
34
+ __( 'Please click the link below to confirm the removal of the Security Admin restriction.', 'wp-simple-firewall' ),
35
+ '',
36
+ '<strong>'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ),
37
+ __( 'This link must be opened in the same browser that was used to make this original request.', 'wp-simple-firewall' )
38
+ ).'</strong>',
39
+ '',
40
+ sprintf( '%s: %s', __( 'Confirmation link', 'wp-simple-firewall' ),
41
+ $mod->buildAdminActionNonceUrl( 'remove_secadmin_confirm' ) ),
42
+ '',
43
+ __( "Please understand that to reinstate the Security Admin features, you'll need to provide a new Security Admin password.", 'wp-simple-firewall' ),
44
+ '',
45
+ __( "Thank you.", 'wp-simple-firewall' )
46
+ ]
47
+ );
 
 
 
 
 
48
  }
49
 
50
  private function sendNotificationEmail() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  return $this->getMod()
52
  ->getEmailProcessor()
53
+ ->sendEmailWithWrap(
54
+ $this->getMod()->getPluginReportEmail(),
55
+ __( 'Security Admin restrictions have been removed', 'wp-simple-firewall' ),
56
+ [
57
+ __( 'This is an email notification to inform you that the Security Admin restriction has been removed.', 'wp-simple-firewall' ),
58
+ __( 'This was done using a confirmation email sent to the Security Administrator email address.', 'wp-simple-firewall' ),
59
+ __( 'All restrictions imposed by the Security Admin module have been lifted.', 'wp-simple-firewall' ),
60
+ '',
61
+ __( "Please understand that to reinstate the Security Admin features, you'll need to provide a new Security Admin password.", 'wp-simple-firewall' ),
62
+ '',
63
+ __( "Thank you.", 'wp-simple-firewall' )
64
+ ]
65
+ );
66
  }
67
  }
src/lib/src/Modules/SecurityAdmin/Lib/Actions/SetSecAdminPin.php CHANGED
@@ -9,11 +9,11 @@ class SetSecAdminPin {
9
  use ModConsumer;
10
 
11
  /**
12
- * @param string $sKey
13
  * @throws \Exception
14
  */
15
- public function run( $sKey ) {
16
- if ( empty( $sKey ) ) {
17
  throw new \Exception( 'Attempting to set an empty Security Admin Access Key.' );
18
  }
19
  if ( !$this->getCon()->isPluginAdmin() ) {
@@ -21,7 +21,7 @@ class SetSecAdminPin {
21
  }
22
 
23
  $this->getOptions()
24
- ->setOpt( 'admin_access_key', md5( $sKey ) );
25
  $this->getMod()
26
  ->setIsMainFeatureEnabled( true )
27
  ->saveModOptions();
9
  use ModConsumer;
10
 
11
  /**
12
+ * @param string $pin
13
  * @throws \Exception
14
  */
15
+ public function run( string $pin ) {
16
+ if ( empty( $pin ) ) {
17
  throw new \Exception( 'Attempting to set an empty Security Admin Access Key.' );
18
  }
19
  if ( !$this->getCon()->isPluginAdmin() ) {
21
  }
22
 
23
  $this->getOptions()
24
+ ->setOpt( 'admin_access_key', md5( $pin ) );
25
  $this->getMod()
26
  ->setIsMainFeatureEnabled( true )
27
  ->saveModOptions();
src/lib/src/Modules/SecurityAdmin/Lib/WhiteLabel/ApplyLabels.php CHANGED
@@ -75,7 +75,7 @@ class ApplyLabels {
75
  * @return array
76
  */
77
  public function applyPluginLabels( $pluginLabels ) {
78
- /** @var \ICWP_WPSF_FeatureHandler_AdminAccessRestriction $mod */
79
  $mod = $this->getMod();
80
 
81
  $labels = $mod->getWhitelabelOptions();
75
  * @return array
76
  */
77
  public function applyPluginLabels( $pluginLabels ) {
78
+ /** @var SecurityAdmin\ModCon $mod */
79
  $mod = $this->getMod();
80
 
81
  $labels = $mod->getWhitelabelOptions();
src/lib/src/Modules/SecurityAdmin/ModCon.php ADDED
@@ -0,0 +1,384 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin;
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
+ const HASH_DELETE = '32f68a60cef40faedbc6af20298c1a1e';
12
+
13
+ /**
14
+ * @var bool
15
+ */
16
+ private $bValidSecAdminRequest;
17
+
18
+ /**
19
+ * @var Lib\WhiteLabel\ApplyLabels
20
+ */
21
+ private $whitelabelCon;
22
+
23
+ protected function setupCustomHooks() {
24
+ add_action( $this->prefix( 'pre_deactivate_plugin' ), [ $this, 'preDeactivatePlugin' ] );
25
+ }
26
+
27
+ public function getWhiteLabelController() :Lib\WhiteLabel\ApplyLabels {
28
+ if ( !$this->whitelabelCon instanceof Lib\WhiteLabel\ApplyLabels ) {
29
+ $this->whitelabelCon = ( new Lib\WhiteLabel\ApplyLabels() )
30
+ ->setMod( $this );
31
+ }
32
+ return $this->whitelabelCon;
33
+ }
34
+
35
+ /**
36
+ * @return bool
37
+ * @throws \Exception
38
+ */
39
+ protected function isReadyToExecute() :bool {
40
+ return $this->isEnabledSecurityAdmin() && parent::isReadyToExecute();
41
+ }
42
+
43
+ /**
44
+ * No checking of admin capabilities in-case of infinite loop with
45
+ * admin access caps check
46
+ * @return bool
47
+ */
48
+ public function isRegisteredSecAdminUser() {
49
+ /** @var Options $opts */
50
+ $opts = $this->getOptions();
51
+ $sUser = Services::WpUsers()->getCurrentWpUsername();
52
+ return !empty( $sUser ) && in_array( $sUser, $opts->getSecurityAdminUsers() );
53
+ }
54
+
55
+ protected function preProcessOptions() {
56
+ /** @var Options $opts */
57
+ $opts = $this->getOptions();
58
+
59
+ if ( $this->isValidSecAdminRequest() ) {
60
+ $this->setSecurityAdminStatusOnOff( true );
61
+ }
62
+
63
+ // Verify whitelabel images
64
+ if ( $this->isWlEnabled() ) {
65
+ foreach ( [ 'wl_menuiconurl', 'wl_dashboardlogourl', 'wl_login2fa_logourl' ] as $key ) {
66
+ if ( !Services::Data()->isValidWebUrl( $this->buildWlImageUrl( $key ) ) ) {
67
+ $opts->resetOptToDefault( $key );
68
+ }
69
+ }
70
+ }
71
+
72
+ $opts->setOpt( 'sec_admin_users', $this->verifySecAdminUsers( $opts->getSecurityAdminUsers() ) );
73
+ }
74
+
75
+ /**
76
+ * Ensures that all entries are valid users.
77
+ * @param string[] $aSecUsers
78
+ * @return string[]
79
+ */
80
+ private function verifySecAdminUsers( $aSecUsers ) {
81
+ /** @var Options $opts */
82
+ $opts = $this->getOptions();
83
+ $DP = Services::Data();
84
+ $WPU = Services::WpUsers();
85
+
86
+ $aFiltered = [];
87
+ foreach ( $aSecUsers as $nCurrentKey => $sUsernameOrEmail ) {
88
+ if ( $DP->validEmail( $sUsernameOrEmail ) ) {
89
+ $user = $WPU->getUserByEmail( $sUsernameOrEmail );
90
+ }
91
+ else {
92
+ $user = $WPU->getUserByUsername( $sUsernameOrEmail );
93
+ if ( is_null( $user ) && is_numeric( $sUsernameOrEmail ) ) {
94
+ $user = $WPU->getUserById( $sUsernameOrEmail );
95
+ }
96
+ }
97
+
98
+ if ( $user instanceof \WP_User && $user->ID > 0 && $WPU->isUserAdmin( $user ) ) {
99
+ $aFiltered[] = $user->user_login;
100
+ }
101
+ }
102
+
103
+ // We now run a bit of a sanity check to ensure that the current user is
104
+ // not adding users here that aren't themselves without a key to still gain access
105
+ $oCurrent = $WPU->getCurrentWpUser();
106
+ if ( !empty( $aFiltered ) && !$opts->hasSecurityPIN() && !in_array( $oCurrent->user_login, $aFiltered ) ) {
107
+ $aFiltered[] = $oCurrent->user_login;
108
+ }
109
+
110
+ natsort( $aFiltered );
111
+ return array_unique( $aFiltered );
112
+ }
113
+
114
+ public function getSecAdminTimeout() :int {
115
+ return (int)$this->getOptions()->getOpt( 'admin_access_timeout' )*MINUTE_IN_SECONDS;
116
+ }
117
+
118
+ /**
119
+ * Only returns greater than 0 if you have a valid Sec admin session
120
+ */
121
+ public function getSecAdminTimeLeft() :int {
122
+ $nLeft = 0;
123
+ if ( $this->getCon()->getModule_Sessions()->getSessionCon()->hasSession() ) {
124
+
125
+ $nSecAdminAt = $this->getSession()->getSecAdminAt();
126
+ if ( $this->isRegisteredSecAdminUser() ) {
127
+ $nLeft = 0;
128
+ }
129
+ elseif ( $nSecAdminAt > 0 ) {
130
+ $nLeft = $this->getSecAdminTimeout() - ( Services::Request()->ts() - $nSecAdminAt );
131
+ }
132
+ }
133
+ return (int)max( 0, $nLeft );
134
+ }
135
+
136
+ protected function handleModAction( string $action ) {
137
+ switch ( $action ) {
138
+ case 'remove_secadmin_confirm':
139
+ ( new Lib\Actions\RemoveSecAdmin() )
140
+ ->setMod( $this )
141
+ ->remove();
142
+ break;
143
+ default:
144
+ break;
145
+ }
146
+ }
147
+
148
+ public function isSecAdminSessionValid() :bool {
149
+ return $this->getSecAdminTimeLeft() > 0;
150
+ }
151
+
152
+ public function isEnabledSecurityAdmin() :bool {
153
+ /** @var Options $opts */
154
+ $opts = $this->getOptions();
155
+ return $this->isModOptEnabled() &&
156
+ ( count( $opts->getSecurityAdminUsers() ) > 0 ||
157
+ ( $opts->hasSecurityPIN() && $this->getSecAdminTimeout() > 0 )
158
+ );
159
+ }
160
+
161
+ /**
162
+ * @param bool $bSetOn
163
+ * @return bool
164
+ */
165
+ public function setSecurityAdminStatusOnOff( $bSetOn = false ) {
166
+ /** @var Shield\Databases\Session\Update $oUpdater */
167
+ $oUpdater = $this->getDbHandler_Sessions()->getQueryUpdater();
168
+ return $bSetOn ?
169
+ $oUpdater->startSecurityAdmin( $this->getSession() )
170
+ : $oUpdater->terminateSecurityAdmin( $this->getSession() );
171
+ }
172
+
173
+ public function isValidSecAdminRequest() :bool {
174
+ return $this->isAccessKeyRequest() && $this->testSecAccessKeyRequest();
175
+ }
176
+
177
+ public function testSecAccessKeyRequest() :bool {
178
+ if ( !isset( $this->bValidSecAdminRequest ) ) {
179
+ $bValid = false;
180
+ $sReqKey = Services::Request()->post( 'sec_admin_key' );
181
+ if ( !empty( $sReqKey ) ) {
182
+ /** @var Options $opts */
183
+ $opts = $this->getOptions();
184
+ $bValid = hash_equals( $opts->getSecurityPIN(), md5( $sReqKey ) );
185
+ if ( !$bValid ) {
186
+ $sEscaped = isset( $_POST[ 'sec_admin_key' ] ) ? $_POST[ 'sec_admin_key' ] : '';
187
+ if ( !empty( $sEscaped ) ) {
188
+ // Workaround for escaping of passwords
189
+ $bValid = hash_equals( $opts->getSecurityPIN(), md5( $sEscaped ) );
190
+ if ( $bValid ) {
191
+ $opts->setOpt( 'admin_access_key', md5( $sReqKey ) );
192
+ }
193
+ }
194
+ }
195
+
196
+ $this->getCon()->fireEvent( $bValid ? 'key_success' : 'key_fail' );
197
+ }
198
+
199
+ $this->bValidSecAdminRequest = $bValid;
200
+ }
201
+ return $this->bValidSecAdminRequest;
202
+ }
203
+
204
+ private function isAccessKeyRequest() :bool {
205
+ return strlen( Services::Request()->post( 'sec_admin_key', '' ) ) > 0;
206
+ }
207
+
208
+ public function verifyAccessKey( string $key ) :bool {
209
+ /** @var Options $opts */
210
+ $opts = $this->getOptions();
211
+ return !empty( $key ) && hash_equals( $opts->getSecurityPIN(), md5( $key ) );
212
+ }
213
+
214
+ public function getWhitelabelOptions() :array {
215
+ $opts = $this->getOptions();
216
+ $main = $opts->getOpt( 'wl_pluginnamemain' );
217
+ $menu = $opts->getOpt( 'wl_namemenu' );
218
+ if ( empty( $menu ) ) {
219
+ $menu = $main;
220
+ }
221
+
222
+ return [
223
+ 'name_main' => $main,
224
+ 'name_menu' => $menu,
225
+ 'name_company' => $opts->getOpt( 'wl_companyname' ),
226
+ 'description' => $opts->getOpt( 'wl_description' ),
227
+ 'url_home' => $opts->getOpt( 'wl_homeurl' ),
228
+ 'url_icon' => $this->buildWlImageUrl( 'wl_menuiconurl' ),
229
+ 'url_dashboardlogourl' => $this->buildWlImageUrl( 'wl_dashboardlogourl' ),
230
+ 'url_login2fa_logourl' => $this->buildWlImageUrl( 'wl_login2fa_logourl' ),
231
+ ];
232
+ }
233
+
234
+ /**
235
+ * We cater for 3 options:
236
+ * Full URL
237
+ * Relative path URL: i.e. starts with /
238
+ * Or Plugin image URL i.e. doesn't start with HTTP or /
239
+ * @param string $key
240
+ * @return string
241
+ */
242
+ private function buildWlImageUrl( $key ) {
243
+ $opts = $this->getOptions();
244
+
245
+ $sLogoUrl = $opts->getOpt( $key );
246
+ if ( empty( $sLogoUrl ) ) {
247
+ $opts->resetOptToDefault( $key );
248
+ $sLogoUrl = $opts->getOpt( $key );
249
+ }
250
+ if ( !empty( $sLogoUrl ) && !Services::Data()->isValidWebUrl( $sLogoUrl ) && strpos( $sLogoUrl, '/' ) !== 0 ) {
251
+ $sLogoUrl = $this->getCon()->getPluginUrl_Image( $sLogoUrl );
252
+ if ( empty( $sLogoUrl ) ) {
253
+ $opts->resetOptToDefault( $key );
254
+ $sLogoUrl = $this->getCon()->getPluginUrl_Image( $opts->getOpt( $key ) );
255
+ }
256
+ }
257
+
258
+ return $sLogoUrl;
259
+ }
260
+
261
+ public function isWlEnabled() :bool {
262
+ /** @var Options $opts */
263
+ $opts = $this->getOptions();
264
+ return $opts->isEnabledWhitelabel() && $this->isPremium();
265
+ }
266
+
267
+ public function isWlHideUpdates() :bool {
268
+ return $this->isWlEnabled() && $this->getOptions()->isOpt( 'wl_hide_updates', 'Y' );
269
+ }
270
+
271
+ /**
272
+ * @param string $pin
273
+ * @return $this
274
+ * @throws \Exception
275
+ */
276
+ public function setNewPinManually( string $pin ) {
277
+ if ( empty( $pin ) ) {
278
+ throw new \Exception( 'Attempting to set an empty Security PIN.' );
279
+ }
280
+ if ( !$this->getCon()->isPluginAdmin() ) {
281
+ throw new \Exception( 'User does not have permission to update the Security PIN.' );
282
+ }
283
+
284
+ $this->setIsMainFeatureEnabled( true );
285
+ $this->getOptions()->setOpt( 'admin_access_key', md5( $pin ) );
286
+ return $this->saveModOptions();
287
+ }
288
+
289
+ public function insertCustomJsVars_Admin() {
290
+ parent::insertCustomJsVars_Admin();
291
+
292
+ if ( $this->getSecAdminTimeLeft() > 0 ) {
293
+ $aInsertData = [
294
+ 'ajax' => [
295
+ 'check' => $this->getSecAdminCheckAjaxData(),
296
+ ],
297
+ 'is_sec_admin' => true, // if $nSecTimeLeft > 0
298
+ 'timeleft' => $this->getSecAdminTimeLeft(), // JS uses milliseconds
299
+ 'strings' => [
300
+ 'confirm' => __( 'Security Admin session has timed-out.', 'wp-simple-firewall' ).' '.__( 'Reload now?', 'wp-simple-firewall' ),
301
+ 'nearly' => __( 'Security Admin session has nearly timed-out.', 'wp-simple-firewall' ),
302
+ 'expired' => __( 'Security Admin session has timed-out.', 'wp-simple-firewall' )
303
+ ]
304
+ ];
305
+ }
306
+ else {
307
+ $aInsertData = [
308
+ 'ajax' => [
309
+ 'req_email_remove' => $this->getAjaxActionData( 'req_email_remove' ),
310
+ ],
311
+ 'strings' => [
312
+ 'are_you_sure' => __( 'Are you sure?', 'wp-simple-firewall' )
313
+ ]
314
+ ];
315
+ }
316
+
317
+ if ( !empty( $aInsertData ) ) {
318
+ wp_localize_script(
319
+ $this->prefix( 'plugin' ),
320
+ 'icwp_wpsf_vars_secadmin',
321
+ $aInsertData
322
+ );
323
+ }
324
+ }
325
+
326
+ /**
327
+ * This is the point where you would want to do any options verification
328
+ */
329
+ protected function doPrePluginOptionsSave() {
330
+ /** @var Options $opts */
331
+ $opts = $this->getOptions();
332
+
333
+ if ( hash_equals( $opts->getSecurityPIN(), self::HASH_DELETE ) ) {
334
+ $opts->clearSecurityAdminKey();
335
+ $this->setSecurityAdminStatusOnOff( false );
336
+ }
337
+
338
+ // Restricting Activate Plugins also means restricting the rest.
339
+ $aPluginsRestrictions = $opts->getAdminAccessArea_Plugins();
340
+ if ( in_array( 'activate_plugins', $aPluginsRestrictions ) ) {
341
+ $opts->setOpt(
342
+ 'admin_access_restrict_plugins',
343
+ array_unique( array_merge( $aPluginsRestrictions, [
344
+ 'install_plugins',
345
+ 'update_plugins',
346
+ 'delete_plugins'
347
+ ] ) )
348
+ );
349
+ }
350
+
351
+ // Restricting Switch (Activate) Themes also means restricting the rest.
352
+ $aThemesRestrictions = $opts->getAdminAccessArea_Themes();
353
+ if ( in_array( 'switch_themes', $aThemesRestrictions ) && in_array( 'edit_theme_options', $aThemesRestrictions ) ) {
354
+ $opts->setOpt(
355
+ 'admin_access_restrict_themes',
356
+ array_unique( array_merge( $aThemesRestrictions, [
357
+ 'install_themes',
358
+ 'update_themes',
359
+ 'delete_themes'
360
+ ] ) )
361
+ );
362
+ }
363
+
364
+ $aPostRestrictions = $opts->getAdminAccessArea_Posts();
365
+ if ( in_array( 'edit', $aPostRestrictions ) ) {
366
+ $opts->setOpt(
367
+ 'admin_access_restrict_posts',
368
+ array_unique( array_merge( $aPostRestrictions, [ 'create', 'publish', 'delete' ] ) )
369
+ );
370
+ }
371
+ }
372
+
373
+ public function preDeactivatePlugin() {
374
+ if ( !$this->getCon()->isPluginAdmin() ) {
375
+ Services::WpGeneral()->wpDie(
376
+ __( "Sorry, this plugin is protected against unauthorised attempts to disable it.", 'wp-simple-firewall' )
377
+ .'<br />'.sprintf( '<a href="%s">%s</a>',
378
+ $this->getUrl_AdminPage(),
379
+ __( "You'll just need to authenticate first and try again.", 'wp-simple-firewall' )
380
+ )
381
+ );
382
+ }
383
+ }
384
+ }
src/lib/src/Modules/SecurityAdmin/Options.php CHANGED
@@ -2,10 +2,10 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
- class Options extends Base\ShieldOptions {
9
 
10
  public function clearSecurityAdminKey() :self {
11
  return $this->setOpt( 'admin_access_key', '' );
@@ -91,7 +91,7 @@ class Options extends Base\ShieldOptions {
91
  return $this->isEnabledWhitelabel() && $this->isOpt( 'wl_hide_updates', 'Y' );
92
  }
93
 
94
- public function isReplaceBadgeUrl() :bool {
95
  return $this->isOpt( 'wl_replace_badge_url', 'Y' );
96
  }
97
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
+ class Options extends BaseShield\Options {
9
 
10
  public function clearSecurityAdminKey() :self {
11
  return $this->setOpt( 'admin_access_key', '' );
91
  return $this->isEnabledWhitelabel() && $this->isOpt( 'wl_hide_updates', 'Y' );
92
  }
93
 
94
+ public function isReplacePluginBadge() :bool {
95
  return $this->isOpt( 'wl_replace_badge_url', 'Y' );
96
  }
97
  }
src/lib/src/Modules/SecurityAdmin/Processor.php ADDED
@@ -0,0 +1,426 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class Processor extends BaseShield\Processor {
9
+
10
+ protected function run() {
11
+ add_filter( $this->getCon()->prefix( 'is_plugin_admin' ), [ $this, 'adjustUserAdminPermissions' ] );
12
+
13
+ /** @var Options $opts */
14
+ $opts = $this->getOptions();
15
+ if ( $opts->isEnabledWhitelabel() ) {
16
+ /** @var ModCon $mod */
17
+ $mod = $this->getMod();
18
+ $mod->getWhiteLabelController()->execute();
19
+ }
20
+ }
21
+
22
+ /**
23
+ * @param bool $bHasPermission
24
+ * @return bool
25
+ */
26
+ public function adjustUserAdminPermissions( $bHasPermission = true ) :bool {
27
+ /** @var ModCon $mod */
28
+ $mod = $this->getMod();
29
+ return $bHasPermission &&
30
+ ( $mod->isRegisteredSecAdminUser() || $mod->isSecAdminSessionValid()
31
+ || $mod->testSecAccessKeyRequest() );
32
+ }
33
+
34
+ public function onWpInit() {
35
+ if ( !$this->getCon()->isPluginAdmin() ) {
36
+ /** @var ModCon $mod */
37
+ $mod = $this->getMod();
38
+ /** @var Options $opts */
39
+ $opts = $this->getOptions();
40
+
41
+ if ( !$mod->isUpgrading() && !Services::WpGeneral()->isLoginRequest() ) {
42
+ add_filter( 'pre_update_option', [ $this, 'blockOptionsSaves' ], 1, 3 );
43
+ }
44
+
45
+ if ( $opts->isSecAdminRestrictUsersEnabled() ) {
46
+ add_filter( 'editable_roles', [ $this, 'restrictEditableRoles' ], 100, 1 );
47
+ add_filter( 'user_has_cap', [ $this, 'restrictAdminUserChanges' ], 100, 3 );
48
+ add_action( 'delete_user', [ $this, 'restrictAdminUserDelete' ], 100, 1 );
49
+ add_action( 'add_user_role', [ $this, 'restrictAddUserRole' ], 100, 2 );
50
+ add_action( 'remove_user_role', [ $this, 'restrictRemoveUserRole' ], 100, 2 );
51
+ add_action( 'set_user_role', [ $this, 'restrictSetUserRole' ], 100, 3 );
52
+ }
53
+
54
+ $aPluginRestrictions = $opts->getAdminAccessArea_Plugins();
55
+ if ( !empty( $aPluginRestrictions ) ) {
56
+ add_filter( 'user_has_cap', [ $this, 'disablePluginManipulation' ], 0, 3 );
57
+ }
58
+
59
+ $aThemeRestrictions = $opts->getAdminAccessArea_Themes();
60
+ if ( !empty( $aThemeRestrictions ) ) {
61
+ add_filter( 'user_has_cap', [ $this, 'disableThemeManipulation' ], 0, 3 );
62
+ }
63
+
64
+ $aPostRestrictions = $opts->getAdminAccessArea_Posts();
65
+ if ( !empty( $aPostRestrictions ) ) {
66
+ add_filter( 'user_has_cap', [ $this, 'disablePostsManipulation' ], 0, 3 );
67
+ }
68
+
69
+ if ( !$this->getCon()->isThisPluginModuleRequest() ) {
70
+ add_action( 'admin_footer', [ $this, 'printAdminAccessAjaxForm' ] );
71
+ }
72
+ }
73
+ }
74
+
75
+ /**
76
+ * @param int $nUserId
77
+ * @param string $sRole
78
+ */
79
+ public function restrictAddUserRole( $nUserId, $sRole ) {
80
+ $oWpUsers = Services::WpUsers();
81
+
82
+ if ( $oWpUsers->getCurrentWpUserId() !== $nUserId && strtolower( $sRole ) === 'administrator' ) {
83
+ $oModUser = $oWpUsers->getUserById( $nUserId );
84
+ remove_action( 'remove_user_role', [ $this, 'restrictRemoveUserRole' ], 100 );
85
+ $oModUser->remove_role( 'administrator' );
86
+ add_action( 'remove_user_role', [ $this, 'restrictRemoveUserRole' ], 100, 2 );
87
+ }
88
+ }
89
+
90
+ /**
91
+ * @param int $nUserId
92
+ * @param string $sRole
93
+ * @param array $aOldRoles
94
+ */
95
+ public function restrictSetUserRole( $nUserId, $sRole, $aOldRoles = [] ) {
96
+ $oWpUsers = Services::WpUsers();
97
+
98
+ $sRole = strtolower( $sRole );
99
+ if ( !is_array( $aOldRoles ) ) {
100
+ $aOldRoles = [];
101
+ }
102
+
103
+ if ( $oWpUsers->getCurrentWpUserId() !== $nUserId ) {
104
+ $bNewRoleIsAdmin = $sRole == 'administrator';
105
+
106
+ // 1. Setting administrator role where it doesn't previously exist
107
+ if ( $bNewRoleIsAdmin && !in_array( 'administrator', $aOldRoles ) ) {
108
+ $bRevert = true;
109
+ }
110
+ // 2. Setting non-administrator role when previous roles included administrator
111
+ elseif ( !$b