<?php
/************
*
*    Файл func.php - общий функционал
*
************/

// проверяем, запускаем ли мы наши скрипты локально
function isLocal() 
{
    return ((
$_SERVER['REMOTE_ADDR'] === '127.0.0.1'));

}


// для преобразования данных в JSON, необходимо перевести их в UTF
function makeGoodForJSON($str
{
    return 
stripslashes(trim(iconv('windows-1251''UTF-8'rawurldecode($str))));
}


// вывод данных по запросу Ajax
// возвращает запись в формате JSON
function jsOutput($key$str)
{
    
header('Content-type: text/javascript');
    echo 
json_encode(array($key => makeGoodForJSON($str)));
    exit;
}


// проверка, нужен ли вывод в виде JSON для Ajax'a
function isJs()
{
    return ((isset(
$_REQUEST['js'])) ? true false);
}


// вывод ошибок в зависимости от того, локальный режим или нет;
// необходим JSON или нет
// флаг noPrint указывает, выводить ли ошибку, или возвращать её
function myError($msg_$noPrint false)
{

    
$msg = (isLocal()) ? $msg_ 'Ошибка! Попробуйте ещё раз';

    if (
isJs())
    {
        
jsOutput('error'$msg);    
    }
    
    
$ms '<p><b><font color="#ff0000">Error:</font> '.$msg.'</b></p>';
    
    if (
$noPrint === true)
    {    
        return 
$ms;
    }
    else
    {
        echo 
$ms;
    }
}

?>


<?php
/************
*
*    Файл class.FileKeeper.php - класс для работы с Файловой системой
*
************/
include_once('func.php');
class 
FileKeeper
{
    private 
$descriptor;
    private 
$filename 'vote_new.txt';
    private 
$data = array();
    private 
$isReaded false;
    
    protected function 
__construct()
    {
        
//$this->setFilename($_SERVER['DOCUMENT_ROOT'].'/'.$this->filename);
        
$this->setFilename($this->filename);
            
    }


    public function 
setFilename($file)
    {
        if (!
file_exists($file))
        {
            
$fp = @fopen($file'w') or 
                        
myError('Не могу создать файл "'.$file.'"');
            
fclose($fp);
        }
        
        
$this->filename $file;
    }


    private function 
getDescriptor()
    {
    
        if (
is_resource($this->descriptor))
        {
            return 
$this->descriptor;
        }
        
        
$this->descriptor = @fopen($this->filename'r+') or 
                    
myError('Не могу открыть файл "'.$this->filename.'"');
        return 
$this->descriptor;
    }


    private function 
getWriteDescriptor()
    {

        
$this->descriptor = @fopen($this->filename'w+') or 
                    
myError('Не могу открыть файл "'.$this->filename.'" на запись');
        return 
$this->descriptor;
    }    

    
    private function 
setLock($flag LOCK_SH)
    {
        if (!@
flock($this->getDescriptor(), $flag))
        {
            
myError('Не могу заблокировать на чтение файл "'.$this->filename.'"');
        }
    }

    
    protected function 
getData()
    {
        if (
$this->isReaded)
        {
            return 
$this->data;
        }
        
        
$this->setLock();

        
$data = @file_get_contents($this->filename);
        
$this->isReaded true;
        if (
strlen($data) < 6)
        {
            return array();
        }
        else
        {
            
$data unserialize($data);
            
            if (!
is_array($data))
            {
                
myError('Не могу прочесть данные из файла "'.$this->filename.'"');
            }
            else
            {
                
$this->data $data;
                unset(
$data);
            }
        }
        
        
$this->setLock(LOCK_UN);
        return 
$this->data;    
    }

    
    protected function 
setData($data)
    {
        if (
is_array($data))
        {
            
$this->data $data;
        }    
    }


    protected function 
writeData()
    {
        
$this->setLock(LOCK_EX);
        if (-
== fwrite($this->getWriteDescriptor(), serialize($this->getData()))) 
        {
            
myError('Не могу записать данные в файл!');
        }
        
$this->setLock(LOCK_UN);
    }


    public function 
getValue($chapter$key)
    {
        
$a $this->getData();
        if (isset(
$a[$chapter][$key]))
        {
            return (float)(
$a[$chapter][$key]);
        }
        
        return 
0;
    }


    public function 
setValue($chapter$key$newData)
    {
        
$a $this->getData();

        
$a[$chapter][$key] = (float)($newData);

        
$this->setData($a);
        unset(
$a);
    }

    
    function 
__destruct()
    {
        if (
is_resource($this->descriptor))
        {
            
fclose($this->descriptor);
        }
    }        
}

?>


<?php
/************
*
*    Файл class.DataBase.php - класс, осуществляющий работу через БД
*    (было бы лучше сделать для работы с БД отдельный класс, которому
*    этот класс делегировал бы обязанности по работе с данными)
*
************/
include_once('func.php');

define('HOST''localhost');    // хост
define('USER''root');        // пользователь
define('PASSWORD''');        // пароль
define('DATABASE''php5');    // название БД

class DataBase
{
    private 
$descriptor;
    private 
$table_name 'vote';        // имя таблицы
    
private $_database_name DATABASE;
    private 
$_host HOST;
    private 
$_user USER;
    private 
$_password PASSWORD;
    
    private 
$data = array();
    private 
$isReaded false;
    
    
    protected function 
__construct()
    {
        
        
$this->descriptor 
            
mysql_connect($this->_host$this->_user$this->_password) or 
                        
myError('Не могу подключиться к БД!');
                        
        if (!
mysql_select_db($this->_database_name$this->descriptor))
        {
            
myError('Не могу выбрать базу "'.$this->_database_name.'"!');
        }        
    }

    private function 
query($query)
    {
        
$res mysql_query($query) or myError('Не могу выполнить запрос: "'.$query.'" '.mysql_errno().': '.mysql_error());

        return 
$res;
    }

    protected function 
getData($chapter)
    {
        if (isset(
$this->data[$chapter]))
        {
            return 
$this->data[$chapter];
        }
        
        
$query 'SELECT * FROM vote WHERE chapter = "'.$chapter.'" LIMIT 1';
        
        
$result $this->query($query);
        
        
$row mysql_fetch_assoc($result);
        
        
$this->data[$chapter] = (is_array($row) && (count($row) > 0)) ? $row : -1;
        
        
mysql_free_result($result);
        unset(
$row);
        
        return 
$this->data[$chapter];    
    }
    

    public function 
getValue($chapter$key)
    {
        
$a $this->getData($chapter);

        if (isset(
$a[$key]))
        {
            return (float)(
$a[$key]);
        }
        
        return 
0;
    }

    public function 
setValue($chapter$array = array())
    {
        if (
count($array) < 2)
        {
            
myError('Не переданы необходимые параметры');
        }
        
        
$a $this->getData($chapter);
        
        if (
$a != -1)
        {
            
$query 'UPDATE ';
            
$where ' WHERE chapter="'.$chapter.'"';
        }
        else
        {
            
$query 'INSERT INTO ';
            
$array['chapter'] = $chapter;
            
$where '';
        }
        
        
$query.= $this->table_name.' SET ';
        
        foreach (
$array as $key => $value)
        {
            
$query.= $key.'="'.$value.'", ';
        }
        
        
$query substr($query0, -2);
        
$query.= $where;

        
$this->query($query);
        
        unset(
$this->data[$chapter]);
        unset(
$a);
    }
    
    function 
__destruct()
    {
        if (
is_resource($this->descriptor))
        {
            
mysql_close($this->descriptor);
        }
    }        
}



?>


<?php
/************
*
*    Файл class.Voter.php - Модель. Для примера, содержит два класса:
*    Voter, работающий посредством файлов и DBVoter - работающий через БД
*    Оставьте тот, который сочтёте нужным, не забыв агрегировать 
*    выбранный класс в контроллере.
*
************/
include_once('func.php');
include_once(
'class.FileKeeper.php');
include_once(
'class.DataBase.php');

class 
Voter extends FileKeeper
{
    static 
$instance false;
    
    protected function 
__construct() 
    {
        
parent::__construct();
    }
    private function 
__clone() {}    
    
    public static function 
getInstance() 
    {
            if (!
is_object(self::$instance)) 
            {
                    
$c __CLASS__;
                   
self::$instance = new $c;
            }

            return 
self::$instance;
    }
    
    function 
getVotesSum($chapter)
    {
        return 
$this->getValue($chapter'summ');
    }

    function 
setVotesSum($chapter$sum)
    {
        
$summa $this->getVotesSum($chapter);
        
$summa += $sum;
        
$this->setValue($chapter'summ'$summa);
    }

    function 
getVotesCount($chapter)
    {
        return 
$this->getValue($chapter'count');
    }

    function 
setVotesCount($chapter)
    {
        
$ct $this->getVotesCount($chapter);
        
$ct += 1;
        
$this->setValue($chapter'count'$ct);    
    }

    function 
getRate($chapter)
    {
        
$count = ($this->getVotesCount($chapter) > 0) ? $this->getVotesCount($chapter) : 1;
        return 
round(($this->getVotesSum($chapter)/$count), 2);
    }


    function 
setRate($chapter$mark)
    {
        
$this->setVotesCount($chapter);
        
        
$this->setVotesSum($chapter$mark);

        
$this->writeData();    
    }        
}


class 
DBVoter extends DataBase
{
    static 
$instance false;
    
    protected function 
__construct() 
    {
        
parent::__construct();
    }
    private function 
__clone() {}
    
    static function 
getInstance() 
    {
            if (!
is_object(self::$instance)) 
            {
                    
$c __CLASS__;
                   
self::$instance = new $c;
            }

            return 
self::$instance;
    }
    
    function 
getVotesSum($chapter)
    {
        return 
$this->getValue($chapter'summ');
    }

    function 
getVotesCount($chapter)
    {
        return 
$this->getValue($chapter'count');
    }


    function 
getRate($chapter)
    {
        
$count_ = ($this->getVotesCount($chapter) > 0) ? $this->getVotesCount($chapter) : 1;
        return 
round(($this->getVotesSum($chapter)/$count_), 2);
    }


    function 
setRate($chapter$mark)
    {
        
$array = array(
            
'count' => ($this->getVotesCount($chapter) + 1),
            
'summ'    => ($this->getVotesSum($chapter) + $mark)
            );
        
        
$this->setValue($chapter$array);    
    }        
}

?>


<?php
/************
*
*    Файл class.VoteController.php - Контроллер.
*    
*
************/
include_once('class.Voter.php');
include_once(
'class.VoteView.php');

// параметр, передаваемый из $_GET или REQUEST
define('VOTE''v');

// максимальное количество баллов
define('MAX_VOTE'5);

class 
VoteController
{

    private static 
$voter;
    private static 
$chapter;
    
    private static function 
init()
    {
        if (!
is_object(self::$voter))
        {
            
self::$voter Voter::getInstance();
        }    

        
self::$chapter $_SERVER['PHP_SELF'];
    }

    public static function 
displayRating($chapter_ false)
    {
        
self::init();
        
        
$error false;
        
        
$chapter self::checkChapter($error$chapter_);

        if (
$error !== false)
        {
            
VoteView::displayError($error);
            return 
true;
        }
        
        if (
self::getVoteRequest())
        {
            if (!
self::checkForSpam(true))
            {
                
$error 'Подозрение на накрутку рейтинга';
            }
            else
            {
                
// приём оценки от пользователя
                
$error self::updateRating($chapter);
                
                if (
$error === false)
                {
                    
// вывод результатов
                    
$rate self::$voter->getRate($chapter);
                    
VoteView::displayResults($rateMAX_VOTE);
                    return 
true;
                }
            }    
        }
        
        
// вывод формы для голосования или ошибки
        
if ($error === false)
        {
            
self::checkForSpam();
            
            
// добываем количество голосов и среднюю оценку
            
$total self::$voter->getVotesCount($chapter);
            
$rate self::$voter->getRate($chapter);
            
VoteView::displayRateForm($rate$totalVOTEMAX_VOTE);
        }
        else
        {
            
VoteView::displayError($error);
        }
        return 
true;
    }
    
    private static function 
updateRating($chapter false)
    {            
        if ((
$error self::checkMark(self::getVoteRequest())) === false)
        {
            
self::$voter->setRate($chapterself::getVoteRequest());
            return 
$error;
        }

        return 
myError($errortrue);

    }
    
    private static function 
checkMark($mark)
    {
        if ((
$mark <= 0) || ($mark MAX_VOTE))
        {
            return 
'Недопустимое значение оценки!';
        }
        
        return 
false;
    }
    
    
    private static function 
checkChapter(&$error$chapter_ false)
    {
        
$chapter = ($chapter_ !== false) ? $chapter_ self::$chapter;

        if (
is_numeric($chapter))
        {
            
$chapter = (int) $chapter;
            if (
$chapter == 0)
            {
                
$error 'Неверное значение раздела голосования!';
            }
        }
        else
        {
            
$chapter = (string) $chapter;
            if (
$chapter === '')
            {
                
$error 'Неверное значение раздела голосования!';
            }            
        }
        
        return 
$chapter;
    }


    
// если Вы будете использовать подобную проверку - она должна осуществляться до любого вывода
    // либо session_start() должна вызываться ранее, либо сперва должен быть вызов ob_start();
    
private static function checkForSpam($set false)
    {
        @
session_start();
        
        if (
$set === false)
        {
            
$_SESSION['vote_ticket'] = md5('some_hash'.microtime());

        }
        else
        {
            if ((!isset(
$_SESSION['vote_ticket'])) || (strlen($_SESSION['vote_ticket']) != 32))
            {
                return 
false;
            }
            unset(
$_SESSION['vote_ticket']);
            return 
true;
        }    
    }


    private static function 
getVoteRequest()
    {
        if (isset(
$_REQUEST[VOTE]))
        {
            if (
intval($_REQUEST[VOTE]) > 0)
                return (int) 
$_REQUEST[VOTE];
        
        }
        return 
false;
    }
}

?>


<?php
/************
*
*    Файл class.VoteView.php - представление
*    Лучше сделать два класса - один выводит данные в JSON
*    Другой - выводит обычный HTML
*
************/
//error_reporting(E_ALL);
//ini_set('display_errors', '1');
// количество "звёздочек"
define('STAR_COUNT'5);
// Максимальная ширина блока  = (ширина одной звёздочки = 25px) * кол-во звёздочек 
define('BLOCK_WIDTH'25.3*STAR_COUNT);

class 
VoteView
{

    public static function 
displayRateForm($rate$total$parName 'v'$rateCount 5)
    {
        
$width round((BLOCK_WIDTH/$rateCount)*$rate0);
?>
    <!-- RATING -->
    <p class='rate'><strong>Оценка материала:</strong></p>
    <div id='ratingRoot'>
    <div id='currentRating' style='width: <?=($width);?>px;'>&nbsp;</div>
    <div id='rating'>
<?
        
for ($i 1$i <= STAR_COUNT$i++)
        {
            echo 
'<a href="?'.$parName.'='.$i.'" title="'.$i.' б."> '.$i.'</a>';
        }
?>
    </div>
<?
        
if ($total == 0)
        {
?>
    <div class='rate'>На данный момент нет голосовавших</div>
<?
        
}
        else
        {
?>
    <div class='rate'>Текущая оценка <strong><?=$rate;?></strong> из <?=$rateCount;?><br>
    Всего проголосовавших: <strong><?=$total;?></strong></div>
<?
        
}
?>    
    </div>
    <script type='text/javascript' src='rate.js'></script>
    <script type='text/javascript' src='ajax.js'></script>
    <script type='text/javascript'>
<!--
    initializeRating();
-->
    </script>
<?            
    
}


    public static function 
displayResults($rate$rateCount 5)
    {
        
// это выражение лучше выделить в отдельную функцию
        
$width round((BLOCK_WIDTH/$rateCount)*$rate0);
        
$s '';

        
        if (!
isJS())
        {
            
$s.= '<div id="ratingRoot">';
        }
        
$s.= '<div id="currentRating" style="width: '.$width.'px;">&nbsp;</div>';
        
$s.= '<div id="rating">&nbsp;';
        
$s.= '    </div>
            <div class="rate">Спасибо, Ваш голос учтён!</div>'
;
        if (!
isJS())
        {
            
$s.= '</div>';
        }
    
        if (!
isJS())
        {
            echo 
$s;
        }
        else
        {
            
jsOutput('response'$s);    
        }        
    }

    public static function 
displayError($error)
    {    
        if (!
isJS())
        {
?>
        <p class='error'><?=$error;?></p>

<?
        
}
        else
        {
            
jsOutput('error''<p class="error">'.$error.'</p>');
        }        
    }

}

?>