PluginProbe ʕ •ᴥ•ʔ
Limit Login Attempts Security – Login Security, 2FA, Firewall, Brute Force Prevention / 3.2.4
Limit Login Attempts Security – Login Security, 2FA, Firewall, Brute Force Prevention v3.2.4
3.2.4 3.2.3 3.2.2 3.2.1 3.2.0 trunk 2.0.0 2.1.0 2.10.0 2.10.1 2.11.0 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.14.0 2.15.0 2.15.1 2.15.2 2.16.0 2.17.0 2.17.1 2.17.2 2.17.3 2.17.4 2.18.0 2.19.0 2.19.1 2.19.2 2.2.0 2.20.0 2.20.1 2.20.2 2.20.3 2.20.4 2.20.5 2.20.6 2.21.0 2.21.1 2.22.0 2.22.1 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.25.0 2.25.1 2.25.10 2.25.11 2.25.12 2.25.13 2.25.14 2.25.15 2.25.16 2.25.17 2.25.18 2.25.19 2.25.2 2.25.20 2.25.21 2.25.22 2.25.23 2.25.24 2.25.25 2.25.26 2.25.27 2.25.28 2.25.29 2.25.3 2.25.4 2.25.5 2.25.6 2.25.7 2.25.8 2.25.9 2.26.0 2.26.1 2.26.10 2.26.11 2.26.12 2.26.13 2.26.14 2.26.15 2.26.16 2.26.17 2.26.18 2.26.19 2.26.2 2.26.20 2.26.21 2.26.22 2.26.23 2.26.24 2.26.25 2.26.26 2.26.27 2.26.28 2.26.3 2.26.4 2.26.5 2.26.6 2.26.7 2.26.8 2.26.9 2.3.0 2.4.0 2.5.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.8.0 2.8.1 2.9.0 3.0.0 3.0.1 3.0.2 3.1.0
limit-login-attempts-reloaded / core / integrations / MemberPressIntegration.php
limit-login-attempts-reloaded / core / integrations Last commit date
BaseIntegration.php 2 weeks ago IntegrationInterface.php 2 weeks ago IntegrationManager.php 2 weeks ago MemberPressIntegration.php 2 weeks ago WooCommerceIntegration.php 2 weeks ago
MemberPressIntegration.php
280 lines
1 <?php
2
3 namespace LLAR\Core\Integrations;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit;
7 }
8
9 class MemberPressIntegration extends BaseIntegration {
10
11 /**
12 * Get the name of the plugin this integration supports
13 *
14 * @return string
15 */
16 public function get_plugin_name() {
17 return 'MemberPress';
18 }
19
20 /**
21 * Check if MemberPress plugin is active
22 *
23 * @return bool
24 */
25 public static function is_plugin_active() {
26 return function_exists( 'mepr_validate_login' ) || class_exists( '\MeprUser' );
27 }
28
29 /**
30 * Register all hooks and filters for MemberPress
31 *
32 * @return void
33 */
34 public function register_hooks() {
35 if ( ! static::is_plugin_active() ) {
36 return;
37 }
38
39 // hook for the plugin MemberPress
40 add_filter( 'mepr_validate_login', array( $this, 'mepr_validate_login_handler' ), 10, 2 );
41 }
42
43 /**
44 * Check if this is MemberPress login page
45 *
46 * @return bool
47 */
48 public function is_login_page() {
49 if ( ! static::is_plugin_active() ) {
50 return false;
51 }
52
53 // Check for POST request with login credentials
54 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Only checking for presence, not processing
55 $has_post_credentials = isset( $_POST['log'] ) && isset( $_POST['pwd'] );
56
57 // Most reliable check: MemberPress login form has specific identifier
58 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Only checking for presence, not processing
59 if ( isset( $_POST['mepr_process_login_form'] ) ) {
60 return true;
61 }
62
63 // Check if we're on MemberPress login page using MeprUser method (if available)
64 // This works for both GET and POST requests
65 if ( class_exists( '\MeprUser' ) && method_exists( '\MeprUser', 'is_login_page' ) ) {
66 global $post;
67 if ( $post && \MeprUser::is_login_page( $post ) ) {
68 return true;
69 }
70 }
71
72 // Check if we're on MemberPress login page via MeprOptions (if available)
73 // This works for both GET and POST requests
74 if ( class_exists( '\MeprOptions' ) ) {
75 $mepr_options = \MeprOptions::fetch();
76 if ( ! empty( $mepr_options->login_page_id ) && is_page( $mepr_options->login_page_id ) ) {
77 // For GET requests, return true if we're on the login page
78 // For POST requests, also require login credentials
79 if ( ! $has_post_credentials ) {
80 // GET request - we're on the login page
81 return true;
82 } else {
83 // POST request with credentials - verify it's not standard WP login
84 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Only checking for presence, not processing
85 if ( isset( $_SERVER['REQUEST_URI'] ) ) {
86 $request_uri = $_SERVER['REQUEST_URI'];
87 // Exclude standard WordPress login page
88 if ( ! preg_match( '/wp-login\.php/i', $request_uri ) ) {
89 return true;
90 }
91 } else {
92 return true;
93 }
94 }
95 }
96 }
97
98 // For POST requests only: check if we have login credentials
99 // but exclude standard WordPress login page
100 if ( $has_post_credentials ) {
101 // Exclude standard WordPress login page more reliably
102 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Only checking for presence, not processing
103 if ( isset( $_SERVER['REQUEST_URI'] ) ) {
104 $request_uri = $_SERVER['REQUEST_URI'];
105 // Check for wp-login.php in various forms (handles custom paths, multisite, etc.)
106 if ( preg_match( '/wp-login\.php/i', $request_uri ) ) {
107 return false;
108 }
109 }
110 // If we have login fields but none of the MemberPress-specific checks passed,
111 // and it's not standard WP login, it's likely not a MemberPress login
112 return false;
113 }
114
115 // No POST credentials and not on MemberPress login page
116 return false;
117 }
118
119 /**
120 * Get login credentials from the request
121 *
122 * @return array|null
123 */
124 public function get_login_credentials() {
125 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reading POST data for validation, nonce checked by MemberPress
126 if ( ! isset( $_POST['log'] ) || ! isset( $_POST['pwd'] ) ) {
127 return null;
128 }
129
130 return array(
131 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reading POST data for validation, nonce checked by MemberPress
132 'username' => sanitize_text_field( wp_unslash( $_POST['log'] ) ),
133 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reading POST data for validation, nonce checked by MemberPress
134 'password' => wp_unslash( $_POST['pwd'] ), // Password should not be sanitized, but needs wp_unslash() to remove magic quotes
135 );
136 }
137
138 /**
139 * Display error message on MemberPress login page
140 *
141 * @param string $message Error message
142 * @return void
143 */
144 public function display_error( $message ) {
145 // MemberPress handles errors through its own mechanisms
146 // Errors are added through mepr_validate_login_handler
147 }
148
149 /**
150 * Check if this is MemberPress registration page
151 *
152 * @return bool
153 */
154 public function is_registration_page() {
155 if ( ! static::is_plugin_active() ) {
156 return false;
157 }
158
159 // Most reliable check: MemberPress registration form has specific identifier
160 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Only checking for presence, not processing
161 if ( isset( $_POST['mepr_process_signup_form'] ) || isset( $_POST['mepr_process_checkout_form'] ) ) {
162 return true;
163 }
164
165 // Check if we're on MemberPress signup/checkout page via MeprOptions (if available)
166 if ( class_exists( '\MeprOptions' ) ) {
167 $mepr_options = \MeprOptions::fetch();
168 // Check if we're on signup page
169 if ( ! empty( $mepr_options->signup_page_id ) && is_page( $mepr_options->signup_page_id ) ) {
170 // Additional check: ensure we have registration-related POST data
171 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Only checking for presence, not processing
172 if ( isset( $_POST['user_login'] ) || isset( $_POST['user_email'] ) ) {
173 return true;
174 }
175 }
176 }
177
178 // Fallback: check for MemberPress-specific registration fields
179 // Only if we have both user_login/user_email AND MemberPress is active
180 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Only checking for presence, not processing
181 $has_registration_fields = isset( $_POST['user_login'] ) || isset( $_POST['user_email'] );
182 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Only checking for presence, not processing
183 $has_mepr_action = isset( $_POST['action'] ) && $_POST['action'] === 'register';
184
185 // Require both conditions to reduce false positives
186 if ( $has_registration_fields && $has_mepr_action ) {
187 return true;
188 }
189
190 return false;
191 }
192
193 /**
194 * Get registration data from the request
195 *
196 * @return array|null
197 */
198 public function get_registration_data() {
199 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reading POST data for validation, nonce checked by MemberPress
200 if ( empty( $_POST['user_login'] ) && empty( $_POST['user_email'] ) ) {
201 return null;
202 }
203
204 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reading POST data for validation, nonce checked by MemberPress
205 $user_login = isset( $_POST['user_login'] ) ? sanitize_text_field( wp_unslash( $_POST['user_login'] ) ) : '';
206 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reading POST data for validation, nonce checked by MemberPress
207 // Note: sanitize_email() is used here for form data retrieval
208 $user_email = isset( $_POST['user_email'] ) ? sanitize_email( wp_unslash( $_POST['user_email'] ) ) : '';
209
210 // Only return if at least one field is present
211 if ( empty( $user_login ) && empty( $user_email ) ) {
212 return null;
213 }
214
215 return array(
216 'username' => $user_login,
217 'email' => $user_email,
218 );
219 }
220
221 /**
222 * Display error message on MemberPress registration page
223 *
224 * @param string $message Error message
225 * @return void
226 */
227 public function display_registration_error( $message ) {
228 // MemberPress handles registration errors through WordPress registration_errors filter
229 // Errors should be added via the registration validation hooks
230 }
231
232 /**
233 * For plugin MemberPress
234 * Triggers authenticate filter to allow Limit Login Attempts Reloaded
235 * to track credentials and check lockouts before MemberPress validates the password
236 * This enables the plugin to display remaining attempts messages
237 *
238 * @param array $errors Array of existing errors (from MemberPress validate_login, applied before this filter).
239 * @param array $params Login parameters (log, pwd)
240 * @return array Errors for MemberPress; when LLAR blocks login, returns that message so MP shows it (MP uses only $errors[0]).
241 */
242 public function mepr_validate_login_handler( $errors, $params = array() ) {
243 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- MemberPress handles nonce verification
244 if ( ! isset( $_POST['log'] ) || ! isset( $_POST['pwd'] ) ) {
245 return $errors;
246 }
247
248 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- MemberPress handles nonce verification
249 $log = sanitize_text_field( wp_unslash( $_POST['log'] ) );
250 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- MemberPress handles nonce verification
251 $pwd = isset( $_POST['pwd'] ) ? wp_unslash( $_POST['pwd'] ) : ''; // Password should not be sanitized, but needs wp_unslash() to remove magic quotes
252
253 // Trigger authenticate filter to track credentials and check lockouts.
254 // MemberPress runs validate_login before this filter, so $errors may already contain a generic
255 // "incorrect password" message; MeprLoginCtrl only displays $errors[0]. If authenticate returns
256 // lockout/blacklist, we must replace $errors so the user sees LLAR messaging.
257 $auth_result = apply_filters( 'authenticate', null, $log, $pwd );
258
259 if ( is_wp_error( $auth_result ) ) {
260 $codes = $auth_result->get_error_codes();
261 if ( in_array( 'too_many_retries', $codes, true ) ) {
262 return array( $auth_result->get_error_message( 'too_many_retries' ) );
263 }
264 if ( in_array( 'username_blacklisted', $codes, true ) ) {
265 return array( $auth_result->get_error_message( 'username_blacklisted' ) );
266 }
267 }
268
269 // Wrong-password path usually leaves authenticate() as incorrect_password only: core does not
270 // call wp_authenticate_user (where too_many_retries is added after a valid password check), and
271 // authenticate_late_lockout_check runs only on WP 7.0+. is_login_allowed() uses cloud ACL
272 // (or local lockouts when cloud is off) to surface the lockout message on this path.
273 if ( ! $this->is_login_allowed() ) {
274 return array( $this->get_error_message() );
275 }
276
277 return $errors;
278 }
279 }
280