Ten artykuł jest archiwalny, korzystasz z niego na własną odpowiedzialność.

System ankiet w PHP

System ankiet to bardzo przydatne narzędzie. Dzięki niemu możemy sprawdzić np. co użytkownicy myślą o stronie lub jakiej przeglądarki używają.

Przedstawione przeze mnie rozwiązanie będzie się opierało na obiektowym PHP5 oraz bazie danych MySQL. Na wstępie zaznaczę że niektóry kod jest bardzo podobny do tego w tutorialu o Systemie podstron.

Na początek należy stworzyć odpowiednie tabele w bazie danych. Będą to ankiety (będzie przechowywać nazwę ankiety i określać która jest aktywna), odpowiedzi (nazwa odpowiedzi wraz z ilościami głosów) i glosy (ta tabela będzie przechowywać ip głosujących aby nie mogli ponownie głosować, będzie też zabezpieczenie na ciasteczka).

CREATE TABLE ankiety (
  id int(11) NOT NULL AUTOINCREMENT,
  tytul varchar(255) NOT NULL,
  aktywna int(11) NOT NULL,
  PRIMARY KEY  (id)
) ENGINE=MyISAM ;
 
CREATE TABLE odpowiedzi (
  id int(11) NOT NULL AUTOINCREMENT,
  id_ankiety int(11) NOT NULL,
  tytul varchar(255) NOT NULL,
  ilosc int(11) NOT NULL,
  PRIMARY KEY  (id)
) ENGINE=MyISAM ;
 
CREATE TABLE glosy (
  id int(11) NOT NULL AUTO_INCREMENT,
  id_ankiety int(11) NOT NULL,
  ip varchar(255) NOT NULL,
  PRIMARY KEY  (id)
) ENGINE=MyISAM ;

Jeśli stworzyliśmy już tabelę w bazie danych czas zrobić plik ankiety.class.php. To plik w którym będą trzymane wszystkie funkcje klasy Strony.

<?php
 
class Ankiety {
 
    // Hasło do panelu admina
    private $haslo = 'haslo123';
 
    // Host bazy danych
    private $dbhost = 'localhost';
 
    // Użytkownik bazy danych
    private $dbuser = 'root';
 
    // Hasło do bazy danych
    private $dbpass = '';
 
    // Nazwa bazy danych
    private $dbname = 'test';
 
    function __construct() {
        mysql_connect( $this->dbhost, $this->dbuser, $this->dbpass ) or die(mysql_error());
        mysql_select_db( $this->dbname ) or die(mysql_error());
        mysql_query("SET NAMES utf8");
        mysql_query("SET CHARACTER SET utf8");
        mysql_query("SET collation_connection = utf8_polish_ci");
    }
 
    function __destruct() {
        mysql_close();
    }
 
    // inne funkcje
}
 
?>

W powyższym kodzie stworzyliśmy klasę Ankiety wraz z prywatnymi zmiennymi (co one przechowują jest opisane w komentarzach). Dodatkowo w konstruktorze klasy jest połączenie z bazą danych. Przy połączeniu ustawiamy kodowanie na UTF-8 gdyż jest ono bardzo zalecane i będziemy z niego korzystać w tym tutorialu. W destruktorze rozłączamy się z bazą danych. Tam gdzie jest zaznaczone komentarzem będziemy wklejać inne funkcje.

Na początek stworzymy sobie funkcję pokazującą aktywną ankiete.

  function pokaz() {
        // 1
        if(isset($_POST['odpowiedz'])) $this->dodaj_odpowiedz($_POST['id_ankiety'], $_POST['odpowiedz']);
 
        $result = mysql_query("SELECT * FROM ankiety WHERE aktywna = '1' LIMIT 1");
        $row = $this->stripslashes_deep( mysql_fetch_array($result) ); // 2
 
        $glosowal = $this->glosowal($row['id']); // 3
 
        // 4
        $odpowiedzi = mysql_query("SELECT * FROM odpowiedzi WHERE id_ankiety = '{$row[id]}' ORDER BY ilosc DESC");
        $odp = Array();
        $glosow = 0;
        while($row_odp = mysql_fetch_array($odpowiedzi)) { // 5
            $odp[$row_odp['id']] = $this->stripslashes_deep( $row_odp );
            $glosow = $glosow + $row_odp['ilosc'];
        }
        echo '<div id="ankieta">
            <h3>'.$row['tytul'].' (głosów: '.$glosow.')</h3>'; // 6
 
        if($glosowal) { // 7
            foreach($odp as $odpowiedz) {
                // 8
                $procenty = ($odpowiedz['ilosc']!='0') ? (round(($odpowiedz['ilosc']/$glosow)*100)) : '0';
                echo '<div class="odpowiedz">
                    <b>'.$odpowiedz['tytul'].'</b> <span class="ilosc">'.$procenty.'% ('.$odpowiedz['ilosc'].')</span>
                    <div class="pasek" style="width: '.$procenty.'%"></div>
                </div>';
            }
        } else { // 9
            ksort($odp);
            echo '<form action="" method="POST">';
            foreach($odp as $odpowiedz) {
                echo '<div class="odpowiedz">
                    <input type="radio" name="odpowiedz" value="'.$odpowiedz['id'].'"> '.$odpowiedz['tytul'].'<br>
                </div>';
            }
            echo '<input type="hidden" name="id_ankiety" value="'.$row['id'].'">
                <input type="submit" value="Głosuj">
                </form>';
        }
        echo '</div>';
    }
  1. Jeśli jest w użyciu $POST['odpowiedz'] to wykonujemy funkcję dodajodpowiedz() wraz z id ankiety i id odpowiedzi.
  2. Wykonujemy zapytanie na aktywną ankietę i używamy na wynikach funkcji stripslashes_deep którą stworzymy później.
  3. Sprawdzamy czy użytkownik już oddał głos, jako parametr do funkcji podajemy id ankiety.
  4. Wykonujemy zapytanie na wszystkie odpowiedzi sortując wg. ilości oddanych głosów.
  5. W pętli while pobieramy rekordy i dodajemy je do tablicy $odp. Używamy tutaj też funkcji stripslashes_deep. Przy okazji obliczamy ilość oddanych głosów (można by to zrobić odpowiednim zapytaniem do bazy danych ale po co nam dodatkowe zapytanie ;) ).
  6. Wyświetlamy tytuł ankiety wraz z ilością głosów.
  7. Jeśli użytkownik głosował to wyświetlamy odpowiedzi wraz z ilością oddanych na nie głosów.
  8. Obliczamy ilość procent oddanych głosów na daną odpowiedź. ilość głosów na odpowiedź/ilość wszystkich głosów*100
  9. Jeśli użytkownik nie głosował to sortujemy tablicę z odpowiedziamy wg. klucza (czyli wg. ID) i wyświetlamy radio-buttony z odpowiedziami.

Troche to skomplikowane ale da radę się w tym połapać. Teraz stworzymy funkcję na dodawanie odpowiedzi.

  function dodaj_odpowiedz($id_ankiety, $odpowiedz) {
        if($this->glosowal($id_ankiety)) { // 1
            echo '<p>Już głosowałeś w tej ankiecie!</p>';
        } else {
            $ip = $_SERVER['REMOTE_ADDR'];
            mysql_query("UPDATE odpowiedzi SET ilosc = ilosc + 1 WHERE id = '$odpowiedz'");
            mysql_query("INSERT INTO glosy (`id_ankiety`, `ip`) VALUES ('$id_ankiety', '$ip')"); // 2
 
            $ob = ini_get('output_buffering');
            if(!headers_sent() || strtolower($ob) == 'on' ) { // 3
                setcookie("ankieta_$id_ankiety", "1", time()+(60*60*24*365));
            }
        }
    }
  1. Najpierw sprawdzamy czy użytkownik już głosował.
  2. Pobieramy IP, zwiększamy ilość głosów o 1 i dodajemy do tabeli glosy jeden głos wraz z IP.
  3. Tutaj mamy trochę zabezpieczeń aby nam nie wyskakiwały błędy. Jeśli nagłowki nie zostały wysłane albo jest włączone buforowanie to wysyłamy ciasteczko z ID ankiety.

Teraz czas na funkcję sprawdzającą czy użytkownik już oddał głos w ankiecie.

  function glosowal($id) {
        $ip = $_SERVER['REMOTE_ADDR'];
        $result = mysql_query("SELECT id FROM glosy WHERE ip = '$ip' AND id_ankiety = '$id'");
        $num_rows = mysql_num_rows($result);
        return ($num_rows > 0 || isset($_COOKIE['ankieta_'.$id])) ? true : false;
    }

Pobieramy z tabeli wszystkie rekordy z danym IP i id ankiety. Jeśli znalazło jakieś rekordy lub istnieje odpowiednie ciasteczko to zwracamy true (głosował) w przeciwnym wypadku false (nie głosował).

Teraz trzeba zająć się panelem admina. Będziemy tam mogli dodawać, edytować i usuwać ankiety - wszystko to czego potrzebujemy ;)

  function admin() {
        if(!isset($_SESSION['admin']) || $_SESSION['admin'] != true) {
            $this->admin_logowanie();
            return;
        }
        switch($_GET['akcja']) {
            case 'dodaj':
                $this->admin_dodaj();
            break;
 
            case 'edytuj':
                $this->admin_edytuj($_GET['id']);
            break;
 
            case 'usun':
                $this->admin_usun($_GET['id']);
            break;
 
            default:
                $this->admin_lista();
        }
    }

Na początek sprawdzamy czy użytkownik jest zalogowany, jeśli nie to wyświetlamy formularz logowania, w przeciwnym wypadku zależnie od akcji wykonujemy odpowiednią funkcję, domyślnie jest to lista ankiet.

  function admin_logowanie() {
        if(isset($_POST['haslo'])) {
            if($_POST['haslo'] == $this->haslo) $_SESSION['admin'] = true;
            else echo '<p>Hasło niepoprawne!</p>';
        }
 
        if(!isset($_SESSION['admin']) || $_SESSION['admin'] != true) {
            echo '<form action="" method="POST">
                    <label>Hasło:</label><br>
                    <input type="password" name="haslo"><br>
                    <input type="submit" value="Zaloguj">
                </form>';
        } else {
            echo '<p>Jesteś zalogowany! <a href="admin.php">Przejdź do strony głównej</a>.';
        }
    }

Bardzo prosta funkcja na logowanie.

  function admin_lista() {
        $result = mysql_query("SELECT * FROM ankiety ORDER BY id DESC");
        if(mysql_num_rows($result) > 0) {
            echo '<table>';
            while($row = mysql_fetch_array($result)) {
                echo '<tr>
                        <td'.(($row['aktywna']) ? ' style="font-weight:bold"' : '').'>'.$this->stripslashes_deep($row['tytul']).'</td>
                        <td>
                            <a href="admin.php?akcja=edytuj&id='.$row['id'].'">Edytuj</a> |
                            <a href="admin.php?akcja=usun&id='.$row['id'].'">Usuń</a>
                        </td>
                    </tr>';
            }
            echo '</table>';
        } else {
            echo '<p>Brak ankiet!</p>';
        }
        echo '<p><a href="admin.php?akcja=dodaj">Dodaj nową ankietę</a></p>';
    }

Tradycyjna funkcja na wyświetlanie rekordów z bazy wraz z linkami do edycji i usuwania. Jedyny dodatek jest taki że napis aktywnej ankiety jest pogrubiony. Na dole mamy link do dodawania ankiety.

  function admin_dodaj() {
        if(isset($_POST['tytul'])) { // 1
            $tytul = $this->czysc($_POST['tytul']);
            if($_POST['aktywna']=='1') { // 2
                $aktywna = '1';
                mysql_query("UPDATE ankiety SET aktywna = '0'");
            } else $aktywna = '0';
 
            // 3
            $result = mysql_query("INSERT INTO ankiety (`tytul`, `aktywna`) VALUES ('$tytul', '$aktywna')");
            $id = mysql_insert_id();
            // 4
            foreach($_POST['odpowiedzi'] as $odpowiedz) {
                if(!empty($odpowiedz)) mysql_query("INSERT INTO odpowiedzi (`id_ankiety`, `tytul`, `ilosc`) VALUES ('$id', '$odpowiedz', '0')");
            }
            echo '<p>Dodano poprawnie! <a href="admin.php">Wróć na stronę główną</a></p>';
        } else { // 5
            echo '<form action="" method="POST">
                    <label>Tytuł:</label><br>
                    <input type="text" name="tytul" style="width:300px"><br>';
            for($i=1;$i<=10;$i++) { // 6
                echo '<label>Odpowiedź '.$i.':</label><br>
                    <input type="text" name="odpowiedzi[]" style="width:300px"><br>';
            }
            echo '<input type="checkbox" id="aktywna" name="aktywna" value="1"> <label for="aktywna">Aktywuj?</label><br>
            <input type="submit" value="Wyślij">
                </form>';
        }
    }
  1. Jeśli jest w użyciu $_POST['tytul'] to przystępujemy do dodawania ankiety.
  2. Gdy zaznaczyliśmy w formularzu że ankieta ma być aktywna to wtedy wykonujemy zapytanie zmieniające wszystkie ankiety na nieaktywne. Do zmiennej $aktywna przypisujemy 1 lub 0 zależnie od wyboru.
  3. Wykonujemy zapytanie na dodawanie ankiety i pobieramy jej ID.
  4. Dla wszystkich odpowiedzi w pętli wykonujemy zapytanie na ich dodawanie. Przed tym sprawdzamy czy wpisaliśmy jakąś nazwę funkcją empty().
  5. Jeśli nie wcisnęliśmy przycisku Wyślij to wyświetlamy formularz.
  6. W pętli wyświetlamy 10 odpowiedzi. Jeśli potrzebujemy więcej można zwiększyć tą liczbę.

No i mamy dodawanie ankiety, teraz czas na edytowanie:

  function admin_edytuj($id) {
        $id = (int)$id;
        if(isset($_POST['tytul'])) {
            $tytul = $this->czysc($_POST['tytul']);
 
            if($_POST['aktywna']=='1') {
                $aktywna = '1';
                mysql_query("UPDATE ankiety SET aktywna = '0'");
            } else $aktywna = '0';
 
            $result = mysql_query("UPDATE ankiety SET tytul = '$tytul', aktywna = '$aktywna' WHERE id = '$id'");
 
            foreach($_POST['odpowiedzi'] as $id_odp => $odpowiedz) {
                if(!empty($odpowiedz)) mysql_query("UPDATE odpowiedzi SET tytul = '$odpowiedz' WHERE id = '$id_odp'");
            }
            echo '<p>Zaktualizowano poprawnie! <a href="admin.php">Wróć na stronę główną</a></p>';
        } else {
            $result = mysql_query("SELECT * FROM ankiety WHERE id = '$id'");
            $row = $this->stripslashes_deep( mysql_fetch_array($result) );
            echo '<form action="" method="POST">
                    <label>Tytuł:</label><br>
                    <input type="text" name="tytul" style="width:500px" value="'.$row['tytul'].'"><br>';
 
            $result2 = mysql_query("SELECT * FROM odpowiedzi WHERE id_ankiety = '$id'");
            $i = 1;
            while($row2 = mysql_fetch_array($result2)) {
                echo '<label>Odpowiedź '.$i.':</label><br>
                    <input type="text" name="odpowiedzi['.$row2['id'].']" style="width:300px" value="'.$this->stripslashes_deep($row2['tytul']).'"><br>';
                $i++;
            }
 
            echo '<input type="checkbox" id="aktywna" name="aktywna" value="1"'.(($row['aktywna']) ? ' checked' : '').'> <label for="aktywna">Aktywuj?</label><br>
                    <input type="submit" value="Wyślij">
                </form>';
        }
    }

Praktycznie wszystko tutaj jest takie samo jak przy dodawaniu z tym że nie używamy zapytania INSERT ale UPDATE. Myślę, że nic nie wymaga tutaj komentarza ;) Jedyna uwaga to taka że wyświetlamy jedynie istniejące odpowiedzi. Nie można więc dodawać ani ich usuwać - nie oszukuje się wtedy wyników ankiety. To służy raczej do poprawek błędów.

Teraz jeszcze tylko szybka funkcja na usuwanie ankiety:

  function admin_usun($id) {
        $result = mysql_query("DELETE FROM ankiety WHERE id = '$id'");
        $result2 = mysql_query("DELETE FROM odpowiedzi WHERE id_ankiety = '$id'");
        $result3 = mysql_query("DELETE FROM glosy WHERE id_ankiety = '$id'");
        if($result && $result2 && $result3) {
            echo '<p>Pomyślnie usunięto ankietę! <a href="admin.php">Wróć do strony głównej</a>.</p>';
        }
    }

Funkcja bardzo prosta i nie wymaga dłuższego komentarza bo wykonuje jedynie zapytania do bazy danych :) Usuwa ankietę, odpowiedzi i głosy na podstawie ID ankiety.

No i to by było na tyle. Jeszcze tylko funkcje pomocnicze czysc() i stripslashes_deep()

  function czysc($tresc) {
        $tresc = mysql_real_escape_string($tresc);
        return $tresc;
    }
 
    function stripslashes_deep($value) {
        $value = is_array($value) ?
                array_map(Array($this, 'stripslashes_deep'), $value) :
                stripslashes($value);
        return $value;
    }

Funkcja czysc() to tak jakby alias dla funkcji mysqlrealescape_string(). Istnieje ona ponieważ ma krótszą nazwę oraz zawsze możemy coś jeszcze dodać zmieniając kod tylko w jednym miejscu.

Co do stripslashesdeep() to jeśli $value jest tablicą to używamy na niej funkcji arraymap (dla każdej wartości w tablicy zostanie wywołana funkcja w pierwszym parametrze funkcji). Jeśli $value nie jest tablicą to używamy zwykłego stripslashes. Funkcja została zaczerpnięta z manuala pod funkcją stripslashes.

Klasę mamy już skończoną, teraz trzeba napisać odpowiedni kod który ją wykorzysta.

Na początek plik odpowiadający za wyświetlanie ankiety:

<?php
ob_start();
?>
<html>
<head>
<title>Ankieta</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style type="text/css">
#ankieta {
    width: 300px;
    padding: 5px;
}
#ankieta .pasek {
    height: 8px;
    background-color: #9AC2E8;
}
#ankieta .odpowiedz {
    background-color: #F0F0F0;
    margin: 5px 0;
    width: 100%;
    padding: 5px;
}
#ankieta span.ilosc {
    font-size: 12px;
    margin-left: 8px;
}
#ankieta h3 {
    font-size: 16px;
    margin: 2px 0;
}
</style>
</head>
<body>
<?php
 
include "ankiety.class.php";
 
$ankiety = new Ankiety;
$ankiety->pokaz();
 
?>
</body>
</html>
<?php
ob_end_flush();
?>

Na początek włączamy buforowanie abyśmy mogli wysyłać ciasteczka. Tworzymy standardowy kod HTML z kodowaniem UTF-8 i dodajemy jakieś style CSS. W środku załączamy plik z klasą, tworzymy nowy obiekt i wywołujemy funkcję pokaz().

Teraz plik admin.php:

<?php session_start(); ?>
<html>
<head>
<title>Panel administratora</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
<?php
 
include "ankiety.class.php";
 
echo '<a href="admin.php"><h3>Panel administratora</h3></a>';
$ankiety = new Ankiety;
$ankiety->admin();
 
?>
</body>
</html>

Kod bardzo prosty: na początek startujemy sesję a w środku strony tworzymy nowy obiekt Ankiety i wywołujemy funkcję admin().

No i to by było na tyle ;) Od teraz mamy już system ankiet!

Jeszcze tylko pokażę jak to wygląda w praktyce: