<?php
/**
 * Test a bunch of permutations of the password when logging into WordPress.
 *
 * Drop this file in your mu-plugins directory.
 * Inspired by Facebook: https://twitter.com/gcpascutto/status/821755332984717314/photo/1
 * Works with any properly coded hashing pluggables, like Roots’ WP Password bcrypt.
 *
 * @author  bjornjohansen
 * @version 0.1.4
 * @license https://www.gnu.org/licenses/old-licenses/gpl-2.0.html  GNU General Public License version 2 (GPLv2)
 * @package Test_Password_Permutations
 */

add_filter( 'check_password', 'test_password_permutations', 20, 4 );

/**
 * Try permutations of the password if the login failed.
 *
 * @param bool       $check    Whether the passwords match.
 * @param string     $password The plaintext password.
 * @param string     $hash     The hashed password.
 * @param string|int $user_id  User ID. Can be empty.
 * @return bool False, if none of the $password permutations match the hashed password.
 */
function test_password_permutations( $check, $password, $hash, $user_id ) {

	// Don’t run this filter recursively (it is applied in wp_check_password() ).
	if ( ! apply_filters( 'enable_password_fixer_filter', true ) ) {
		return $check;
	}
	if ( ! has_filter( 'enable_password_fixer_filter' ) ) {
		add_filter( 'enable_password_fixer_filter', '__return_false' );
	}

	if ( $check ) {
		// Password was correct. No need for us to try permutations.
		return $check;
	}

	// Accept password if a user inadverntently has caps lock enabled.
	if ( ! $check ) {
		// Flip case.
		$test_password = '';
		for ( $i = 0, $c = mb_strlen( $password ); $i < $c; $i++ ) {
			$char = mb_substr( $password, $i, 1 );
			if ( mb_strtoupper( $char ) === $char ) {
				$test_password .= mb_strtolower( $char );
			} else {
				$test_password .= mb_strtoupper( $char );
			}
		}

		$check = wp_check_password( $test_password, $hash );
	}

	// Accept password if their mobile device automatically capitalized the first character of the password.
	if ( ! $check ) {
		$test_password = mb_strtolower( mb_substr( $password, 0, 1 ) ) . mb_substr( $password, 1 );

		// Only test if the permutation actually changed the password.
		if ( $test_password !== $password ) {
			$check = wp_check_password( $test_password, $hash );
		}
	}

	// Accept password if an extra character is added to the end of the password.
	if ( ! $check ) {
		$test_password = mb_substr( $password, 0, mb_strlen( $password ) -1 );

		// Only test if the permutation actually changed the password.
		if ( $test_password !== $password ) {
			$check = wp_check_password( $test_password, $hash );
		}
	}

	// Trim whitespaces from both ends of the password.
	if ( ! $check ) {
		$test_password = trim( $password );

		// Only test if the permutation actually changed the password.
		if ( $test_password !== $password ) {
			$check = wp_check_password( $test_password, $hash );
		}
	}

	return $check;
}