diff --git a/css/custom.css b/css/custom.css
new file mode 100644
index 0000000..146cdb9
--- /dev/null
+++ b/css/custom.css
@@ -0,0 +1,16 @@
+/* default branding values */
+h1, h2, h3, h4, h5, h6, ol, ul, li, dl, dt, dd, p, button, input, select, legend, fieldset, label, navbar {
+ color: #333333;
+ font-family: 'Trebuchet MS';
+ text-align: left;
+}
+
+/* adaptions of w3 css */
+.w3-button{
+ background-color: #E89212!important;
+ color: white;
+}
+.w3-button:hover {
+ color: #000!important;
+ background-color: #C95802!important;
+}
diff --git a/css/style.css b/css/style.css
new file mode 100644
index 0000000..ffe88a1
--- /dev/null
+++ b/css/style.css
@@ -0,0 +1,59 @@
+/* Form: Label */
+label {
+ display: block;
+ font-weight: bold;
+ margin: 0 0 0 10px; /* direction: N O S W */
+}
+/* fieldset, surrounds form */
+fieldset {
+ border: 1px solid #C95802;
+ background-color: white;
+ margin-top: 10px;
+}
+/* legend, title of a fieldset */
+legend {
+ font-size: 110%;
+ font-weight: bold;
+ color: #FFFFFF; /* font color */
+ text-transform: uppercase;
+ background-color: #C95802;
+ padding: 5px 10px 5px 10px;
+ margin-top: 10px;
+}
+
+/* aside image */
+.aside-image {
+ max-width: 100%;
+ height: auto;
+}
+
+/* navbar */
+.navbar {
+ background-color: #E89212!important;
+ color: white;
+ font-size: 125%;
+}
+
+/* flexbox styling */
+#parent {
+ display: flex;
+ flex-flow: row wrap;
+ align-items: center;
+ justify-content: center;
+}
+/* text description element */
+#parent div:nth-child(1) {
+ flex-basis: 100%;
+}
+/* symbol elements */
+#parent div {
+ flex-basis: 50%;
+}
+
+/* spacing */
+.between-spacing {
+ margin-bottom: 1cm;
+}
+.bottom-spacing {
+ margin-bottom: 3cm;
+ }
diff --git a/css/style_confirmation.css b/css/style_confirmation.css
new file mode 100644
index 0000000..11596e7
--- /dev/null
+++ b/css/style_confirmation.css
@@ -0,0 +1,49 @@
+/* define vertical gridbox container */
+.confirmation-container {
+ height: 300px;
+ display: grid;
+ grid-template-rows: 1fr 1fr 1fr 1fr 1fr;
+ grid-row-gap: 20px;
+ font-family: 'Trebuchet MS';
+}
+/* top container for confirmation headline */
+confirmation-header {
+ grid-column: 1;
+ grid-row: 1;
+ align-items: start;
+}
+/* containers for details below headline */
+confirmation-details-1 {
+ grid-column: 1;
+ grid-row: 2/3;
+ justify-items: start;
+}
+confirmation-details-2 {
+ grid-column: 1;
+ grid-row: 3/4;
+ justify-items: start;
+}
+confirmation-details-3 {
+ grid-column: 1;
+ grid-row: 4/5;
+ justify-items: start;
+}
+/* bottom container for action buttons */
+confirmation-footer {
+ grid-column: 1;
+ grid-row: 5;
+ justify-items: end;
+}
+
+/* classes for successful / failed renting */
+.success {
+ color: green;
+}
+.failure {
+ color: red;
+}
+
+/* confirmation symbol */
+.confirmation-symbol {
+ margin: 10px;
+}
\ No newline at end of file
diff --git a/js/canvas.js b/js/canvas.js
new file mode 100644
index 0000000..36670a5
--- /dev/null
+++ b/js/canvas.js
@@ -0,0 +1,141 @@
+/************ GLOBAL VARS ***************/
+let incrementStepsAnimation = 0.15;
+let timeBetweenRedrawAnimationMS = 10;
+
+/****************************************/
+/************ CROSS SYMBOL **************/
+/****************************************/
+let crossProperties;
+
+/* function that draws a Cross Symbol */
+function drawCross(canvasElementId, size, canvasSize) {
+ // get canvas element from HTML
+ let canvas = document.getElementById(canvasElementId);
+ let c = canvas.getContext('2d');
+ // calculate some relative positions according to size:
+ // relativeSizeExclusions = 0...1 relative size of exclusions from the circle
+ // lineWidth = width of the stroke
+ let relativeSizeExclusions = 0.7; let lineWidth = 0.05*size;
+ // d = distance from canvas border
+ let d = lineWidth;
+ // clear canvas
+ c.clearRect(0,0,canvasSize,canvasSize);
+
+ // draw circle
+ c.lineWidth = lineWidth;
+ c.fillStyle = "red";
+ c.beginPath();
+ c.arc(canvasSize/2, canvasSize/2, size/2-d, 0, 2 * Math.PI, false);
+ c.stroke();
+ c.fill();
+
+ // draw exclusions
+ c.beginPath();
+ c.arc(canvasSize/2, canvasSize/2, relativeSizeExclusions*(size/2-d), 1.85 * Math.PI, 0.65 * Math.PI, false);
+ c.closePath();
+ c.fillStyle = "white";
+ c.lineWidth = lineWidth;
+ c.stroke();
+ c.fill();
+ c.beginPath();
+ c.arc(canvasSize/2, canvasSize/2, relativeSizeExclusions*(size/2-d), 1.65 * Math.PI, 0.85 * Math.PI, true);
+ c.closePath();
+ c.fillStyle = "white";
+ c.lineWidth = lineWidth;
+ c.stroke();
+ c.fill();
+}
+
+/* recurring function that draws the animation of the Cross Symbol */
+function drawAnimatedCross() {
+ setTimeout(drawAnimatedCross, timeBetweenRedrawAnimationMS);
+ calculateNewSize(crossProperties);
+ drawCross(crossProperties.canvasElementId, crossProperties.currentSize, crossProperties.maxSize);
+}
+
+/* initiates an animation of the Cross Symbol */
+function animateCross(canvasElementId, size, maxSize){
+ crossProperties = {
+ canvasElementId: canvasElementId,
+ minSize: size,
+ maxSize: maxSize,
+ currentSize: size,
+ growing: true
+ }
+ drawAnimatedCross();
+}
+
+/****************************************/
+/********** CHECKMARK SYMBOL ***********/
+/****************************************/
+let checkmarkProperties;
+
+/* function that draws a Checkmark Symbol */
+function drawCheckmark(canvasElementId, size, canvasSize) {
+ // get canvas element from HTML
+ let canvas = document.getElementById(canvasElementId);
+ let c = canvas.getContext('2d');
+ // calculate some relative positions according to size:
+ // xPosKink = x position of kink of the checkmark
+ // yHeightHook = y position of highest point of the hook
+ // lineWidth = width of the stroke
+ let xPosKink = 0.25*size; let yHeightHook = 0.5*size; let lineWidth = 0.05*size;
+ // d = distance from canvas border
+ // t = thickness of checkmark
+ let d = (canvasSize-size)/2+lineWidth; let t = 0.2*size;
+ // clear canvas
+ c.clearRect(0,0,canvasSize,canvasSize);
+
+ // draw checkmark
+ c.lineWidth = lineWidth;
+ c.fillStyle = "green";
+ c.beginPath();
+ c.moveTo(xPosKink + d, canvasSize-d);
+ c.lineTo(canvasSize-d, d);
+ c.lineTo(canvasSize-d-t, d);
+ c.lineTo(xPosKink+d, canvasSize-d-t-lineWidth/2);
+ c.lineTo(d, yHeightHook+d-lineWidth/2);
+ c.lineTo(d, yHeightHook+d+t);
+ c.lineTo(xPosKink + d, canvasSize - d);
+ c.closePath();
+ c.stroke();
+ c.fill();
+}
+
+/* recurring function that draws the animation of the Checkmark Symbol */
+function drawAnimatedCheckmark() {
+ setTimeout(drawAnimatedCheckmark, timeBetweenRedrawAnimationMS);
+ calculateNewSize(checkmarkProperties);
+ drawCheckmark(checkmarkProperties.canvasElementId, checkmarkProperties.currentSize, checkmarkProperties.maxSize);
+}
+
+/* initiates an animation of the Checkmark Symbol */
+function animateCheckmark(canvasElementId, size, maxSize){
+ checkmarkProperties = {
+ canvasElementId: canvasElementId,
+ minSize: size,
+ maxSize: maxSize,
+ currentSize: maxSize,
+ growing: true
+ }
+ drawAnimatedCheckmark(); // start timed function
+}
+
+/****************************************/
+/*************** HELPERS ***************/
+/****************************************/
+
+/* Function to calculate the new size of a canvas symbol. Requires object created in 'animateX' function. */
+function calculateNewSize(symbol){
+ if(symbol.growing && symbol.currentSize < symbol.maxSize){
+ symbol.currentSize += incrementStepsAnimation;
+ }else if(symbol.growing && symbol.currentSize >= symbol.maxSize){
+ symbol.growing = false;
+ symbol.currentSize -= incrementStepsAnimation;
+ }else if(symbol.growing == false && symbol.currentSize > symbol.minSize){
+ symbol.currentSize -= incrementStepsAnimation;
+ }else if(symbol.growing == false && symbol.currentSize <= symbol.minSize){
+ symbol.growing = true;
+ symbol.currentSize += incrementStepsAnimation;
+ }
+}
\ No newline at end of file
diff --git a/js/validation.js b/js/validation.js
new file mode 100644
index 0000000..3c7a641
--- /dev/null
+++ b/js/validation.js
@@ -0,0 +1,63 @@
+// Validation of form entry
+function isValid() {
+ console.log("Validation started....");
+ /* VALIDATE RENTAL ARTICLE NUMBER */
+ let rental_art_nr = document.getElementById("mietart_nr").value;
+ if(rental_art_nr == ''){ // check if rental art nr is present.
+ setErrorPanel("Bitte eine Mietartikel Nummer angeben."); return false;
+ }
+ console.log("Testing if " + rental_art_nr[0] + " is a letter. IsLetter=" + rental_art_nr[0].match(/[a-z]/i));
+ if(rental_art_nr[0].match(/[a-z]/i) == null){ // check if first character is a letter. all article numbers start with a letter.
+ setErrorPanel("Bitte eine gültige Mietartikel Nummer angeben."); return false;
+ }
+
+ /* VALIDATE START DATE */
+ let start_date_string = document.getElementById("von").value;
+ let start_date = new Date(start_date_string);
+ if(start_date_string == '' || start_date == null){ // check if start date is present and valid.
+ setErrorPanel("Bitte ein valides Startdatum angeben."); return false;
+ }
+
+ /* VALIDATE END DATE */
+ let end_date_string = document.getElementById("bis").value;
+ let end_date = new Date(end_date_string);
+ if(end_date_string == '' || end_date == null){ // check if end date is present and valid.
+ setErrorPanel("Bitte ein valides Enddatum angeben."); return false;
+ }
+
+ /* VALIDATE START DATE IS BEFORE END DATE */
+ console.log("Testing if start date " + start_date.toISOString() + " is before end date " + end_date.toISOString() + " Is=" + (end_date >= start_date));
+ if(end_date < start_date){ // check if start date is before end date.
+ setErrorPanel("Startdatum muss vor Enddatum liegen."); return false;
+ }
+
+ /* VALIDATE START DATE IS AFTER TODAY */
+ let now = new Date(); // need to remove time from the "now" date object, therefore create new one with only date:
+ let now_dateOnly = new Date(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
+ console.log("Testing if start date " + start_date.toISOString() + " is after now " + now_dateOnly.toISOString() + ". IsTodayOrLater=" + (start_date >= now_dateOnly));
+ if(start_date < now_dateOnly){ // check if start date in the past. today is ok.
+ setErrorPanel("Startdatum liegt in der Vergangenheit."); return false;
+ }
+
+ /* VALIDATE CUSTOMER NUMBER */
+ let customer_nr = document.getElementById("kunden_nr").value;
+ if(customer_nr == ''){ // check if customer number empty
+ setErrorPanel("Bitte eine Kundennummer angeben."); return false;
+ }
+ console.log("Testing if customer number " + customer_nr + " is a number. IsInteger=" + (isNaN(customer_nr) == false));
+ if(isNaN(customer_nr) == true){ // check if customer number is a number
+ setErrorPanel("Kundennummer ist ungültig."); return false;
+ }
+
+ console.log("Validation sucessful");
+ return true; // valid
+}
+
+// Unhides error panel and shows text
+function setErrorPanel(errorText) {
+ let error_panel = document.getElementById("error-panel");
+ let error_text = document.getElementById("error-text");
+
+ error_text.innerText = errorText;
+ error_panel.hidden = false;
+}
\ No newline at end of file
diff --git a/rental.html b/rental.html
new file mode 100644
index 0000000..bff5867
--- /dev/null
+++ b/rental.html
@@ -0,0 +1,106 @@
+
+
+
+ Mietanfrage
+
+
+
+
+
+
+
+
+
+
+
+
Mietanfrage Jonas Arnold Eventtechnik
+
+
+
+
+
+
+
Information
+
+
+
Beschreibung
+
+ Auf dieser Webseite können Sie als registrierter Kunde, Material von Jonas Arnold Eventtechnik mieten. Sie erhalten direkt eine Bestätigung, ob das Material zum gewünschten Zeitpunkt noch verfügbar ist.
+ Sofern es verfügbar ist, wird es für Sie reserviert und wir nehmen im Anschluss Kontakt mit Ihnen auf.
+ Wenn Sie mehrere Materialien mieten möchten, füllen Sie das untenstehende Formular mehrmals aus. Die Bestellnummer bleibt während einer Stunde dieselbe.
+ Die verfügbaren Mietartikel und deren Nummer finden Sie hier:
+
+
+
Bestätigung Reservation
+
+
+
+ Die Mietanfrage wird mit der Datenbank abgeglichen. Sofern das Material verfügbar ist, wird es für Sie reserviert. Ansonsten werden Sie darüber informiert, dass das Material bereits vermietet ist.
+
+
+ Erfolgreiche Reservation
+
+
+
+ Reservation nicht möglich
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/rental_action.php b/rental_action.php
new file mode 100644
index 0000000..3fe91be
--- /dev/null
+++ b/rental_action.php
@@ -0,0 +1,202 @@
+
+
+
+ Mietanfrage
+
+
+
+
+
+
+
+
+
+
Rückmeldung Mietanfrage
+
+
+
+
+
+
+Bestellnummer: ".$order_num."";
+ echo "Gemietet von: ".$start_date." bis ".$end_date."";
+ echo "Material: ".$material_num." - ".$material_name;
+ if($material_desc != null){
+ echo " Beschreibung: ".$material_desc."";
+ } else{
+ echo "";
+ }
+}
+
+/* builds html content of the grid-element "confirmation details" a failed (unsuccessful) response */
+function buildFailedConfirmationDetailsSection($text) {
+ echo "".$text."";
+}
+
+/* main PHP */
+// initialize variables
+$failure_text = "Unbekannter Fehler";
+$material_name = "";
+$material_desc = "";
+$order_nr = 0;
+// get cookie with order nr, otherwise set it to 0
+if(isset($_COOKIE['OrderNr'])){
+ $order_nr = $_COOKIE['OrderNr'];
+}
+
+$rentalSuccess = false;
+// start validation of parameters
+if(validateParameters()){
+ // open SQL connection
+ $conn = mysqli_connect("localhost", "root", "", "rental_system");
+ if (!$conn) {
+ $failure_text = "Datenbankverbindung fehlgeschlagen.";
+ } else {
+ do{ // do once loop to be able to make breaks everywhere
+ $rental_art_nr = trim($_POST['rental_article_nr']);
+ $start_date = trim($_POST['start_date']);
+ $end_date = trim($_POST['end_date']);
+ $customer_nr = trim(intval($_POST['customer_nr']));
+
+ // check if rental article number exists in article database
+ $query = "SELECT * FROM articles WHERE article_number = ?";
+ $stmt = mysqli_prepare($conn, $query);
+ mysqli_stmt_bind_param($stmt, 's', $rental_art_nr);
+ mysqli_stmt_execute($stmt);
+ $res = mysqli_stmt_get_result($stmt);
+ if($res){
+ $row = mysqli_fetch_assoc($res); //expecting only one row since article_number is unique in SQL
+ if($row == null){ // row == null means there is no material with this number in the database
+ $failure_text = "Materialnummer existiert nicht in Datenbank.";
+ break;
+ }
+ $material_name = $row['article_name'];
+ $material_desc = $row['html_description'];
+ } else{
+ $failure_text = "Abfrage der Materialnummer fehlgeschlagen.";
+ break;
+ }
+
+ // check if the material is not yet rented out in the selected time range
+ $query = "SELECT * FROM rental_entries WHERE article_number = ? AND start_day <= ? AND end_day >= ?";
+ $stmt = mysqli_prepare($conn, $query);
+ mysqli_stmt_bind_param($stmt, 'sss', $rental_art_nr, $end_date, $start_date);
+ mysqli_stmt_execute($stmt);
+ $res = mysqli_stmt_get_result($stmt);
+ if($res){
+ $row = mysqli_fetch_assoc($res);
+ if($row != null){ // row == null means the material is not rented out in this time range
+ $failure_text = "Material ist bereits vermietet von ".$row['start_day']." bis ".$row['end_day'].".";
+ break;
+ }
+ } else{
+ $failure_text = "Abfrage der vermieteten Artikel fehlgeschlagen.";
+ break;
+ }
+
+ // if no order number exists yet => create one
+ if($order_nr == 0){
+ $order_nr = $customer_nr.".".date("YmdHis"); // Create unique order number with customer number and current time (full date + time up to seconds)
+ setcookie("OrderNr", $order_nr, time() + 3600); // valid for 1hr.
+ }
+
+ // enter the rental
+ $query = "INSERT INTO rental_entries (rental_id, article_number, start_day, end_day, order_number, customer_number) VALUES (NULL, ?, ?, ?, ?, ?);";
+ $stmt = mysqli_prepare($conn, $query);
+ mysqli_stmt_bind_param($stmt, 'ssssi', $rental_art_nr, $start_date, $end_date, $order_nr, $customer_nr);
+ $res = mysqli_stmt_execute($stmt);
+ if($res){
+ $rentalSuccess = true;
+ } else{
+ $failure_text = "Eintragen der Mietanfrage fehlgeschlagen. SQL query was:\n".$query;
+ break;
+ }
+
+ }while(0);
+
+ mysqli_close($conn); // close database connection if it was connected
+ }
+}
+
+/* final check for success, generate according defauls section */
+if($rentalSuccess){
+ echo "";
+ echo "