PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 4.0.0
WP 2FA – Two-factor authentication for WordPress v4.0.0
4.0.0 1.7.1 2.0.0 2.0.1 2.1.0 2.2.0 2.2.1 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.8.0 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0 3.0.1 3.1.0 3.1.1 3.1.1.2 trunk 1.2.0 1.3.0 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.6.2 1.7.0
wp-2fa / wp-2fa.php
wp-2fa Last commit date
css 10 hours ago dist 10 hours ago includes 10 hours ago js 10 hours ago languages 10 hours ago templates 10 hours ago vendor 10 hours ago class-plugin-deactivation.php 10 hours ago index.php 10 hours ago license.txt 10 hours ago readme.txt 10 hours ago wp-2fa.php 10 hours ago
wp-2fa.php
368 lines
1 <?php
2 /**
3 * WP 2FA - Two-factor authentication for WordPress .
4 *
5 * @copyright Copyright (C) 2013-2026, Melapress - support@melapress.com
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 or higher
7 *
8 * @wordpress-plugin
9 * Plugin Name: WP 2FA - Two-factor authentication for WordPress
10 * Version: 4.0.0
11 * Plugin URI: https://melapress.com/
12 * Description: Easily add an additional layer of security to your WordPress login pages. Enable Two-Factor Authentication for you and all your website users with this easy to use plugin.
13 * Author: Melapress
14 * Author URI: https://melapress.com/
15 * Text Domain: wp-2fa
16 * Domain Path: /languages/
17 * License: GPL v3
18 * Requires at least: 5.5
19 * Requires PHP: 7.4
20 * Network: true
21 *
22 * @package WP2FA
23 *
24 * This program is free software: you can redistribute it and/or modify
25 * it under the terms of the GNU General Public License as published by
26 * the Free Software Foundation, either version 3 of the License, or
27 * (at your option) any later version.
28 *
29 * This program is distributed in the hope that it will be useful,
30 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 * GNU General Public License for more details.
33 *
34 * You should have received a copy of the GNU General Public License
35 * along with this program. If not, see <http://www.gnu.org/licenses/>.
36 *
37 * @fs_ignore /dist/, /extensions/, /freemius/, /includes/, /languages/, /third-party/, /vendor/
38 */
39
40 use WP2FA\WP2FA;
41 use WP2FA\Utils\Migration;
42 use WP2FA\Utils\Upgrade_Guard;
43 use WP2FA\Admin\Setup_Wizard;
44 use WP2FA\Admin\Helpers\WP_Helper;
45 use WP2FA\Admin\Helpers\File_Writer;
46 use WP2FA\Licensing\Licensing_Factory;
47
48 if ( ! defined( 'ABSPATH' ) ) {
49 exit;
50 }
51
52 if ( defined( '\DISABLE_2FA_LOGIN' ) && \DISABLE_2FA_LOGIN ) {
53 return;
54 }
55
56 // Useful global constants.
57 if ( ! defined( 'WP_2FA_VERSION' ) ) {
58 define( 'WP_2FA_VERSION', '4.0.0' );
59 define( 'WP_2FA_BASE', plugin_basename( __FILE__ ) );
60 define( 'WP_2FA_URL', plugin_dir_url( __FILE__ ) );
61 define( 'WP_2FA_PATH', WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . dirname( WP_2FA_BASE ) . DIRECTORY_SEPARATOR );
62 define( 'WP_2FA_INC', WP_2FA_PATH . 'includes/' );
63 define( 'WP_2FA_FILE', __FILE__ );
64 define( 'WP_2FA_LOGS_DIR', 'wp-2fa-logs' );
65
66 // Prefix used in usermetas, settings and transients.
67 define( 'WP_2FA_PREFIX', 'wp_2fa_' );
68 define( 'WP_2FA_POLICY_SETTINGS_NAME', WP_2FA_PREFIX . 'policy' );
69 define( 'WP_2FA_SETTINGS_NAME', WP_2FA_PREFIX . 'settings' );
70 define( 'WP_2FA_WHITE_LABEL_SETTINGS_NAME', WP_2FA_PREFIX . 'white_label' );
71 define( 'WP_2FA_PASSKEYS_SETTINGS_NAME', WP_2FA_PREFIX . 'passkeys' );
72 define( 'WP_2FA_EMAIL_SETTINGS_NAME', WP_2FA_PREFIX . 'email_settings' );
73 define( 'WP_2FA_NEW_SETTINGS_NAME', WP_2FA_PREFIX . 'settings_new' );
74
75 define( 'WP_2FA_PREFIX_PAGE', 'wp-2fa-' );
76
77 define( 'WP_2FA_TEXTDOMAIN', 'wp-2fa' );
78 }
79
80 require_once \plugin_dir_path( __FILE__ ) . 'class-plugin-deactivation.php';
81
82 new WP2FA_Deactivation_Feedback_Server\Plugin_Deactivation();
83
84
85 if ( ! function_exists( 'wp_2f_is_just_in_time_for_2fa_domain' ) ) {
86 /**
87 * Whether it is the just_in_time_error for wp-2fa domains.
88 *
89 * @since 2.9.0
90 *
91 * @param string $status Status of the error.
92 * @param string $function_name Function name.
93 * @param string $message Message.
94 *
95 * @return bool
96 */
97 function wp_2f_is_just_in_time_for_2fa_domain( $status, string $function_name, string $message ): bool {
98
99 return '_load_textdomain_just_in_time' === $function_name && strpos( $message, '<code>' . WP_2FA_TEXTDOMAIN ) !== false;
100 }
101 }
102
103 if ( ! function_exists( 'wp_2fa_trigger_error' ) ) {
104 /**
105 * Catches errors which come from the doing_it_wrong() function, WP core does not provide much information about what is really going on and where, this method adds some more information to the error log.
106 *
107 * @param bool $status - Whether to trigger the error for _doing_it_wrong() calls. Default true.
108 * @param string $function_name - The name of the function that triggered the error (this is the WP function which is not called right, not the real function that actually called it).
109 * @param string $errstr - The WP error string (message).
110 * @param string $version - Since which WP version given error was added.
111 * @param int $errno - The number of the error (type of the error - that probably never get set by WP and always falls to the default which is E_USER_NOTICE).
112 *
113 * @return bool
114 *
115 * @since 2.9.0
116 */
117 function wp_2fa_trigger_error( $status, string $function_name, $errstr, $version, $errno = E_USER_NOTICE ) {
118
119 if ( false === $status ) {
120 return $status;
121 }
122
123 if ( wp_2f_is_just_in_time_for_2fa_domain( '', $function_name, $errstr ) ) {
124 // This error code is not included in error_reporting, so let it fall.
125 // through to the standard PHP error handler.
126 return false;
127 }
128
129 return $status;
130 }
131 }
132
133 if ( ! function_exists( 'wp_2fa_action_doing_it_wrong_run' ) ) {
134 /**
135 * Action for _doing_it_wrong() calls.
136 *
137 * @since 2.9.0
138 *
139 * @param string $function_name The function that was called.
140 * @param string $message A message explaining what has been done incorrectly.
141 * @param string $version The version of WordPress where the message was added.
142 *
143 * @return void
144 */
145 function wp_2fa_action_doing_it_wrong_run( $function_name, $message, $version ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
146
147 global $wp_filter;
148
149 $function_name = (string) $function_name;
150 $message = (string) $message;
151
152 if ( ! class_exists( '\QM_Collectors', false ) || ! wp_2f_is_just_in_time_for_2fa_domain( '', $function_name, $message ) ) {
153 return;
154 }
155
156 $qm_collector_doing_it_wrong = \QM_Collectors::get( 'doing_it_wrong' );
157 $current_priority = $wp_filter['doing_it_wrong_run']->current_priority();
158
159 if ( null === $qm_collector_doing_it_wrong || false === $current_priority ) {
160 return;
161 }
162
163 switch ( $current_priority ) {
164 case 0:
165 \remove_action( 'doing_it_wrong_run', array( $qm_collector_doing_it_wrong, 'action_doing_it_wrong_run' ) );
166 break;
167
168 case 20:
169 \add_action( 'doing_it_wrong_run', array( $qm_collector_doing_it_wrong, 'action_doing_it_wrong_run' ), 10, 3 );
170 break;
171
172 default:
173 break;
174 }
175 }
176 }
177
178 \add_action( 'doing_it_wrong_trigger_error', 'wp_2fa_trigger_error', 0, 4 );
179
180 \add_action( 'aadvana_trigger_error_doing_it_wrong', 'wp_2fa_trigger_error', 0, 4 );
181
182 \add_action( 'doing_it_wrong_run', 'wp_2fa_action_doing_it_wrong_run', 0, 3 );
183 \add_action( 'doing_it_wrong_run', 'wp_2fa_action_doing_it_wrong_run', 20, 3 );
184
185 // Load upgrade guard BEFORE the autoloader — catches stale class references during updates.
186 if ( file_exists( WP_2FA_INC . 'classes/Utils/class-upgrade-guard.php' ) ) {
187 require_once WP_2FA_INC . 'classes/Utils/class-upgrade-guard.php';
188 Upgrade_Guard::init();
189 }
190
191 // Require Composer autoloader if it exists.
192 if ( file_exists( WP_2FA_PATH . 'vendor/autoload.php' ) ) {
193 require_once WP_2FA_PATH . 'vendor/autoload.php';
194 } else {
195 // Composer autoloader is required.
196 \wp_die( \esc_html__( 'The required libraries for WP 2FA are missing. Please reinstall the plugin.', 'wp-2fa' ) );
197 }
198
199 if ( ! class_exists( '\WP2FA\Licensing\Licensing_Factory' ) ) {
200 return;
201 }
202
203 Licensing_Factory::init();
204 Licensing_Factory::provider_call( 'set_basename', true, __FILE__ );
205 if ( null !== Licensing_Factory::get_provider() ) {
206 Licensing_Factory::get_provider()::add_action( 'after_uninstall', '\WP2FA\Core\uninstall' );
207 }
208 require_once WP_2FA_INC . 'functions/core.php';
209
210 // run any required update routines.
211 Migration::migrate();
212
213 // Setup_Wizard.
214 if ( WP_Helper::is_multisite() ) {
215 \add_action( 'network_admin_menu', array( Setup_Wizard::class, 'network_admin_menus' ), 10 );
216 \add_action( 'admin_menu', array( Setup_Wizard::class, 'admin_menus' ), 10 );
217 } else {
218 \add_action( 'admin_menu', array( Setup_Wizard::class, 'admin_menus' ), 10 );
219 }
220
221 // Activation/Deactivation.
222 \register_activation_hook( WP_2FA_FILE, '\WP2FA\Core\activate' );
223 \register_deactivation_hook( WP_2FA_FILE, '\WP2FA\Core\deactivate' );
224 // Register our uninstallation hook.
225 \register_uninstall_hook( WP_2FA_FILE, '\WP2FA\Core\uninstall' );
226
227 \add_filter( 'plugins_loaded', array( WP2FA::class, 'init' ) );
228 \add_action( 'plugins_loaded', array( WP2FA::class, 'add_wizard_actions' ), 10 );
229
230 // Declare compatibility with WooCommerce features.
231 \add_action(
232 'before_woocommerce_init',
233 function () {
234 if ( class_exists( '\Automattic\WooCommerce\Utilities\FeaturesUtil' ) ) {
235 \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', WP_2FA_FILE );
236 \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'cart_checkout_blocks', WP_2FA_FILE );
237 \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'product_block_editor', WP_2FA_FILE );
238 }
239 }
240 );
241
242 // Include files.
243 // require_once WP_2FA_INC . 'functions/core.php';
244
245 // Require Composer autoloader if it exists.
246 // if ( file_exists( WP_2FA_PATH . 'vendor/autoload.php' ) ) {
247 // require_once WP_2FA_PATH . 'vendor/autoload.php';
248 // }
249
250 // run any required update routines.
251 // Migration::migrate();
252
253 // Setup_Wizard.
254 // if ( WP_Helper::is_multisite() ) {
255 // \add_action( 'network_admin_menu', array( Setup_Wizard::class, 'network_admin_menus' ), 10 );
256 // \add_action( 'admin_menu', array( Setup_Wizard::class, 'admin_menus' ), 10 );
257 // } else {
258 // \add_action( 'admin_menu', array( Setup_Wizard::class, 'admin_menus' ), 10 );
259 // }
260
261 // Activation/Deactivation.
262 // \register_activation_hook( WP_2FA_FILE, '\WP2FA\Core\activate' );
263 // \register_deactivation_hook( WP_2FA_FILE, '\WP2FA\Core\deactivate' );
264 // Register our uninstallation hook.
265 // \register_uninstall_hook( WP_2FA_FILE, '\WP2FA\Core\uninstall' );
266
267 // \add_filter( 'plugins_loaded', array( WP2FA::class, 'init' ) );
268 // \add_action( 'plugins_loaded', array( WP2FA::class, 'add_wizard_actions' ), 10 );
269
270
271
272 if ( ! defined( File_Writer::SECRET_NAME ) ) {
273 define( File_Writer::SECRET_NAME, WP2FA::get_secret_key() );
274
275 define( 'WP2FA_SECRET_IS_IN_DB', true );
276 }
277
278 // @free:start
279 if ( ! function_exists( 'wp2fa_free_on_plugin_activation' ) ) {
280 /**
281 * Takes care of deactivation of the premium plugin when the free plugin is activated.
282 *
283 * Note: This code MUST NOT be present in the premium version an is removed automatically during the build process.
284 *
285 * @since 2.0.0
286 */
287 function wp2fa_free_on_plugin_activation() {
288 $premium_version_slug = 'wp-2fa-premium/wp-2fa.php';
289 if ( is_plugin_active( $premium_version_slug ) ) {
290 deactivate_plugins( $premium_version_slug, true );
291 }
292 check_ssl();
293 }
294
295 \register_activation_hook( __FILE__, 'wp2fa_free_on_plugin_activation' );
296 }
297 // @free:end
298
299 /*
300 * Clears the config cache from the DB
301 *
302 * @return void
303 *
304 * @since 2.2.0
305 */
306 \add_action(
307 'upgrader_process_complete',
308 function () {
309 delete_transient( 'wp_2fa_config_file_hash' );
310 },
311 10,
312 2
313 );
314
315 if ( ! function_exists( 'check_ssl' ) ) {
316 /**
317 * Checks if the required library is installed and cancels the process if not.
318 *
319 * @return void
320 *
321 * @since 2.2.0
322 */
323 function check_ssl() {
324 if ( ! \WP2FA\Authenticator\Open_SSL::is_ssl_available() ) {
325 $html = '<div class="updated notice is-dismissible">
326 <p>' . \esc_html__( 'This plugin requires OpenSSL. Contact your web host or website administrator so they can enable OpenSSL. Re-activate the plugin once the library has been enabled.', 'wp-2fa' )
327 . '</p>
328 </div>';
329
330 echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
331
332 \wp_die();
333 }
334 }
335 }
336
337 if ( \PHP_VERSION_ID < 80000 && ! \interface_exists( 'Stringable' ) ) {
338 interface Stringable {
339 // phpcs:ignore Universal.Files.SeparateFunctionsFromOO.Mixed
340 /**
341 * Mockup function for PHP versions lower than 8.
342 *
343 * @return string
344 */
345 public function __toString();
346 }
347 }
348
349 if ( ! function_exists( 'str_starts_with' ) ) {
350 /**
351 * PHP lower than 8 is missing that function but it required in the newer versions of our plugin.
352 *
353 * @param string $haystack - The string to search in.
354 * @param string $needle - The needle to search for.
355 *
356 * @return bool
357 *
358 * @since 2.6.4
359 */
360 function str_starts_with( $haystack, $needle ): bool {
361 if ( '' === $needle ) {
362 return true;
363 }
364
365 return 0 === strpos( $haystack, $needle );
366 }
367 }
368