Enviando el enlace de restablecimiento de contraseña programáticamente
Tengo esta página creada manualmente:
$user_login = sanitize_text_field( $_GET['user_login'] );
if ( username_exists( $user_login ) || email_exists($user_login) ) { ?>
<!--Todo ha sido validado, continuar ....-->
<!DOCTYPE HTML>
<html lang="es-ES">
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
function submit()
{
var f = document.getElementById('lostpasswordform');
f.onclick = function () { };
document.lostpasswordform.submit();
}
</script>
</head>
<body onload="submit()">
<form name="lostpasswordform" id="lostpasswordform" action="<?php echo esc_url( site_url( 'wp-login.php?action=lostpassword', 'login_post' ) ); ?>" method="post">
<input type="hidden" name="user_login" id="user_login" class="input" value="<?php echo ($user_login); ?>" />
<?php do_action('lost_password'); ?>
</form>
</body>
</html>
<?php
echo "ÉXITO";
exit();
} else {
echo "¡El nombre de usuario o email introducido es incorrecto, por favor inténtelo de nuevo!";
}
Todo parece correcto, pero no funciona cuando se llama desde una aplicación. Si visito manualmente domain.example/forgot-password?user_login=username
sí envía correctamente el email de restablecimiento de contraseña.

Así que si deseas enviar el enlace para restablecer la contraseña y tienes acceso al código base, puedes usar el siguiente fragmento y modificarlo según necesites. De hecho, este código es una versión ligeramente modificada de wp-login.php
/**
* Maneja el envío del correo electrónico para recuperar la contraseña al usuario.
*
* @uses $wpdb Objeto de la base de datos de WordPress
* @param string $user_login Nombre de usuario o correo electrónico
* @return bool true en caso de éxito, false en caso de error
*/
function retrieve_password($user_login) {
global $wpdb, $current_site;
if ( empty( $user_login) ) {
return false;
} else if ( strpos( $user_login, '@' ) ) {
$user_data = get_user_by( 'email', trim( $user_login ) );
if ( empty( $user_data ) )
return false;
} else {
$login = trim($user_login);
$user_data = get_user_by('login', $login);
}
do_action('lostpassword_post');
if ( !$user_data ) return false;
// redefinir user_login asegura que devolvemos el caso correcto en el correo
$user_login = $user_data->user_login;
$user_email = $user_data->user_email;
do_action('retreive_password', $user_login); // Error ortográfico y obsoleto
do_action('retrieve_password', $user_login);
$allow = apply_filters('allow_password_reset', true, $user_data->ID);
if ( ! $allow )
return false;
else if ( is_wp_error($allow) )
return false;
$key = $wpdb->get_var($wpdb->prepare("SELECT user_activation_key FROM $wpdb->users WHERE user_login = %s", $user_login));
if ( empty($key) ) {
// Generar algo aleatorio para la clave...
$key = wp_generate_password(20, false);
do_action('retrieve_password_key', $user_login, $key);
// Ahora insertar la nueva clave md5 en la base de datos
$wpdb->update($wpdb->users, array('user_activation_key' => $key), array('user_login' => $user_login));
}
$message = __('Alguien ha solicitado que se restablezca la contraseña para la siguiente cuenta:') . "\r\n\r\n";
$message .= network_home_url( '/' ) . "\r\n\r\n";
$message .= sprintf(__('Nombre de usuario: %s'), $user_login) . "\r\n\r\n";
$message .= __('Si esto fue un error, simplemente ignora este correo y no pasará nada.') . "\r\n\r\n";
$message .= __('Para restablecer tu contraseña, visita la siguiente dirección:') . "\r\n\r\n";
$message .= '<' . network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') . ">\r\n";
if ( is_multisite() )
$blogname = $GLOBALS['current_site']->site_name;
else
// La opción blogname se escapa con esc_html al ingresar a la base de datos en sanitize_option
// queremos revertir esto para el texto plano de los correos.
$blogname = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES);
$title = sprintf( __('[%s] Restablecimiento de contraseña'), $blogname );
$title = apply_filters('retrieve_password_title', $title);
$message = apply_filters('retrieve_password_message', $message, $key);
if ( $message && !wp_mail($user_email, $title, $message) )
wp_die( __('El correo electrónico no pudo ser enviado.') . "<br />\n" . __('Posible razón: tu servidor puede tener deshabilitada la función mail()...') );
return true;
}
$user_login = sanitize_text_field( $_GET['user_login'] );
if (retrieve_password($user_login)) {
echo "ÉXITO";
} else {
echo "ERROR";
}

Esto no está funcionando correctamente para mí. Lo he introducido en un plugin personalizado y lo he puesto en mi sitio, pero está causando un problema donde cuando instalo el plugin y luego hago clic en 'Cerrar sesión', no me desconecta del sitio. ¿Alguna idea de por qué ocurre esto?

La respuesta anterior no funcionó para mí (dice que el código es inválido en la página de inicio de sesión de WP), probablemente porque la respuesta tiene 1,5 años y algo ha cambiado en el código de WP, así que he actualizado un poco este código (también desde wp-login.php), aquí está:
function retrieve_password($user_login){
global $wpdb, $wp_hasher;
$user_login = sanitize_text_field($user_login);
if ( empty( $user_login) ) {
return false;
} else if ( strpos( $user_login, '@' ) ) {
$user_data = get_user_by( 'email', trim( $user_login ) );
if ( empty( $user_data ) )
return false;
} else {
$login = trim($user_login);
$user_data = get_user_by('login', $login);
}
do_action('lostpassword_post');
if ( !$user_data ) return false;
// redefinir user_login asegura que devolvamos el caso correcto en el email
$user_login = $user_data->user_login;
$user_email = $user_data->user_email;
do_action('retreive_password', $user_login); // Error ortográfico y obsoleto
do_action('retrieve_password', $user_login);
$allow = apply_filters('allow_password_reset', true, $user_data->ID);
if ( ! $allow )
return false;
else if ( is_wp_error($allow) )
return false;
$key = wp_generate_password( 20, false );
do_action( 'retrieve_password_key', $user_login, $key );
if ( empty( $wp_hasher ) ) {
require_once ABSPATH . 'wp-includes/class-phpass.php';
$wp_hasher = new PasswordHash( 8, true );
}
$hashed = time() . ':' . $wp_hasher->HashPassword( $key );
$wpdb->update( $wpdb->users, array( 'user_activation_key' => $hashed ), array( 'user_login' => $user_login ) );
$message = __('Alguien solicitó que se restableciera la contraseña para la siguiente cuenta:') . "\r\n\r\n";
$message .= network_home_url( '/' ) . "\r\n\r\n";
$message .= sprintf(__('Nombre de usuario: %s'), $user_login) . "\r\n\r\n";
$message .= __('Si esto fue un error, ignora este correo electrónico y no pasará nada.') . "\r\n\r\n";
$message .= __('Para restablecer tu contraseña, visita la siguiente dirección:') . "\r\n\r\n";
$message .= '<' . network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') . ">\r\n";
if ( is_multisite() )
$blogname = $GLOBALS['current_site']->site_name;
else
$blogname = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES);
$title = sprintf( __('[%s] Restablecimiento de contraseña'), $blogname );
$title = apply_filters('retrieve_password_title', $title);
$message = apply_filters('retrieve_password_message', $message, $key);
if ( $message && !wp_mail($user_email, $title, $message) )
wp_die( __('El correo electrónico no pudo ser enviado.') . "<br />\n" . __('Posible razón: tu host puede haber deshabilitado la función mail()...') );
echo '<p>El enlace para restablecer la contraseña ha sido enviado a tu correo electrónico. Por favor revisa tu email.</p>';;
}

Esto funcionó para mí. La respuesta aceptada para la versión 3.8.1 no funcionó y resultó en una clave expirada. Específicamente, en el procesamiento de $key
y la actualización de user_activation_key
.

Ninguna de las respuestas anteriores funcionó para mí, así que revisé wp-login.php para ver su funcionalidad predeterminada de restablecimiento. Utilizaron la función get_password_reset_key( $userData ). En caso de que alguien se quede estancado con las respuestas anteriores, aquí está mi solución:
$userData = get_userdata($user_id);
$user_login = $userData->user_login;
$user_email = $userData->user_email;
$key = get_password_reset_key( $userData );
$message = __('Alguien ha solicitado restablecer la contraseña para la siguiente cuenta:') . "\r\n\r\n";
$message .= network_home_url( '/' ) . "\r\n\r\n";
$message .= sprintf(__('Nombre de usuario: %s'), $user_login) . "\r\n\r\n";
$message .= __('Si fue un error, simplemente ignora este correo y no pasará nada.') . "\r\n\r\n";
$message .= __('Para restablecer tu contraseña, visita la siguiente dirección:') . "\r\n\r\n";
$message .= network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login');

Noté que después de actualizar WordPress a la versión 4.3, lo anterior ya no funcionaba para mi plugin personalizado. Siempre indicaba que la clave no era válida.
Cambiar:
$hashed = $wp_hasher->HashPassword( $key );
por
$hashed = time() . ':' . $wp_hasher->HashPassword( $key );
Esto solucionó el problema para mí, espero que ayude a alguien más

WordPress 4.3.1
function retrieve_password($user_login){
global $wpdb, $wp_hasher;
$user_login = sanitize_text_field($user_login); // Sanitiza el campo de entrada
if ( empty( $user_login) ) {
return false; // Retorna falso si el campo está vacío
} else if ( strpos( $user_login, '@' ) ) {
$user_data = get_user_by( 'email', trim( $user_login ) ); // Busca usuario por email
if ( empty( $user_data ) )
return false; // Retorna falso si no encuentra el usuario
} else {
$login = trim($user_login);
$user_data = get_user_by('login', $login); // Busca usuario por nombre de usuario
}
do_action('lostpassword_post'); // Acción hook para después de enviar formulario de contraseña perdida
if ( !$user_data ) return false; // Verifica si existe el usuario
// Redefinir user_login asegura que devolvemos el caso correcto en el email
$user_login = $user_data->user_login;
$user_email = $user_data->user_email;
do_action('retreive_password', $user_login); // Hook mal escrito y obsoleto
do_action('retrieve_password', $user_login); // Hook correcto
$allow = apply_filters('allow_password_reset', true, $user_data->ID);
if ( ! $allow )
return false; // Filtro para permitir/denegar el reseteo
else if ( is_wp_error($allow) )
return false;
$key = wp_generate_password( 20, false ); // Genera clave de reseteo
do_action( 'retrieve_password_key', $user_login, $key );
if ( empty( $wp_hasher ) ) {
require_once ABSPATH . 'wp-includes/class-phpass.php';
$wp_hasher = new PasswordHash( 8, true ); // Crea hash de contraseña
}
$hashed = $wp_hasher->HashPassword( $key );
$wpdb->update( $wpdb->users, array( 'user_activation_key' => time().":".$hashed ), array( 'user_login' => $user_login ) ); // Actualiza clave en BD
$message = __('Alguien ha solicitado restablecer la contraseña de la siguiente cuenta:') . "\r\n\r\n"; // Mensaje del email
$message .= network_home_url( '/' ) . "\r\n\r\n";
$message .= sprintf(__('Nombre de usuario: %s'), $user_login) . "\r\n\r\n";
$message .= __('Si fue un error, ignora este correo y no pasará nada.') . "\r\n\r\n";
$message .= __('Para restablecer tu contraseña, visita la siguiente dirección:') . "\r\n\r\n";
$message .= '<' . network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') . ">\r\n";
if ( is_multisite() )
$blogname = $GLOBALS['current_site']->site_name; // Nombre del sitio en multisitio
else
$blogname = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES); // Nombre del blog
$title = sprintf( __('[%s] Restablecimiento de contraseña'), $blogname );
$title = apply_filters('retrieve_password_title', $title); // Filtro para título
$message = apply_filters('retrieve_password_message', $message, $key); // Filtro para mensaje
if ( $message && !wp_mail($user_email, $title, $message) )
wp_die( __('El correo no pudo ser enviado.') . "<br />\n" . __('Posible razón: tu servidor puede tener deshabilitada la función mail()...') );
echo '<p>Se ha enviado un enlace para restablecer tu contraseña a tu correo electrónico. Por favor revisa tu bandeja de entrada.</p>';;
}

Ejemplo Combinado (Probado en 5.2)
Formulario Frontend
<form id="form-password-reset" action="<?php echo wp_lostpassword_url(); ?>" method="post">
<?php wp_nonce_field( 'ajax-resetpassword-nonce', 'security' ); ?>
<div class="text-align-center margin-bottom-32">
<h3 class="h3">Restablecer tu contraseña</h3>
</div>
<div class="row">
<div class="col-12 col-md-4">
<div class="form-group">
<label for="auth-password-reset-email">Correo electrónico</label>
<div class="form-input-wrapper">
<input type="email" class="form-input" id="auth-password-reset-email" name="email" placeholder="ej. nombre@dominio.com">
</div>
</div>
<button type="submit" class="btn-solid btn-primary btn-block">
Enviar</button>
<p class="form__error margin-top-16">
// TODO puedes mostrar el mensaje aquí
</p>
</div>
<div class="col-12 col-md-4"></div>
</div>
</form>
JS para manejar el envío AJAX
$(form).on('submit', function (event) {
event.preventDefault(event)
const email = $(`#auth-password-reset-email`).val()
const security = $(`#security`).val()
const action = 'authResetPassword'
var has_error = false;
if (email.length <= 0) {
// TODO
has_error = true;
}
if (security.length <= 0) {
// TODO
has_error = true;
}
if (has_error == true) {
// TODO
return false
}
$.ajax({
type: 'POST',
dataType: 'json',
url: ajax.ajaxurl,
data: { action, email, security },
success: function (data) {
if (data.status) {
// TODO
}
if (!data.status) {
// TODO
}
}
})
return false;
})
Lado del servidor para generar el enlace de restablecimiento y enviar el correo
try {
check_ajax_referer('ajax-resetpassword-nonce', 'security');
$response = [
"status" => false,
"error" => true,
"data" => "",
];
extract($_POST);
$user_email = sanitize_text_field($email);
$user = get_user_by( 'email', $user_email );
if( $user instanceof WP_User ) {
$user_id = $user->ID;
$user_info = get_userdata($user_id);
$unique = get_password_reset_key( $user_info );
$unique_url = network_site_url("wp-login.php?action=rp&key=$unique&login=" . rawurlencode($user_info->user_login), 'login');
$subject = "Enlace para restablecer contraseña";
$message = __('Alguien ha solicitado restablecer la contraseña para la siguiente cuenta:') . "\r\n\r\n";
$message = "<p>Hola ".ucfirst( $user_info->first_name ).",</p>";
$message .= network_home_url( '/' ) . "\r\n\r\n";
$message .= sprintf(__('Usuario: %s'), $user_info->user_login) . "\r\n\r\n";
$message .= __('Si fue un error, ignora este correo y no pasará nada.') . "\r\n\r\n";
$message .= __('Para restablecer tu contraseña, visita la siguiente dirección:') . "\r\n\r\n";
$message .= $unique_url;
wp_mail( $user_email, $subject, $message ); // TODO
$response['data'] = [ "message" => __('Enlace para restablecer contraseña enviado.'), 'reset_link' => $unique_url ];
wp_send_json($response);
}
$response['data'] = [ "message" => __('El correo electrónico no existe.') ];
wp_send_json($response);
} catch (Exception $e) {
wp_send_json([
"status" => false,
"error" => false,
"data" => ["message" => $e->getMessage() ]
]);
}
Para mostrar tu propio formulario personalizado de restablecimiento de contraseña
add_action( 'login_form_lostpassword', 'redirect_to_custom_lostpassword' );
function redirect_to_custom_lostpassword() {
if ( 'GET' == $_SERVER['REQUEST_METHOD'] ) {
if ( is_user_logged_in() ) {
// TODO
exit;
}
wp_redirect( home_url( 'restablecer-contrasena-personalizado' ) ); // TODO
exit;
}
}

Como la solución anterior no funcionó para mí, hice algunos cambios menores en el código de Bhavesh Vala.
Reemplaza:
$key = wp_generate_password( 20, false );
Con esto:
$key = get_password_reset_key( $user_data );
Y no necesitas usar la consulta de actualización, así que elimina la siguiente consulta:
$wpdb->update( $wpdb->users, array( 'user_activation_key' => time().":".$hashed ), array( 'user_login' => $user_login ) );
¡Feliz programación!

No pude hacer que ninguna de estas soluciones funcionara elegantemente en WordPress (probando en WP 6.2.2), pero después de revisar el repositorio de WP en Github, resulta que es sorprendentemente simple en la solicitud AJAX para restablecer una contraseña.
/* Asumiendo que ya tienes la variable $user_login configurada */
$results = retrieve_password( $user_login ); // Dispara el correo de restablecimiento de contraseña
if($results === true) {
// Éxito
} else {
// Error
}
Si solo tienes el ID de usuario, puedes recuperar el inicio de sesión usando el siguiente código:
/* Asumiendo que ya tienes la variable $user_id configurada */
$user = get_userdata( $user_id);
$results = retrieve_password( $user->user_login ); // Dispara el correo de restablecimiento de contraseña

Prueba esto
$wpdb->update( $wpdb->users, array( 'user_activation_key' => $key ), array( 'user_login' => $user_login ) );
en lugar de
$wpdb->update( $wpdb->users, array( 'user_activation_key' => $hashed ), array( 'user_login' => $user_login ) );
Me funcionó (WordPress 4.3.1)
