воскресенье, 6 марта 2011 г.

Сайт для избранных! (мини-соцальная сеть средствами ООП php)

Итак, как сказано в заголовке попробую поделиться впечатлениями как я реализовывал систему учетных записей и прав доступа в отдельно взятом сайте на самопальном php-движке.

Внимание!

Пост ОЧЕНЬ старый, висит в ознакомительно-развлекательно-ностальгических целях. Примеры полны уязвимостей для SQL-инъекций.

Сразу оговорюсь, я студент и мне интересен именно проект с нуля, его совершенствование, так сказать, проба пера. Поэтому возможны некоторые упущения. Но процент "лазеек" достаточно мал, чтобы ими смог воспользоваться даже продвинутый пользователь. Абсолютно безопасных проектов не существует, есть лишь достаточно защищенные...

Приступим. В кратце о идее создания сайта в общем:

1) Ваяем шаблонную страницу в HTML со стилями CSS, гоняя блоки-divы туда-сюда, попутно проверяя разъезжающуюся "красоту" методом изменения масштаба страницы (Ctrl + скролл мышью). Доводим до готовности.

2) "Режем" шаблон на блоки html-разметки, которые сохраняем в отдельные файлы, а потом прицепляем на старое место в шаблоне с помощью директивы-функции php - include('путь_к_файлу_блока'). В итоге у нас...ничего не должно измениться для пользователя! У нас есть каркас, который с помощью include подцепляет недостающие блоки к каркасу. Зачем это нужно? Если вы сделаете на основе нашего каркаса (методом copy-paste) другие страницы (например, главную страницу, каталог статей и т.п., что понадобится!), то, чтобы внести изменения, к примеру, в блок навигации, не придется ползать по каааааждой страницы и ручками менять эти значения, а достаточно поменять этот самый блок навигации. Это понятно.

3) Теперь можно перейти к реализации. Для начала, я бы посоветовал создать единый скрипт подключения к MySQL-базе, ибо почти на каждой странице он нам понадобится. А при переезде на хостинг не охото будет везде менять хост-адрес, имя, пароль... Вообщем предлагаю такую весчь в отдельном файле base_connect.php, либо в удобной вам библиотеке функций :

<?php
function connect() {
    $link = mysql_connect('localhost','root','qqq');
    return $link;
}
?>

Теперь займемся интересным - авторизацией. Предположим, создали вы формочку с полями для ввода логина/пороля. С нее необходимо сослаться на скрипт-обработчик. Он выглядит примерно так:

<?php
// Вот прямо здесь мы включаем нашу библиотеку из одной функции)) Она у меня в директории lib
include('lib/base_connect.php');

// Функция возвращает линк, с помощью которого неплохо бы было завершать
// соединение с БД...ну или управлять
$link = connect();
mysql_select_db('dbase',$link);

// Обратите внимание: пара логин-пароль передается только методом post! Не хотелось бы
// видеть свой "супер секретный" пароль в адресной строке...
$res = mysql_query("SELECT * FROM user WHERE nick='".$_POST['login']."'");
$user = mysql_fetch_array($res);

// Да-да, пароль шифрованный, поэтому сравнивается с помощью функции crypt()
if(crypt( strip_tags($_POST['pass']),$user['pass'] ) ===  $user['pass']){

    // Если пользователь прошел авторизацию - ему отсылаются куки с его id
    // на час (3600 секунд) с текущего момента
    setcookie('id',$user['id'],time()+3600);
}

// Редирект из поддиректории на главную страницу.
echo"<META HTTP-EQUIV="Refresh" CONTENT="0; URL=../index.php">";
?>

Но вот как быть с данными? Для какого-нибудь банального приветствия, проверки доступа в "раздел для избранных" и т.п. необходимо будет писать запрос к базе и оперировать фиговой тучей промежуточных переменных? Есть вопрос - есть ответ. Проще данные извлечь и применять по необходимости. Для этого мы воспользуемся средствами ООП. Создадим класс User, который будет "знать" все о том, кто и что из себя представляет пользователь - анкетные данные, права доступа и т.п. Потом мы будем регистрировать пользователя на страницах. Т.е. создавать объект класса и считывать в него данные из базы. Класс, для примера, будет выглядеть так:

<?php
// Класс проверки доступа хранит "уровень доступа" для каждого пользователя.
// Ниже поля с примерным названиями страниц, куда может ограничиваться доступ
class perm {            
    var $users_view;    // Просмотр списка пользователей
    var $person_view;    // Просмотр детализированной информации
    var $user_edit;        // Редактирование информации о пользователях
    var $art_all;        // Просмотр списка статей
    var $art_priv;        // Просмотр списка ПРИВАТНЫХ статей
    var $del_com;        // Возможность удаления ЧУЖИХ комментариев

    // Это конструктор класса perm. Он узнавая по номеру группы пользователя
    // ставит флаги, куда можно (true) и куда нет (false)
    function perm($user) {
        $this->users_view = false;
        $this->person_view = false;
        $this->user_edit = false;
        $this->art_all = false;
        $this->art_priv = false;
        $this->del_com = false;

        if($user->group <= 4){
            $this->users_view = true;
            $this->person_view = true;
            $this->art_all = true;
        }
        if($user->group <=3){
            $this->art_priv = true;
        }
        if($user->group <= 2){
            $this->del_com = true;
            $this->user_edit = true;
        }
    }
}
//=======================================================================
// Функция для автоматического сбора информации из базы по id, возвращает массив с данными. 
// Используется в конструкторе ниже.
function find_info($id) {
    $link = connect();
    mysql_select_db('dbase',$link);
    $res = mysql_query("SELECT * FROM user WHERE id='$id'",$link);
    $arr = mysql_fetch_array($res);
    return $arr;
}
//=======================================================================
class user {
    var $id;// Здесь идет список свойств класса user, их значение понятно из названия
    var $nick;
    var $group;
    var $fname;
    var $lname;
    var $dol;
    var $tel;
    var $icq;
    var $skype;
    var $email;
    var $about;
    var $adr;
    var $dater;

    /* ДАлее тоже свойство, но которое в свою очередь будет являться экземпляром класса 
    perm (от permission - привилегии), в нашем случае - структура с несколькими булевыми 
    переменными, ассоциируемые с разделами, куда можно, а куда нет.*/

    /*Права*/
    var $user_perm;
    /*--------------*/
/* Конструктор класса, вызываемый при регистрации экземпляра класса. 
Нужен для первоначального заполнения характеристик дального пользователя (имя, ник, и т.п.)*/
    function user() {                                 
        $this->id = $_COOKIE['id'];
        $info = find_info($this->id);
        $this->group = $info['group'];
        $this->nick = $info['nick'];
        $this->fname= $info['fname'];
        $this->lname= $info['lname'];
        $this->dol= $info['dol'];
        $this->tel= $info['tel'];
        $this->icq= $info['ocq'];
        $this->skype= $info['skype'];
        $this->email= $info['e-mail'];
        $this->about= $info['about'];
        $this->adr= $info['adr'];
        $this->dater= $info['date'];
        $this->user_perm = new perm($this);
    }
    function get_group(){
      return $this->group;
    }
}

Это мы поместим тоже в библиотеку классов user.php. Чтобы пользователь стал активным, нам нужно создавать его объект на странице и запускать конструктор, о котором мы говорили выше. Регистрировать нужно только если пользователь авторизовался, т.е. у него должна быть кука с его id. Проверяем ее наличие через функцию isset(). Пишем скрипт:

<?php
// Второе сравнение нужно для возможности удаления куки (или присваивания пустого значения)
if(    (isset($_COOKIE['id'])) && ($_COOKIE['id'] != '') ) {
    $reg_user = new user();
    $user_perm = new perm($reg_user); 
}
?>

Теперь на каждой странице подключаем библиотеки и ифом проверяем возможность доступа к информации. Как пример, приведу код моей страницы с ограниченным доступом к приватным статьям (articles или arts для краткости, они состаят в базе из id, названия title, аннотации short и самого текста, но тут он не используется, т.к. циклом выводятся названия и аннотации) на сайте.

<?php
/*--------------------------
Подключаемые библиотеки
----------------------------*/
include('lib/base_connect.php');
include('lib/user.php');
include('lib/reg_user.php');
/*==========================*/
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251" />
<title>Специальные статьи</title>
<link href="style/main.css" rel="stylesheet" type="text/css" />
<link href="style/arts.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="main_frame">
<?php include('blocks/header.php'); ?>
<?php include('blocks/navi.php'); ?>
<div id="line"></div>
<div id="body">
<!-- Блок инфомации -->
<?php

// Здесь вот и проверяется доступ. Если 1, то войдем, если 0, то выдадут сообщение (см. else)
if($reg_user->user_perm->art_priv) {

    $link = connect();
    mysql_select_db('dbase',$link);

    // Выбераем из базы нужную информацию по всем статьям, с приватным доступом в массив
    $result = mysql_query('SELECT id,title,short FROM arts WHERE private = true',$link);

    while($arts = mysql_fetch_array($result)) { // В цикле выводим все статьи
        $i++;
        echo '<div class='article'>';
        echo '<a href='art_view.php?art_id='.$arts['id'].''>';
        echo '<H4>'.$i.'. '.$arts['title'].'</H4>';
        echo '</a>';
        echo '<p>'.$arts['short'].'</p>';
        echo '</div>';
    }
} else {
    echo 'Недостаточно прав для просмотра контента!';
}
?>
<!-- Конец блока инфомации -->
</div>
<?php include('blocks/footer.php'); ?>
</div>
</body>
</html>

Все, надеюсь, дал идею и пищу для размышления. Удачи!

Комментариев нет:

Отправить комментарий