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 |