Скрипт форума на PHP своими руками. Часть 4


Прислал: Евгений Токмаков [ 03.07.2008 @ 12:51 ]
Раздел:: [ Статьи по PHP ]


Итак, продолжаем наш разговор о пользователях сайта. Если пользователь забыл свой пароль, он может получить новый, заполнив форму, которую формирует функция newPasswordForm():

<?php
// Если пользователь забыл свой пароль, он может получить новый,
// заполнив эту форму (свой логин и e-mail)
function newPasswordForm()
{
  
$html '';
  if ( isset( 
$_SESSION['newPasswordForm']['error'] ) ) {
    
$info file_get_contents'./templates/infoMessage.html' );
    
$info str_replace'{infoMessage}'$_SESSION['newPasswordForm']['error'], $info );
    
$html $html.$info."n";
    unset( 
$_SESSION['newPasswordForm']['error'] );
  }  
  
$action $_SERVER['PHP_SELF'].'?action=sendNewPassword';
  
$tpl file_get_contents'./templates/newPasswordForm.html' );
  
$tpl str_replace'{action}'$action$tpl );
  
$html $html $tpl;
  return 
$html;
}
?>

Обработчик этой формы - функция sendNewPassword() высылает на e-mail пользователя новый пароль.

<?php
// Функция высылает на e-mail пользователя новый пароль
function sendNewPassword()
{

  
// Если не переданы методом POST логин и e-mail - перенаправляем пользователя
  
if ( !isset( $_POST['username'] ) or
       !isset( 
$_POST['email'] )
      ) 
  {
    
header'Location: '.$_SERVER['PHP_SELF'] );
    die();
  }

  
// Обрезаем переменные до длины, указанной в параметре maxlength тега input
  
$name  substr$_POST['username'], 030 );
  
$email substr$_POST['email'], 060 );

  
// Обрезаем лишние пробелы
  
$name trim$name );
  
$email trim$email );

  
// Проверяем, заполнены ли обязательные поля
  
$error "";
  if ( empty( 
$name ) ) $error $error.'<li>не заполнено поле "Имя"</li>'."n";
  if ( empty( 
$email ) ) $error $error.'<li>не заполнено поле "Адрес e-mail"</li>'."n";

  
// Проверяем поля формы на недопустимые символы
  
if ( !empty( $name ) and !ereg"[-_[:blank:]0-9a-zA-Zа-яА-Я]+"$name ) )
    
$error $error.'<li>поле "Имя" содержит недопустимые символы</li>'."n";
  
// Проверяем корректность e-mail
  
if ( !empty( $email ) and !preg_match"#^[0-9a-z_-.]+@[0-9a-z-.]+.[a-z]{2,6}$#i"$email ) )
    
$error $error.'<li>поле "Адрес e-mail" должно соответствовать формату somebody@somewhere.ru</li>'."n";
  
// Проверять существование такого пользователя есть смысл только в том
  // случае, если поля не пустые и не содержат недопустимых символов
  
if ( empty( $error ) ) {    
    
$query "SELECT id_author FROM ".TABLE_USERS.
              WHERE name='"
.mysql_real_escape_string$name )."' 
              AND email='"
.mysql_real_escape_string$email )."'";
    
$res mysql_query$query );
    if ( !
$res ) {
      
$msg 'Ошибка при создании нового пароля.';
      
$err 'Ошибка при выполнении запроса: <br/>'.
             
$query.'<br/>'.mysql_errno().':&nbsp;'.mysql_error().'<br/>'.
             
'(Файл '__FILE__ .', строка '__LINE__ .')';
      return 
showErrorMessage$msg$errtrue'action=newPasswordForm' );
    }
    
    
// Если пользователь с таким логином и e-mail существует
    
if ( mysql_num_rows$res ) > ) {
      
// Небольшой код, который читает содержимое директории activate
      // и удаляет старые файлы для активации пароля (были созданы более суток назад)
      
if ( $dir opendir'./activate' ) ) {
        
chdir'./activate' );
        
$tmp 24*60*60;
        while ( 
false !== ( $file readdir($dir) ) ) { 
          if ( 
is_file$file ) ) 
            if ( ( 
time() - filemtime$file ) ) > $tmp unlink$file );
        }
        
chdir'..' );
        
closedir$dir );
      }
      
// Как происходит процедура восстановления пароля? Пользователь ввел свой логин
      // и e-mail, мы проверяем существование такого пользователя в таблице БД. Потом
      // генерируем с помощью функции getNewPassword() новый пароль, создаем файл с именем
      // md5( $newPassword ) в директории activate. Файл содержит ID пользователя.
      // В качестве кода активации выступает хэш пароля - md5( $newPassword ). 
      // Когда пользователь перейдет по ссылке в письме для активации своего нового пароля,
      // мы проверяем наличие в директории activate файла с именем кода активации,
      // и если он существует, активируем новый пароль.
      
$id mysql_result$res0);
      
$newPassword getNewPassword();
      
$code md5$newPassword );
      
// file_put_contents( './activate/'.$code, $id );
      
$fp fopen'./activate/'.$code"w" );
      
fwrite($fp$id);
      
fclose($fp);
      
// Посылаем письмо пользователю с просьбой активировать пароль
      
$headers "From: ".$_SERVER['SERVER_NAME']." <".ADMIN_EMAIL.">n";
      
$headers $headers."Content-type: text/html; charset="windows-1251"n";
      
$headers $headers."Return-path: <".ADMIN_EMAIL.">n";
      
$message '<p>Добрый день, '.$name.'!</p>'."n";
      
$message $message.'<p>Вы получили это письмо потому, что вы (либо кто-то, выдающий себя 
                 за вас) попросили выслать новый пароль к вашей учётной записи на форуме '
.
                 
$_SERVER['SERVER_NAME'].'. Если вы не просили выслать пароль, то не обращайте 
                 внимания на это письмо, если же подобные письма будут продолжать приходить, 
                 обратитесь к администратору форума</p>'
."n";
      
$message $message.'<p>Прежде чем использовать новый пароль, вы должны его активировать. 
                 Для этого перейдите по ссылке:</p>'
."n";
      
$link '//'.$_SERVER['SERVER_NAME'].$_SERVER['PHP_SELF'].
              
'?action=activatePassword&code='.$code;        
      
$message $message.'<p><a href="'.$link.'">Активировать пароль</a></p>'."n";
      
$message $message.'<p>В случае успешной активации вы сможете входить в систему, используя 
                 следующий пароль: '
.$newPassword.'</p>'."n";
      
$message $message.'<p>Вы сможете сменить этот пароль на странице редактирования профиля. 
                 Если у вас возникнут какие-то трудности, обратитесь к администратору форума.</p>'
."n";
      
$subject 'Активация пароля на форуме '.$_SERVER['SERVER_NAME'];
      
$subject '=?koi8-r?B?'.base64_encode(convert_cyr_string($subject"w","k")).'?=';
      
mail$email$subject$message$headers );
 
      
$msg 'На ваш e-mail выслано письмо. Чтобы активировать новый пароль, зайдите 
              по адресу, указанному в письме.'
;
      
$html file_get_contents'./templates/infoMessage.html' );
      
$html str_replace'{infoMessage}'$msg$html );
      
      return 
$html;  
    } else {
      
$error $error.'<li>неправильный логин или e-mail</li>'."n";
    }
  }
    
  
// Если были допущены ошибки при заполнении формы - перенаправляем посетителя
  
if ( !empty( $error ) ) {
    
$_SESSION['newPasswordForm'] = array();
    
$_SESSION['newPasswordForm']['error'] = '<p class="errorMsg">При заполнениии формы были допущены
    ошибки:</p>'
."n".'<ul class="errorMsg">'."n".$error.'</ul>'."n";
    
header'Location: '.$_SERVER['PHP_SELF'].'?action=newPasswordForm' );
    die();
  }

}
?>

Как происходит процедура восстановления пароля? Пользователь вводит свой логин и e-mail, мы проверяем существование такого пользователя в таблице БД. Потом генерируем с помощью функции getNewPassword() новый пароль, создаем файл с именем md5( $newPassword ) в директории activate. Файл содержит ID пользователя. В качестве кода активации выступает хэш пароля - md5( $newPassword ). Когда пользователь перейдет по ссылке в письме для активации своего нового пароля, мы проверяем наличие в директории activate файла с именем кода активации, и если он существует, активируем новый пароль.

<?php
// Активация нового пароля
function activatePassword()
{
  if ( !isset( 
$_GET['code'] ) ) {
    
header'Location: '.$_SERVER['PHP_SELF'] );
    die();
  }
  
// Т.к. код активации создан с помощью md5, то он 
  // представляет собой 32-значное шестнадцатеричное число
  
$code substr$_GET['code'], 032 );
  
$code preg_replace"#[^0-9a-f]#i"''$code );
  if ( 
is_file'./activate/'.$code ) and  ( ( time() - filemtime('./activate/'.$code) ) < 24*60*60 ) ) {
    
$file file'./activate/'.$code );
    
unlink'./activate/'.$code );
    
$id_user = (int)trim$file[0] );
    
$query "UPDATE ".TABLE_USERS.
              SET passw='"
.mysql_real_escape_string$code )."' 
              WHERE id_author="
.$id_user;
    
$res mysql_query$query );
    if ( !
$res ) {
      
$msg 'Ошибка при активации нового пароля. Обратитесь к администратору.';
      
$err 'Ошибка при выполнении запроса: <br/>'.
             
$query.'<br/>'.mysql_errno().':&nbsp;'.mysql_error().'<br/>'.
             
'(Файл '__FILE__ .', строка '__LINE__ .')';
      return 
showErrorMessage$msg$errtrue'action=newPasswordForm' );
    }  
    
$message 'Ваш новый пароль успешно активирован.';
  } else {
    
$message 'Ошибка при активации нового пароля. Обратитесь к администратору.';
  }
  
  
$html file_get_contents'./templates/infoMessage.html' );
  
$html str_replace'{infoMessage}'$message$html );
  return 
$html;
}
?>

Функция getNewPassword() возвращает случайно сгенерированный пароль:

<?php
// Функция возвращает случайно сгенерированный пароль
function getNewPassword()
{
  
$length rand1030 );
  
$password '';
  for( 
$i 0$i $length$i++ ) {
    
$range rand(13);
    switch( 
$range ) {
      case 
1$password $password.chrrand(4857) );  break;
      case 
2$password $password.chrrand(6590) );  break;
      case 
3$password $password.chrrand(97122) ); break;
    }
  }
  return 
$password;
}
?>

Пользователь должен иметь возможность изменить свои данные - за это отвечают функции getEditUserForm() и updateUser().

<?php
// Функция возвращает html формы для редактирования данных о пользователе
function getEditUserForm()
{
  
// Если информацию о пользователе пытается редактировать 
  // не зарегистрированный пользователь
  
if ( !isset( $_SESSION['user'] ) ) {
    
header'Location: '.$_SERVER['PHP_SELF'] );
    die();
  }
  
  
$html '';
  
  
// Если при заполнении формы были допущены ошибки
  
if ( isset( $_SESSION['editUserForm'] ) ) {
    
$info file_get_contents'./templates/infoMessage.html' );
    
$info str_replace'{infoMessage}'$_SESSION['editUserForm']['error'], $info );
    
$html $html.$info."n";
    
$email     htmlspecialchars$_SESSION['editUserForm']['email'] );
    
$timezone  $_SESSION['editUserForm']['timezone'];
    
$icq       htmlspecialchars$_SESSION['editUserForm']['icq'] );
    
$url       htmlspecialchars$_SESSION['editUserForm']['url'] );
    
$about     htmlspecialchars$_SESSION['editUserForm']['about'] );
    
$signature htmlspecialchars$_SESSION['editUserForm']['signature'] );
    unset( 
$_SESSION['editUserForm'] );
  } else {
    
$email     htmlspecialchars$_SESSION['user']['email'] );
    
$timezone  0;
    
$icq       htmlspecialchars$_SESSION['user']['icq'] );
    
$url       htmlspecialchars$_SESSION['user']['url'] );
    
$about     htmlspecialchars$_SESSION['user']['about'] );
    
$signature htmlspecialchars$_SESSION['user']['signature'] );
  }
  
  
$action $_SERVER['PHP_SELF'].'?action=updateUser';
  
  
$tpl file_get_contents'./templates/editUserForm.html' );
  
$tpl str_replace'{action}'$action$tpl );
  
$tpl str_replace'{email}'htmlspecialchars$email ), $tpl );
  
$tpl str_replace'{icq}'htmlspecialchars$icq ), $tpl );
  
$tpl str_replace'{url}'htmlspecialchars$url ), $tpl );
  
$tpl str_replace'{about}'htmlspecialchars$about ), $tpl );
  
$tpl str_replace'{signature}'htmlspecialchars$signature ), $tpl );
  
  
$options '';
  for ( 
$i = -12$i <= 12$i++ ) {
    if ( 
$i 
      
$value $i.' часов';
    else
      
$value '+'.$i.' часов';
    if ( 
$i == $_SESSION['user']['timezone'] )
      
$options $options '<option value="'.$i.'" selected>'.$value.'</option>'."n";
    else
      
$options $options '<option value="'.$i.'">'.$value.'</option>'."n";
  }
  
$tpl str_replace'{options}'$options$tpl);
  
$tpl str_replace'{servertime}'date"d.m.Y H:i:s" ), $tpl );
  
// Если ранее был загружен файл - надо предоставить возможность удалить его
  
$unlinkfile '';
  if ( 
is_file'./photo/'.$_SESSION['user']['id_author'] ) ) {
    
$unlinkfile '<br/><input type="checkbox" name="unlink" value="1" />
                  Удалить загруженный ранее файл'
."n";
  }
  
$tpl str_replace'{unlinkfile}'$unlinkfile$tpl );
  
  
$html $html.$tpl;
  
  return 
$html;
}
?>

Обработчик формы:

<?php
// Функция обновляет данные пользователя (обновляет запись в таблице TABLE_USERS)
function updateUser()
{
  
// Если это не зарегистрированный пользователь - функция вызвана по ошибке
  
if ( !isset( $_SESSION['user'] ) ) {
    
header'Location: '.$_SERVER['PHP_SELF'] );
    die();
  }

  
// Если не переданы данные формы - функция вызвана по ошибке
  
if ( !isset( $_POST['timezone'] ) or
       !isset( 
$_POST['icq'] ) or
       !isset( 
$_POST['url'] ) or
       !isset( 
$_POST['about'] ) or
       !isset( 
$_POST['signature'] ) or
       !isset( 
$_FILES['avatar'] ) 
    )
  {
    
header'Location: '.$_SERVER['PHP_SELF'] );
    die();
  }

  
// Обрезаем переменные до длины, указанной в параметре maxlength тега input
  
$password     substr$_POST['password'], 030 );
  
$newpassword  substr$_POST['newpassword'], 030 );
  
$confirm      substr$_POST['confirm'], 030 );
  
$email        substr$_POST['email'], 060 );
  
$icq          substr$_POST['icq'], 012 );
  
$url          substr$_POST['url'], 060 );
  
$about        substr$_POST['about'], 01000 );
  
$signature    substr$_POST['signature'], 0500 );

  
// Обрезаем лишние пробелы
  
$password     trim$password );
  
$newpassword  trim$newpassword );
  
$confirm      trim$confirm );
  
$email        trim$email );
  
$icq          trim$icq );
  
$url          trim$url );
  
$about        trim$about );
  
$signature    trim$signature );

  
// Проверяем, заполнены ли обязательные поля
  
$error '';
  
  
// Если заполнено поле "Текущий пароль" - значит пользователь
  // хочет изменить его или поменять свой e-mail 
  
$changePassword false;
  
$changeEmail false;
  if ( !empty( 
$_POST['password'] ) ) {
    if ( 
md5$_POST['password'] ) != $_SESSION['user']['passw'] ) 
      
$error $error.'<li>текущий пароль введен не верно</li>'."n";
    
// Надо выяснить, что хочет сделать пользователь: 
    // поменять свой e-mail, изменить пароль или и то и другое
    
if ( !empty( $newpassword ) ) { // хочет изменить пароль
      
$changePassword true;
      if ( empty( 
$confirm ) ) $error $error.'<li>не заполнено поле "Подтвердите пароль"</li>'."n";
      
// Проверяем, не слишком ли короткий новый пароль
      
if ( strlen$newpassword ) < MIN_PASSWORD_LENGTH )
        
$error $error.'<li>длина пароля должна быть не меньше '.MIN_PASSWORD_LENGTH.' символов</li>'."n";
      
// Проверяем, совпадают ли пароли
      
if ( !empty( $confirm ) and $newpassword != $confirm 
        
$error $error.'<li>не совпадают пароли</li>'."n";
      
// Проверяем поля формы на недопустимые символы
      
if (  !preg_match"#^[-_0-9a-z]+$#i"$newpassword ) )
        
$error $error.'<li>поле "Новый пароль" содержит недопустимые символы</li>'."n";
      if ( !empty( 
$confirm ) and !preg_match"#^[-_0-9a-z]+$#i"$confirm ) )
        
$error $error.'<li>поле "Подтвердите пароль" содержит недопустимые символы</li>'."n";
    }
    if ( 
$email != $_SESSION['user']['email'] ) { // хочет изменить e-mail
      
$changeEmail true;
      if ( empty( 
$email ) ) $error $error.'<li>не заполнено поле "Адрес e-mail"</li>'."n";
      
// Проверяем корректность e-mail
      
if ( !empty( $email ) and !preg_match"#^[0-9a-z_-.]+@[0-9a-z-.]+.[a-z]{2,6}$#i"$email ) )
        
$error $error.'<li>поле "Адрес e-mail" должно соответствовать формату 
                 somebody@somewhere.ru</li>'
."n";    
    }  
  }  
  
  
// Проверяем поля формы на недопустимые символы
  
if ( !empty( $icq ) and !preg_match"#^[0-9]+$#"$icq ) )
    
$error $error.'<li>поле "ICQ" содержит недопустимые символы</li>'."n";
  if ( !empty( 
$about ) and !preg_match"#^[-[].;:,?!/)(_"s0-9а-яА-Яa-z]+$#i"$about ) )
    
$error $error.'<li>поле "Интересы" содержит недопустимые символы</li>'."n";
  if ( !empty( 
$signature ) and !preg_match"#^[-[].;:,?!/)(_"s0-9а-яА-Яa-z]+$#i"$signature ) )
    
$error $error.'<li>поле "Подпись" содержит недопустимые символы</li>'."n";
    
  
// Проверяем корректность URL домашней странички    
  
if ( !empty( $url ) and !preg_match"#^(//)?(www.)?[-0-9a-z]+.[a-z]{2,6}/?$#i"$url ) )
    
$error $error.'<li>поле "Домашняя страничка" должно соответствовать формату //www.homepage.ru</li>'."n";

  if ( !empty( 
$_FILES['avatar']['name'] ) ) {
    
$ext strrchr$_FILES['avatar']['name'], "." );
    
$extensions = array( ".jpg"".gif"".bmp"".png" );
    if ( !
in_array$ext$extensions ) ) 
      
$error $error.'<li>недопустимый формат файла аватара</li>'."n";
    if ( 
$_FILES['avatar']['size'] > MAX_AVATAR_SIZE 
      
$error $error.'<li>размер файла аватора больше '.(MAX_AVATAR_SIZE/1024).' Кб</li>'."n";
  }

  
$timezone = (int)$_POST['timezone'];
  if ( 
$timezone < -12 or $timezone 12 $timezone 0;

  
// Если были допущены ошибки при заполнении формы - 
  // перенаправляем посетителя на страницу редактирования
  
if ( !empty( $error ) ) {
    
$_SESSION['editUserForm'] = array();
    
$_SESSION['editUserForm']['error'] = '<p class="errorMsg">При заполнениии формы были допущены ошибки:</p>'.
    
"n".'<ul class="errorMsg">'."n".$error.'</ul>'."n";
    
$_SESSION['editUserForm']['email'] = $email;
    
$_SESSION['editUserForm']['timezone'] = $timezone;
    
$_SESSION['editUserForm']['icq'] = $icq;
    
$_SESSION['editUserForm']['url'] = $url;
    
$_SESSION['editUserForm']['about'] = $about;
    
$_SESSION['editUserForm']['signature'] = $signature;
    
header'Location: '.$_SERVER['PHP_SELF'].'?action=editUserForm' );
    die();
  }    

  
// Если выставлен флажок "Удалить загруженный ранее файл"
  
if ( isset( $_POST['unlink'] ) and is_file'./photo/'.$_SESSION['user']['id_author'] ) ) {
    
unlink'./photo/'.$_SESSION['user']['id_author'] );
  } 
  if ( !empty( 
$_FILES['avatar']['name'] ) and 
       
move_uploaded_file $_FILES['avatar']['tmp_name'], './photo/'.$_SESSION['user']['id_author'] ) ) {
    
chmod'./photo/'.$_SESSION['user']['id_author'], 0644 );
  }
  
  
// Все поля заполнены правильно - записываем изменения в БД
  
$tmp '';  
  if ( 
$changePassword ) {
    
$tmp $tmp."passw='".mysql_real_escape_stringmd5$newpassword ) )."', ";
    
$_SESSION['user']['passw'] = md5$newpassword );
  }
  if ( 
$changeEmail ) {
    
$tmp $tmp."email='".mysql_real_escape_string$email )."', ";
    
$_SESSION['user']['email'] = $email;
  }
  
$query "UPDATE ".TABLE_USERS." SET ".$tmp."
            timezone="
.$timezone.",
            url='"
.mysql_real_escape_string$url )."',
            icq='"
.$icq."',
            about='"
.mysql_real_escape_string$about )."',
            signature='"
.mysql_real_escape_string$signature )."'
            WHERE id_author="
.$_SESSION['user']['id_author'];
            
//echo $query;
            //die();
  
$res mysql_query$query );
  if ( !
$res ) {
    
$msg 'Ошибка при обновлении профиля';
    
$err 'Ошибка при выполнении запроса: <br/>'.
           
$query.'<br/>'.mysql_errno().':&nbsp;'.mysql_error().'<br/>'.
          
'(Файл '__FILE__ .', строка '__LINE__ .')';
    return 
showErrorMessage$msg$errtrue'' );
  }
  
// Теперь надо обновить данные о пользователе в массиве $_SESSION['user']
  
if ( $changePassword $_SESSION['user']['passw'] = md5$newpassword );
  if ( 
$changeEmail $_SESSION['user']['email'] = $email;
  
$_SESSION['user']['timezone'] = $timezone;
  
$_SESSION['user']['url'] = $url;
  
$_SESSION['user']['icq'] = $icq;
  
$_SESSION['user']['about'] = $about;
  
$_SESSION['user']['signature'] = $signature;
  
// ... и в массиве $_COOKIES
  
if ( isset( $_COOKIE['autologin'] ) ) {
    
$tmppos strrpos$_SERVER['PHP_SELF'], '/' ) + 1;
    
$path substr$_SERVER['PHP_SELF'], 0$tmppos );
    
setcookie'autologin''yes'time() + 3600*24*COOKIE_TIME$path );
    
setcookie'username'$_SESSION['user']['name'], time() + 3600*24*COOKIE_TIME$path );
    
setcookie'password'$_SESSION['user']['passw'], time() + 3600*24*COOKIE_TIME$path );
  }

  return 
showInfoMessage'Ваш профиль был изменён''' );
}
?>

Продолжение следует...