PHP
 

 Abstimmung im Internet

Dieses kleine Programm ist sicherlich nur eins von vielen für eine weit verbreitete einfache Aufgabe bei der Gestaltung von interaktiven Internetseiten: Die Besucher einer Seite sollen aus mehreren Antwortmöglichkeiten eine Wahl treffen; diese soll - anonym und nur zahlenmäßig versteht sich - auf dem Webserver gespeichert und eine Statistik über die gegebenen Antworten dargestellt werden können.
Ich habe diese Aufgabe für die zwei Rätsel Vierkartenproblem und Was ist gerecht? meiner Rätselseite mit nachstehend erläutertem php-Skript gelöst. Ich stelle es hier dar, weil es wie ich finde die eine oder andere nette Detaillösung enthält, die auch andere interessieren könnte.

Ich stelle zuerst die einfachere Variante vor, die ich für das Rätsel Was ist gerecht? verwende.

Formulargrundlage

Das Formular, welches die Datei mit nachfolgendem php-Skript aufruft, enthält zwei für die Auswertung relevante Eingabefelder: Das eine heißt r29ich (1) und ist eine Auswahlliste mit verschiedenen Zahlenwerten (2). Das andere ist ein Kontrollkästchen mit Namen r29koop (3). (Der Anfang der Namensgebung r29 sagt aus, dass dieses Formularfeld zum Rätsel Nummer 29 auf der Rätselseite gehört.)
Wenn im Formular das Kontrollkästchen angehakt wird, sorgt ein Javaskript (4) dafür, dass das Auswahlfeld deaktiviert wird.
Deshalb überträgt das Formular entweder den Inhalt der ausgewählten Option des Auswahlfeldes (das was zwischen den <option>-Tags des Select-Befehls steht) oder ein on, wenn das Kontrollkästchen aktiviert ist. Nicht angehakte Kontrollkästchen und deaktivierte Auswahlfelder erzeugen keine $POST["Feldname"]-Variable in php.

<form action="raetsel/r29loesung.php" method="POST" target="r29" name="r29form">
(1) <select name="r29ich" onchange="javascript: (9) kollege_berechnen();">
    <option>ich möchte ...</option>
    <option>0</option> (2)
    <option>50</option>
    <option>75</option>
    <option>100</option>
    <option>105</option>
    <option>125</option>
    <option>145</option>
    <option>150</option>
    </select>  €
(3) <input type="checkbox" name="r29koop" onclick="javascript:(4) if (this.checked==true) {
        r29form.r29ich.selectedIndex=4;
        r29form.r29ich.disabled=true;
        r29form.r29kollege.value='0';
        alert('Das ist aber nicht nett!');
        }
    else r29form.r29ich.disabled=false;">Ich möchte nicht kooperieren!</input><br>
    <input type="text" value="" size="2" name="r29kollege" readonly></input> (8)
        € soll also mein Kollege bekommen.<br>
    <input type="Submit" name="r29submit" value=" Antwort ">
</form>

Reload-Sperre auf Cookie-Basis

Ganz zu Beginn des Skripts (das ist wichtig, weil diese Befehle vor dem Senden der Headerinformationen an den Browser vom php-Interpreter abgearbeitet worden sein müssen!) wird überpüft ob dieses Skript auf dem aufrufenden Rechner schon einmal ein Cookie hinterlassen hat oder nicht (5). Auf diese Weise wird eine Reload-Sperre realisiert, d.h. von jedem Computer aus kann nur eine Stimme abgegeben werden. (Natürlich ist diese Sperre nicht zuverlässig, weil der Nutzer ja nur das Cookie löschen muss, um sie zu umgehen, aber für den hier beabsichtigten Zweck völlig hinreichend.)
Wenn kein Cookie gefunden wurde, wird die übertragene Auswahl des Nutzers (5a) als Cookie auf seinem Rechner abgelegt (6). Falls bereits ein Cookie gespeichert ist, wird die früher gegebene Antwort des Nutzers ausgelesen (7). Das leistet der folgende Codeabschnitt:

$antwort = $_POST["r29ich"].$_POST["r29koop"]; (5a)
if (!$_COOKIE[gerecht]) { (5)
  $cookiegd = false;
  setcookie("gerecht",$antwort, time()+2678400); (6)
}
else
  $cookiegd = true;
$cookie_inh = $_COOKIE[gerecht]; (7)

Plausibilitätskontrolle

Die erste Auswahl des Auswahlfeldes enthält keinen Zahlenwert sondern nur einen Hinweis für den Nutzer, dass er eine Auswahl treffen soll. Wenn der Nutzer das Formular abgeschickt hat, ohne selbst eine Auswahl zu treffen, ist also diese erste Option aktiv. In diesem Fall soll ihm keine Statistik angezeigt werden sodnern der Hinweis, dass er bitte zuerst seine Wahl treffen soll. Diese Funktion ist hier realisiert, indem ein drittes Formularfeld r29kollege (8), das nur befüllt ist, wenn der Nutzer eine gültige Auswahl getroffen hat ( Javascript (9)), auf Inhalt geprüft wird:

if ($_POST["r29kollege"]=="")
echo "<p>Bitte treffen Sie eine Auswahl, welche Geldverteilung Sie Ihrem Kollegen vorschlagen wollen!<br>Nur dann wird Ihnen hier die Lösung angezeigt.</p>";

Auswertung

Der dann folgende else-Zweig enthält die Auswertung der Daten und ihre Darstellung: Mehrere Echo-Befehle geben Erläuterungen zur Lösung des Rätsels (10). Dann wird die Datei r29data.csv mit der Option r+ zum Lesen und Schreiben geöffnet (11); sie enthält die Anzahl der früher gespeicherten Antworten durch Komma voneinander getrennt: Der erste Wert gibt an, wie viele Besucher das Kontrollkästchen r29koop angeklickt hatten; der zweite Wert sagt aus, wie viele die Option 0€, der dritte wie viele 50€ gewählt hatten usw. Der zehnte und letzte Wert enthält die Summe aller vorangegangenen Zahlen. Eine solche csv-Datei lässt sich sehr elegant mit dem php-Befehl fgetcsv() in ein Array einlesen (12).

else {
echo "<p>Die grundsätzliche Frage ...</p>"; (10)

$datafile = fopen("r29data.csv","r+"); (11)
$statistik = fgetcsv($datafile,200); (12)
rewind($datafile);

Die weitere Verarbeitung ist wieder zweigeteilt, abhängig davon, ob zu Beginn (5), (13) ein Cookie gefunden wurde oder nicht, denn falls dies der erste Besuch ist, soll die Antwort des Besuchers gespeichert werden, sonst nicht:

if (!$cookiegd) { (13)
  if (isset($_POST["r29koop"])) $statistik[0]++; (14)
  else {
    $betraege = array(0,50,75,100,105,125,145,150); (15)
    $index = array_search($_POST["r29ich"],$betraege)+1; (16)
    $statistik[$index]++; (17)
  } //end else
  $statistik[9]++; //Anzahl der Antworten (18)
  fputs($datafile,implode(",",$statistik)); (19)
  echo "<p><font size='-1'>Ihre Antwort ist in der Auswertung bereits enthalten.</font></p>";
} //end if Cookie nicht gesetzt
else {
  echo "<p><font size='-1'>Ihre Antwort wurde nicht mehr gespeichert, weil Sie bereits einmal geantwortet haben, dass Sie ";
  if ($cookie_inh=="on") echo "nicht kooperieren wollen und die 100€ nehmen";
  else echo $cookie_inh."€ erhalten möchten";
  echo ".</font></p>";
} // end else Cookie gesetzt
fclose($datafile);

Wenn der Besucher das Kontrollkästchen r29koop angeklickt hat, wird der erste Zahlenwert der csv-Reihe - zunächst nur im eingelesenen Array $statistik[] - erhöht (14). Für die anderen Fälle bedarf es einer Umsetzung des übergebenen Variableninhalts $_POST["r29ich"] in eine Indexnummer: Wenn der Benutzer z.B. 125 ausgewählt hat, enthält $_POST["r29ich"] diese Zahl und es muss ermittelt werden, die wie vielte Zahl in der csv-Reihe bzw. welches Arrayelement von $statistik[] dann erhöht werden muss. Dazu habe ich das Array $betraege angelegt, das alle möglichen Werte, die $_POST["r29ich"] enthalten kann, in der richtigen Reihenfolge enthält (15). Dann wird die Position des übergebenen Wertes in diesem Array gesucht (16) und das richtige Element von $statistik[] inkrementiert (17).
(Eine andere Möglichkeit, dieses Problem zu lösen, bestünde darin, $statistik[] als assoziatives Array anzulegen, das die möglichen Variableninhalte von $_POST["r29ich"] als Schlüssel benutzt.)
Anschließend braucht nur noch die Gesamtzahl Antworten erhöht (18) und das $statistik-Array wieder in die csv-Datei geschrieben werden (19).

Nun stehen für alle Antworten die aktuellen Abstimmungszahlen im Array $statistik[] bereit und müssen nur noch dargestellt werden.
Um die verschiedenen Möglichkeiten, über die abgestimmt wurde, im einfachen Zugriff zu haben und eine entsprechende Tabelle mit einer einzigen Schleife abarbeiten zu können, habe ich die möglichen Kombinationen der Abstimmungsoptionen in einem Array $vorschlaege[][] abgelegt (20).
Nach der Erstellung der Tabelle mit ihrer Kopfzeile (21) wird in einer Schleife (22) für jede Abstimmungsoption eine Tabellenzeile erstellt (23); diese enthält die Zahlenwerte des Vorschlags (24), die absolute Anzahl Besucher, die diese Antwort gegeben haben (25), den Prozentwert, dem das entspricht (26) sowie eine grafische Darstellung dieses Anteils.
Diese grafische Darstellung erfolgt auf einfache Weise dadurch, dass eine Grafik prozent.gif, die einen Pixel breit ist, mit Hilfe der width-Option des html-Tags <img> auf eine dem Prozentwert entsprechende Breite gedehnt wird.

$vorschlag=array(array(100,0),array(0,150),array(50,100),array(75,75),array(100,50),array(105,45),array(125,25),array(145,5),array(150,0)); (20)
echo "<table cellpadding='2' cellspacing='5' border='1'>";
echo "<tr><td align='center'>Koop.</td><td align='center'>ich</td><td align='center'>Kollege</td><td align='center'>Anzahl<br>absolut</td><td align='center'>Anzahl<br>relativ</td><td></td></tr>"; (21)
for ($i=0; $i<9; $i++) { (22)
  echo "<tr><td align='center'>";
  if ($i==0) echo "nein";
  else echo "ja";
  echo "</td><td align='center'>"; (23)
  echo $vorschlag[$i][0];
  echo "€</td><td align='center'>";
  echo $vorschlag[$i][1]; (24)
  echo "€</td><td align='center'>";
  echo $statistik[$i]; (25)
  echo "</td><td align='right'>";
  echo round($statistik[$i]*100/$statistik[9],1); (26)
  echo "%</td>";
  echo "<td class='rahmen'><img src='../bilder/prozent.gif' width='";
  echo round($statistik[$i]*200/$statistik[9],0);
  echo "' height='20' border='0'></td></tr>"; (27)
} //end for
echo "<tr><td colspan='3'>Anzahl Antworten</td><td align='center'>";
echo $statistik[9];
echo "</td><td align='right'>100%</td><td class='rahmen'><img src='../bilder/prozent.gif' width='200' height='20' border='0'></td></tr></table>";
} // end else (28)

Die letzte geschweifte Klammer (28) beendet den else-Zweig der Plausibilitätskontrolle (10)


Das Vierkartenproblem verwendet ein ganz ähnliches php-Skript zur Verarbeitung der Nutzereingaben. Der wesentliche Unterschied liegt darin, wie der Besucher aus den Auswahlmöglichkeiten aussuchen kann und wie seine Auswahl übertragen wird:

Die Rätselseite zeigt vier Karten, die der Benutzer anklicken kann, um damit auszudrücken, dass er sie umdrehen möchte. Ein Javaskript sorgt dafür, dass ein verborgenes Formularfeld r28auswahl eine Zahl enthält, aus der eindeutig abgeleitet werden kann, welche der Karten umgedreht waren und welche nicht, denn diese Zahl ist die dezimale Repräsentation der Binärzahl, die entsteht, wenn man eine gewendete Karte als "1" und eine ungewendete als "0" interpretiert. Es wird also z.B. eine 9 übertragen, wenn die erste und die letzte Karte umgedreht waren oder eine 6, wenn die beiden mittleren umgedreht waren.

Darstellung der vier Karten:

<table cellspacing="15" cellpadding="15">
<tr>
<td style="background-color:#cccccc; border-width:1px; border-color:#000000; border-style:solid; font-size:36pt; font-weight:bold;" onclick="javascript:umdrehen(this.id);" id="3">E</td>
<td style="background-color:#cccccc; border-width:1px; border-color:#000000; border-style:solid; font-size:36pt; font-weight:bold;" onclick="javascript:umdrehen(this.id);" id="2">T</td>
<td style="background-color:#cccccc; border-width:1px; border-color:#000000; border-style:solid; font-size:36pt; font-weight:bold;" onclick="javascript:umdrehen(this.id);" id="1">4</td>
<td style="background-color:#cccccc; border-width:1px; border-color:#000000; border-style:solid; font-size:36pt; font-weight:bold;" onclick="javascript:umdrehen(this.id);" id="0">7</td>
</tr>
</table>

Das Javascript umdrehen(), das die Umrechnung der ausgewählten Karten in eine Dezimalzahl leistet:

var auswahl = new Array(false,false,false,false); //ausgewählte Karten
var farben = new Array("#CCCCCC","#EE0000"); //Hintergrundfarben der Karten

function umdrehen(karte) {
  auswahl[karte] = !auswahl[karte];
  if (auswahl[karte]) {
    farbe=1;
    r28form.r28auswahl.value = parseInt(r28form.r28auswahl.value) + Math.pow(2,karte);
  }
  else {
    farbe=0;
    r28form.r28auswahl.value = parseInt(r28form.r28auswahl.value) - Math.pow(2,karte);
  }
  document.getElementById(karte).style.backgroundColor=farben[farbe];
return;
}

Das Formular mit dem verborgenen Eingabefeld, das durch oben stehendes Javascript befüllt wird.

<form action="raetsel/r28loesung_csv.php" method="POST" target="r28" name="r28form">
<input type="hidden" name="r28auswahl" value="0">
<input type="Submit" name="r28submit" value=" Lösung ">
</form>

Das php-Skript in r28loesung_csv.php, welches Auswertung und Darstellung der Statistik übernimmt (ohne den analog zu oben geschriebenen Teil zur Überprüfung des Cookies):

<?php
  if ($antwort == 9) echo "richtig";
  else echo "falsch";
  echo "!</p>";
  $datafile = fopen("r28data.csv","r+");
  $statistik = fgetcsv($datafile,200);
  rewind($datafile);
  if (!$cookievkp) {
    $statistik[$antwort]++;(a)
    $statistik[16]++; 
    fputs($datafile,implode(",",$statistik));
  } //end if Cookie nicht gesetzt
  else {
    echo "<p><font size='-1'>Ihre Antwort wurde nicht mehr gespeichert, weil Sie bereits einmal geantwortet haben.</font></p>";
  } // end else Cookie gesetzt
  fclose($datafile);
?>

An der mit (a) bezeichneten Stelle lässt sich erkennen, wie leicht sich durch die Zusammenfassung der Antwort auf eine dezimale Zahl die Statistik in diesem Fall speichern lässt. Eine Umrechnung der Antworten auf einen Array-Index wie oben  (16) ist hier nicht nötig.

Das interessante an der unten stehenden Darstellungsroutine dazu ist, dass die vier Karten genau wie auf der Rätselseite (nur kleiner) dargestellt werden. Das Erzeugen dieser 16-zeiligen Tabelle wird dadurch erleichtert, dass die Schleifenvariable $i bei jeder Karte einem bitweisen Vergleich mit den Zweierpotenzen (b) unterzogen wird, um festzustellen, ob die Karte umgedreht dargestellt werden muss oder nicht.

<p>Nachfolgend sehen Sie, wie sich die Antworten anteilig auf die 16 Möglichkeiten verteilen:</p>
<?php
echo "<table cellpadding='2' cellspacing='5'>";
echo "<tr><td colspan='4' align='center'>gesamt</td><td>";
echo $statistik[16];
echo "</td><td align='right'>100%</td><td class='rahmen'><img src='../bilder/prozent.gif' width='200' height='20' border='0'></td></tr>";
for ($i=0; $i<16; $i++) {
  echo "<tr";
  if ($i==9) echo " class='richtig'";
  echo ">";
  echo "<td class='";
  if ($i & 8) echo "an"; (b)
  else echo "aus";
  echo "'>E</td>";
  echo "<td class='";
  if ($i & 4) echo "an"; (b)
  else echo "aus";
  echo "'>T</td>";
  echo "<td class='";
  if ($i & 2) echo "an"; (b)
  else echo "aus";
  echo "'>4</td>";
  echo "<td class='";
  if ($i & 1) echo "an"; (b)
  else echo "aus";
  echo "'>7</td>";
  echo "<td align='right'>";
  echo $statistik[$i];
  echo "</td><td align='right'>";
  echo round($statistik[$i]*100/$statistik[16],1);
  echo "%</td>";
  echo "<td class='";
  if ($i==9) echo "rahmen_richtig";
  else echo "rahmen";
  echo "'><img src='../bilder/prozent.gif' width='";
  echo round($statistik[$i]*200/$statistik[16],0);
  echo "' height='20' border='0'></td>";
  echo "</tr>";
} //end for alle Elemente von $statistik
?>
</table>




Home
Falls diese Seite ohne Navigationsleiste angezeigt wird, aktivieren Sie Javascript!