script42: Ajax mit MySQL und Google Charts

Liebes Forum,

ich bin Programmieranfängerin und schlage mich gerade das erste Mal mit Ajax herum.
Ich habe die jQuery-Variante gewählt und möchte bei Auswahl eines Datums in einem Select-Menü dieses über Ajax an meine Datenbankabfrage(MySQL) senden, aus dem Ergebnis der Abfrage ein JSON-Objekt generieren und dieses dann meiner Google-Charts-Funktion füttern, um es in Form einer Pie-Chart angezeigt zu bekommen.
Angezeigt wird mir tatsächlich auch eine Pie-Chart mit Datenbank-Daten, nur leider immer von Anfang 2017, statt mit den gefütterten Daten aus 2019. Ich habe schon cache: false ergänzt, GET und POST versucht und alles rauf und runter überprüft, finde aber den Fehler nicht.
Das ist mein Code (jQuery wird in einem der Header geladen; genauso öffnende und schließende Head-, Body- und HTML-Tags. Für die Testung habe ich auch Prepared Statements erst einmal weggelassen):

<?php
  include_once 'header2.php';
?>
<style>
  html, body {
    height: 100%;
    width: 100%;
    margin: 0;
    padding: 0;
  }
</style>

<title>Aufträge</title>

    <!--AJAX API laden-->
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <!-- jQuery wurde schon in einem der Header geladen -->
    <script type="text/javascript">
    
    // Visualization API und Piechart Package laden
    google.charts.load('current', {'packages':['corechart']});
      
    // Erstellen einer Callback-Funktion, die ablaufen soll, wenn die Google Visualization API geladen ist.
    google.charts.setOnLoadCallback(ajaxChart);
      
      function ajaxChart() {
          // Event Listener an Select-Element
          $("#selectinput").change(drawChart);

          function drawChart() {
            $.ajax({
              url: 'ajax/fotografen.php',
              type: 'POST',
              data:{datum: $("#selectinput").val()},
              // data: 'datum=' + $("#selectinput").val(),
              dataType: 'json',
              cache: false,
              success: function (responseText) {
                // Antwort von php soll für Data Table verwendet werden
                var data = new google.visualization.DataTable(responseText);
                var chart = new google.visualization.PieChart(document.getElementById('chart_div'))
                var options = {
                  title: 'Aufträge pro Fotograf',
                  titleTextStyle: {
                    color: 'orange'
                  },
                  is3D: 'true',
                  width: 800,
                  height: 600
                };
                chart.draw(data, options);
              },
              error: function(jqXHR, textStatus, errorThrown) {
                console.log(errorThrown + ': ' + textStatus);
              }
            });
          }
        }
    </script>
<?php
  include_once 'header3.php';
?>
  <form action="">
    <select name="datum" id="selectinput">
      <option value="" selected disabled>Datum auswählen:</option>
      <option value="2019-01-01">Ab Januar</option>
      <option value="2019-02-01">Ab Februar</option>
      <option value="2019-04-01">Ab April</option>
    </select>
  </form>
    
  <div id="chart_div"></div>
<?php 
  include_once 'footer.php';
?>

Und das ist der Code meiner abgefragten ajax/fotografen.php-Datei:

<?php
  include_once '../inc/datenbankverbindung.php';

  $Datum = $_POST['datum'];  

    $selectChart = "SELECT a.FotografenID AS ID, concat(FotografVorname, ' ', FotografNachname) AS Name, COUNT(*) AS Auftraege FROM tbl_KgAuftraege a JOIN tbl_Fotografen b ON a.FotografenID = b.FotografenID WHERE (Fototag1>=$Datum) AND (Aktivitaet='aktiv') GROUP BY a.FotografenID;";
    $resultChart = mysqli_query($conn, $selectChart);

    $table = array();
    $rows = array();
    $table['cols'] = array(
            array('label' => 'Fotograf', 'type' => 'string'),
            array('label' => 'Aufträge', 'type' => 'number')
    );

   	while ($rowChart = mysqli_fetch_assoc($resultChart)) {
        $temp = array();
        $temp[] = array('v' => (string) $rowChart['Name']);
        $temp[] = array('v' => (int) $rowChart['Auftraege']);
        $rows[] = array('c' => $temp);
    }

	$table['rows'] = $rows;
	$jsonTable = json_encode($table);
  echo $jsonTable;
?>

Für jegliche Idee wäre ich sehr dankbar!

Kerstin

akzeptierte Antworten

  1. Wenns Datum nicht stimmt dann guck doch mal was am Server ankommt. Und wie die Abfrage dann aussieht. Und nicht vergessen: Alles was über HTTP kommt ist der DB entsprechend zu maskieren. Und dann auch prüfen was in der Response kommt.

    MfG

    1. Tach!

      Und nicht vergessen: Alles was über HTTP kommt ist der DB entsprechend zu maskieren.

      Diese Aussage ist nur teilweise richtig. Es kommt nicht darauf an, wo Daten herkommen. Sie müssen in jedem Fall kontextgerecht behandelt werden, wenn sie direkt irgendwo eingefügt werden, beispielsweise wie hier in ein SQL-Statement. Das Ziel muss nicht nur im Falle von über HTTP kommenden Daten syntaktisch einwandfrei und der Intention des Autors entsprechen, sondern generell. Außerdem gibt es mit Prepared Statements eine Alternative zum Maskieren.

      Im Ausgangsposting ist unter anderem zu sehen:

      WHERE (Fototag1>=$Datum)

      Wenn nun in $Datum ein Wert à la 2019-02-18 steht (ohne Anführungszeichen), dann ist das kein Datum sondern eine Rechenaufgabe mit dem Ergebnis 1999, was im Vergleich mit dem in Fototag1 stehenden Datum nicht den gewünschten Sinn ergibt.

      ... WHERE (Fototag1>='" . htmlspecialchars($Datum) . "') ...

      So wäre zumindest dieser Teil syntaktisch richtig (mit einfachen Anführungszeichen um das Datum und Maskierung des Wertes). Wenn nun jemand kein Datum, sondern irgendwas anderes eingibt, kommt zwar kein sinnvolles Ergebnis raus, aber man kann diese Stelle auch nicht mehr für SQL-Injection ausnutzen.

      Zudem ist es nicht weiter sinnvoll, für das Datum eine weitere Variable anzulegen.

      $Datum = $_POST['datum'];
      

      Man kann den Wert im $_POST-Array, was auch nichts anderes als eine Variable ist, auch gleich direkt verwenden

      ... WHERE (Fototag1>='" . htmlspecialchars($_POST['datum']) . "') ...

      dedlfix.

      1. Das war die Lösung!
        Vielen Dank, dedlfix!

        Ich habe dieses Problem nie gehabt, weil ich meine POST-Variablen immer in Prepared Statements gelegt habe und dort wird ja bei Wahl von "s" automatisch ein String erzeugt.
        Wenn ich diese aber schlauerweise zur "Erleichterung" zum Testen weglasse...

        Jetzt funktionieren die Abfragen einwandfrei.

        Herzliche Grüße Kerstin

    2. Ähm, wie kann ich denn überprüfen, was beim Server ankommt? Ich habe es mit <div> <?php $string = file_get_contents("ajax/fotografen.php"); echo $string; ?> </div>
      versucht, aber da kommt dann nur das an:

      =$Datum) AND (Aktivitaet='aktiv') GROUP BY a.FotografenID;"; $resultChart = mysqli_query($conn, $selectChart); $table = array(); $rows = array(); $table['cols'] = array( array('label' => 'Fotograf', 'type' => 'string'), array('label' => 'Aufträge', 'type' => 'number') ); while ($rowChart = mysqli_fetch_assoc($resultChart)) { $temp = array(); $temp[] = array('v' => (string) $rowChart['Name']); $temp[] = array('v' => (int) $rowChart['Auftraege']); $rows[] = array('c' => $temp); } $table['rows'] = $rows; $jsonTable = json_encode($table); // echo $jsonTable; ?>

      Für mich ein ganz willkürlicher Ausschnitt aus der Datei.
      Wie kann ich das sinnvoller abfragen?

      Viele Grüße Kerstin

      P.S. Jetzt habe ich gerade geschaut, was im Quelltext steht und da steht der ganze Text:

      <?php
        include_once '../inc/datenbankverbindung.php';
      
        $Datum = $_POST['datum'];  
        echo $Datum;
      
          $selectChart = "SELECT a.FotografenID AS ID, concat(FotografVorname, ' ', FotografNachname) AS Name, COUNT(*) AS Auftraege FROM tbl_KgAuftraege a JOIN tbl_Fotografen b ON a.FotografenID = b.FotografenID WHERE (Fototag1>=$Datum) AND (Aktivitaet='aktiv') GROUP BY a.FotografenID;";
          $resultChart = mysqli_query($conn, $selectChart);
      
          $table = array();
          $rows = array();
          $table['cols'] = array(
                  array('label' => 'Fotograf', 'type' => 'string'),
                  array('label' => 'Aufträge', 'type' => 'number')
          );
      
         	while ($rowChart = mysqli_fetch_assoc($resultChart)) {
              $temp = array();
              $temp[] = array('v' => (string) $rowChart['Name']);
              $temp[] = array('v' => (int) $rowChart['Auftraege']);
              $rows[] = array('c' => $temp);
          }
      
      	$table['rows'] = $rows;
      	$jsonTable = json_encode($table);
        //echo $jsonTable;
      ?>   
      

      Offensichtlich wird das Größer-Zeichen in der Abfrage als schließendes html-Zeichen interpretiert.
      Doch vor allem möchte ich ja den Inhalt der Variable $Datum sehen. Wie kann ich das machen, wenn nicht mit Echo?

      1. Die einfachste Möglichkeit, sich den POST anzuschauen ist ein Dump (print_r) des gesamten Array's. Und idealerweise sendet man vor dem print_r einen Content-Type text/plain.

        Und dann wären noch die Entwicklerwerkzeuge des Browsers wo man sich anschauen kann was rausgeht und wieder reinkommt.

        MfG

        1. Tach!

          Die einfachste Möglichkeit, sich den POST anzuschauen ist ein Dump (print_r) des gesamten Array's. Und idealerweise sendet man vor dem print_r einen Content-Type text/plain.

          Ein <pre> davor auszugeben, ist ausreichend und einfacher, als HTTP-Headers zu manipulieren.

          dedlfix.

          1. Du hast den Sinn des HTTP Headers Content-Type nicht verstanden!

            1. Tach!

              Du hast den Sinn des HTTP Headers Content-Type nicht verstanden!

              Das ist deine Einschätzung. Sie deckt sich nicht mit meiner. Tatsächlich sehe ich jedoch keinen Sinn darin, für eine temporäre Debug-Ausgabe irgendeinen größeren Aufwand zu betreiben. Eine Formatierung, so dass Zeilenumbrüche erhalten bleiben, ist für die meisten Fälle ausreichend. Komplett auf text/plain umzuschalten, beeinflusst den Rest der Ausgabe über Gebühr.

              dedlfix.

              1. Tach!

                Du hast den Sinn des HTTP Headers Content-Type nicht verstanden!

                Das ist deine Einschätzung. Sie deckt sich nicht mit meiner. Tatsächlich sehe ich jedoch keinen Sinn darin, für eine temporäre Debug-Ausgabe irgendeinen größeren Aufwand zu betreiben. Eine Formatierung, so dass Zeilenumbrüche erhalten bleiben, ist für die meisten Fälle ausreichend. Komplett auf text/plain umzuschalten, beeinflusst den Rest der Ausgabe über Gebühr.

                Eigentlich ist beides nicht ganz passend. Es handelt sich ja um eine Antwort auf eien Ajax-Request. Da braucht es natürlich kein <pre> - das ist nur für Ausgaben in den HTML-Kontext sinnvoll. Aber auch das Umschalten nach text/plain bringt keine Punkte. Einfach nur ausgeben und fertig. Der Browser kann mit der Antwort nichts anfangen, aber das ist in dem Fall egal. Man muss nur in die Entwicklertools der Browser in die Response schauen, und die wird immer als Text zu sehen sein. Zusätzlich versuchen die Browser noch das Format zu erkenen und eine schöne Formatierung anzubieten, aber das ist für die Debug-Ausgabe nicht weiter relevant.

                dedlfix.

        2. Welches Array meinst Du?

          Die Entwicklerwerkzeuge hatte ich als Erstes bemüht, doch haben sie mir nicht weitergeholfen.
          Ich habe unter Netzwerk Headers nachgesehen, doch wird mir da der Inhalt der übermittelten Variable nicht angezeigt, so wie ich es sonst bei GET oder POST-Übermittlung ohne Ajax gewohnt bin.
          Wo sollte ich stattdessen nachsehen?

          1. Welches Array meinst Du?

            $_POST

            Die Entwicklerwerkzeuge hatte ich als Erstes bemüht, doch haben sie mir nicht weitergeholfen.
            Ich habe unter Netzwerk Headers nachgesehen, doch wird mir da der Inhalt der übermittelten Variable nicht angezeigt, so wie ich es sonst bei GET oder POST-Übermittlung ohne Ajax gewohnt bin.

            Bei einem POST wird ein HTTP Message Body gesendet.

            Wo sollte ich stattdessen nachsehen?

            Entwicklerkonsole, Register Netzwerk/Parameter. Und in /Antwort steht die Response.

            MfG

            1. Das scheint in jeder Browserversion anders zu sein.
              In der mir vorliegenden Chrome-Version habe ich jetzt gesehen, dass ich erst XHR anwählen muss, sonst werden mir nur GET-responses angezeigt.
              Im Firefox habe ich auch gerade nachgesehen, da ist keine Vorauswahl nötig.

              Danke
              Kerstin

              1. Das scheint in jeder Browserversion anders zu sein.

                Genau. Man muss sich halt ganz einfach mal damit befassen. MFG