ProcessWire Upload Formular mit AJAX: Unterschied zwischen den Versionen

Aus Wikizone
Wechseln zu: Navigation, Suche
 
(Eine dazwischenliegende Version von einem anderen Benutzer wird nicht angezeigt)
Zeile 3: Zeile 3:
  
 
Siehe auch:
 
Siehe auch:
 +
[[ProcessWire Upload Formular mit Drag and Drop]]
 
  [[JQuery - AJAX]]
 
  [[JQuery - AJAX]]
 
  [[jQuery - FormData Objekt (AJAX)]]
 
  [[jQuery - FormData Objekt (AJAX)]]
Zeile 570: Zeile 571:
  
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
== Helferlein ==
 +
=== Aufräumen von Temporären Files ===
 +
Durch fehlgelaufene Skripts können sich im temporären Verzeichnis alte Dateien ansammeln. Mit einem Cronjob oder auch bei jedem Anlegen einer neuen Seite, könnte man z.B. ältere Dateien löschen.
 +
<syntaxhighlight lang="php">
 +
/*
 +
* Delete older files (default 86400s = 24h)- use regularly
 +
* string $dir - path to files
 +
* int $seconds - all files older then $seconds
 +
* string $fileExtension only files with this extension (i.e. .jpg)
 +
*/
 +
function deleteOlderFiles( $dir='temp/', $seconds=86400, $fileExtension='' ){
 +
foreach (glob($dir."*".$fileExtension) as $file) {
 +
//For jpg images this would be: glob($dir."*.jpg")
 +
if(time() - filectime($file) > $seconds){
 +
unlink($file);
 +
}
 +
}
 +
}
 +
</syntaxhighlight>
 +
 +
=== Alte Seiten löschen ===
 +
Auch die angelegten Seiten sollte man nach einer gewissen Zeit löschen. Nicht nur um den Server zu schonen, sondern auch aus Datenschutzgründen.
 +
 +
TODO

Aktuelle Version vom 15. Februar 2019, 12:14 Uhr

Basiert auf dem Beispiel in diesem Artikel

ProcessWire Upload Formular

Siehe auch:

ProcessWire Upload Formular mit Drag and Drop
JQuery - AJAX
jQuery - FormData Objekt (AJAX)

Erweiterung des Beispiels ProcessWire Upload Formular[Bearbeiten]

Wir erweitern das Beispiel folgendermaßen:

  • Formulardaten über jQuery AJAX Funktionen abschicken
  • ProcessWire AJAX Antwort verarbeiten
  • Im Fehlerfall Fehlermeldungen an passender Stelle ausgeben
  • Im Erfolgsfall Dateien verarbeiten und PW Seiten anlegen
  • Im Erfolgsfall Formular per JavaScript aus der Seite entfernen und Erfolgsmeldung ausgeben

Schritt 1 - Formular anpassen[Bearbeiten]

Als erstes passen wir die Datei an, die für die Markup-Erzueugung zuständig ist (basic-upload-ajax.php). Im ursprünglichen Beispiel war es basic-upload.php.

JavaScript Datei für Verarbeitung der AJAX Requests einbinden[Bearbeiten]

$additionalFooterData .= '
<script src="'.urls()->templates.'partials/basic-upload-ajax/main.js"></script>
';

Ausgabe des Markup bei Ajax Requests blockieren[Bearbeiten]

Wir rufen das gleiche Skript bei Ajax Requests auf wie bei normalen. Daher müssen wir aufpassen, das kein unnötiges Markup ausgegeben wird. Da wir beim Ajax Request immer die Variable ajax=1 mitgeben können wir das ganz einfach machen:

 <?php if(!$ajax): ?>
 <!-- das ganze Markup... -->
 <?php endif; ?>

Markup für Fehlerausgaben über AJAX vorbereiten[Bearbeiten]

Damit wir die Fehlerausgaben direkt bei den betreffenden Feldern plazieren können vergeben wir Klassen, die auf den Feldnamen basieren. Später können wir das im JavaScript nutzen und die Labels im Fehlerfall einfärben, sowie eine Meldung an der Input Box ausgeben.

<label for="email" id="email-label">Email* </label>
<?php if(isset($errors['email'])) echo showError($errors['email']); ?>

Nachrichtenbox unter dem Formular[Bearbeiten]

Am Ende des Formulars bauen wir noch eine Box für Nachrichten ein und einen Upload Indikator. Das machen wir direkt nach dem Formular. Das Formular wird nach erfolgreichem Upload ausgeblendet, dann wollen wir aber trotzdem noch die Möglichkeit für eine Nachricht haben.

<div class="row">
  <div class="message-box" style="display:none;"></div>
</div>
  • JavaScript Datei für AJAX Versand hinzufügen
$additionalFooterData .= '
<script src="'.urls()->templates.'partials/basic-upload-ajax/main.js"></script>
';

Schritt 2 - Formular verarbeitung erweitern[Bearbeiten]

Die Verarbeitung liegt in form-process.php Hier müssen wir ebenfalls ein paar Routinen einfügen, die für das AJAX Handling zuständig sind.

AjaxResponse[Bearbeiten]

Dafür setzen wir ein mehrdimensionales Array ein. Dies wird später in ein JSON Objekt umgewandelt und an das Skript zurückgeschickt. Es enthält den Status success. Wenn der True ist hat alles geklappt. Dafür können wir also später die ohnehin schon für den nicht AJAX Ablauf vorhandene $success Variable einsetzen.

Das Array errors entspricht ebenfalls der $error Varible, die wir bei der Verarbeitung bisher schon genutzt haben.

Damit wir noch die Möglichkeit für allgemeine Nachrichten haben nehmen wir noch ein zweites Array hinzu nämlich messages.

 false, 'errors' => null, 'messages' => array() );
//...
// nach der POST Vars Validierung geben wir im Fehler Fall über AJAX die Fehler zurück:
/**
* if ajax request and we have errors sende errors back
*/
if($ajax && !empty($errors)){
   //var_dump($errors);
   $ajaxResponse['success'] = false;
   $ajaxResponse['errors'] = $errors;
   echo(json_encode($ajaxResponse));
 }

// ... 
// Wenn alles geklappt hat brauchen wir eine Erfolgsnachricht
// Deshalb am Ende der PW Seiten Erzeugung im Success Block ein neues Statement:
if($ajax){
  $ajaxResponse['success'] = $success;
  $ajaxResponse['messages'][] = $success_message;
  echo(json_encode($ajaxResponse));
}

// Auch wenn das Formular korrekt ausgefüllt wurde kann es bei der Verarbeitung noch zu Fehlern kommen. 
if($ajax){
  $ajaxResponse['success'] = $success;
  $ajaxResponse['errors'] = $errors;
  $ajaxResponse['messages'][] = 'Die Formulardaten konnten leider nicht verarbeitet werden. Versuchen Sie es später noch einmal oder melden sie sich direkt bei uns.';
  echo(json_encode($ajaxReponse));
}

Schritt 3 - AJAX Skripte[Bearbeiten]

Das Skript das wir in Schritt 1 eingebaut haben sieht etwa so aus.

Wir fangen den Klick auf den Submit Button ab, lesen die Formulardaten ein und schicken Sie an das PHP Skript.

In diesem Beispiel nutzen wir das FormularData Objekt. Das unterstützen alle aktuellen Browser und erspart uns viel Arbeit. Man könnte zur Erweiterung noch einen Test mit Modernizer o.ä. einbauen und im Zweifel das Formular dann doch ohne Ajax absenden.

Die Antwort werten wir aus. Im Erfolgsfall (wenn der Server success=true zurückgibt entfernen wir das form Markup, damit man das Formular nicht nochmal abschickt (das würde einen Fehler ergeben, da das Sicherheitstoken nicht mehr stimmt. Die Seite muss zunächst neu geladen werden (hier könnte man evtl. noch einen Link einbauen, wenn zu erwarten ist, dass das Formular mehrfach hintereinander genutzt wird.

Im Fehlerfall gibt und das PHP Skript das Objekt errors zurück. Das enthält als Schlüssel den Feldnamen und einen Fehlertext. Da wir für die Labels und die Inputfelder zu den Feldnamen passende Klassen gesetzt haben, können wir einfach eine Fehlerklasse für das Label setzen (error) und den Fehlertext beim Inputfeld ausgeben.

Als Extra setzen wir noch einen Upload Indikator. Schicker wäre eine animierte Grafik, die man während des Übermittelns sichtbar macht.

Dann haben wir noch einen Teil in dem wir die ganzen Fehlerklassen und Nachrichten löschen. So wird unser Formular nach dem erneuten absenden resettet, bevor die neuen Meldungen gesetzt werden.

main.js

$(function() {

		var href;
		var title;
		var uploadIndicator = document.getElementById('upload-indicator');
		$('body').on('click','#submit',function(e) { // Submit Button clicked
			// Update button text.
				uploadIndicator.innerHTML = 'Uploading...';
				href = $(this).attr("href");
				title = $(this).attr("name");
				// load content via AJAX
				handleData(href);
				// prevent click and reload
				e.preventDefault();
		});

		function handleData(url){

				// variable for ajaxResponse
				$ajaxResponse = '';
				// Form data

				// Not working with images or files:
				// var data = $('form').serialize();
				// $.post('url', data);

				var form = $('form')[0]; // You need to use standard javascript object here
				var fd = new FormData(form);
				//var myform = document.getElementById("myform");
				//var fd = new FormData(myform);
				fd.append("ajax", 'true');
				// send Ajax request
				$.ajax({
						type: "POST",
						url: url,
						data: fd,
						cache: false,
						processData: false,
						contentType: false,
						dataType: 'json', // tell jquery json response -> automatic deserialization
						success: function(response,status){
							$ajaxResponse = response;// store in $ajaxResponse
						}
				}).done(function(){ // when finished and successful
					// cleanup all messages and errors
					uploadIndicator.innerHTML = '';
					$('label').removeClass('error');
					$('.messages').html(' ');
					if($ajaxResponse.success){
						//success -> remove form and thank you
						$('#myform').html('');
						// Messages
						$.each($ajaxResponse.messages, function(key,val){
							$('#message-box').append('<div>' + val + '</div>');
						});
					}else{ // s.th. gone wrong -> handle errors
						$.each($ajaxResponse.errors, function(key, val) {
							// set error class
							console.log(key + ' -> ' + val);
							$('#'+key+'-label').addClass('error');
							// set error messages
							$('#'+key+'-messages').html(val);
						});
					}



				}); // end of ajax().done()
		} // end of loadContent()
});

PHP Skripte komplett[Bearbeiten]

basic-upload-ajax.php[Bearbeiten]

<?php namespace ProcessWire;
$additionalHeaderData .= '
';
$additionalFooterData .= '
<script src="'.urls()->templates.'partials/basic-upload-ajax/main.js"></script>
';
?>
<?php if(!$ajax): ?>
<!DOCTYPE html>
<html lang="de">
<?php include('partials/head.inc'); ?>
<body>
<?php endif; ?>
<?php
/**
 * ### Example front-end form template with file upload and fields ###
 *
 * - with files (images) upload to page field
 * - adds new page on the fly and adds uploaded images
 * - prevents CRSF attacks, this also prevents double post by refresh page after submit
 * - has required fields with error messages inline
 * - sanitizing and saving values to a page
 * - jquery example with disabled submit button on form submit
 *
 * Edit add or remove form markup below and configure this section according to what you need.
 *
 */


// ------------------------------ FORM Configuration ---------------------------------------

// --- Some default variables ---
$success_message   = '<p class="message">Vielen Dank für Ihre Nachricht!</p>';
$success = false;

// --- All form fields as nested array ---
// using html form field name => template field nam, from the page you're going to create
$form_fields = array(
    'fullname'              => array('type' => 'text', 'value' => '', 'required' => true),
    'email'                 => array('type' => 'email', 'value' => '', 'required' => true),
    'message'               => array('type' => 'textarea', 'value' => '', 'required' => true),
    'newsletter_subscribe'  => array('type' => 'checkbox', 'value' => 0, 'required' => false),
    'images'                => array('type' => 'file', 'value' => 0, 'required' => true)
);

// --- WireUpload settings ---
$upload_path        = $config->paths->assets . "files/.tmp_uploads/"; // tmp upload folder
$file_extensions    = array('jpg', 'jpeg', 'gif', 'png');
$max_files          = 3;
$max_upload_size    = 2*1024*1024; // make sure PHP's upload and post max size is also set to a reasonable size
$overwrite          = false;

// --- Page creation settings ---
$template           = "basic-upload-entry"; // the template used to create the page
$parent             = $pages->get("/basic-upload/");
$file_field         = "images";
$page_fields        = array('fullname','email','message','newsletter_subscribe','images');

// $page_fields = define the fields (except file) you want to save value to a page
// this is for the form process to populate page fields.
// Your page template must have the same field names existent


// ------------------------------ FORM Processing ---------------------------------------

  include("./partials/basic-upload-ajax/form-process.php");

?>
<?php if(!$ajax): ?>
<!-- ========================= FORM HTML markup  ================================== -->

<?php

/**
 * Some vars used on the form markup for error and population of fields
 *
 * $errors[fieldname]; to get errors
 * $form_fields[fieldname]['value'];
 *
 * Some helper function to get error markup
 * echo showError(string);
 *
 * Prevent CSRF attacks by adding hidden field with name and value
 * you an get by using $session->CSRF
 * $session->CSRF->getTokenName();
 * $session->CSRF->getTokenValue();
 *
 * $errors['csrf']; used to check for CSRF error
 *
 */

?>

<div class="content">

    <h2>Upload Images to Page Example Form</h2>
		<p>Creates Pages in Backend from Form including upload field.</p>



<?php if(!$success) : ?>

    <?php if(!empty($errors)) echo showError("Form contains errors"); ?>
    <div id="csrf-messages" class="messages"><?php if(!empty($errors['csrf'])) echo showError($errors['csrf']); ?></div>

    <form name="myform" class="myform" id="myform" method="post" action="./" enctype="multipart/form-data">

        <input type="hidden" name="<?php echo $session->CSRF->getTokenName(); ?>" value="<?php echo $session->CSRF->getTokenValue(); ?>"/>

        <div class="row <?php if(isset($errors['fullname'])) echo "error";?>">
            <label for="fullname" id="fullname-label">Name* </label><br/>
            <input type="text" name="fullname" id="fullname" value="<?php echo $sanitizer->entities($form_fields['fullname']['value']); ?>"/>
            <div id="fullname-messages" class="messages"><?php if(isset($errors['fullname'])) echo showError($errors['fullname']); ?></div>
        </div>

        <div class="row <?php if(isset($errors['email'])) echo "error";?>">
            <label for="email" id="email-label">Email* </label><br/>
            <input type="text" name="email" id="email" value="<?php echo $sanitizer->entities($form_fields['email']['value']); ?>"/>
            <div id="email-messages" class="messages"><?php if(isset($errors['email'])) echo showError($errors['email']); ?></div>
        </div>

        <div class="row <?php if(isset($errors['message'])) echo "error";?>">
            <label for="message" id="message-label">Message* </label><br/>
            <textarea type="text" name="message" id="message"><?php echo $sanitizer->entities($form_fields['message']['value']); ?></textarea>
            <div id="message-messages" class="messages"><?php if(isset($errors['message'])) echo showError($errors['message']); ?></div>
        </div>

        <div class="row <?php if(isset($errors['newsletter_subscribe'])) echo "error";?>">
            <label for="newsletter_subscribe" id="newsletter_subscibe-label">Newsletter* </label><br/>
            <input type="checkbox" name="newsletter_subscribe" id="newsletter_subscribe"
                <?php echo $form_fields['newsletter_subscribe']['value'] ? "checked='checked'" : "" ; ?>
            />
            <div id="newsletter_subscribe-messages" class="messages"><?php if(isset($errors['newsletter_subscribe'])) echo showError($errors['newsletter_subscribe']); ?></div>
        </div>

        <div class="row <?php if(isset($errors['images'])) echo "error";?>">
            <label for="images" id="images-label">Images* </label><br/>
            <input type="file" name="images[]" id="images" multiple="multiple" size="40" accept="image/jpg,image/jpeg,image/gif,image/png"/>
            <div id="images-messages" class="messages">
              <?php
            // show upload errors
            if(isset($errors['images'])){
                // if multiple errors
                if(is_array($errors['images'])){
                    foreach($errors['images'] as $e){
                        echo showError($e);
                    }
                } else { // if single error
                    echo showError($errors['images']);
                }
            }
            ?>
          </div>
        </div>
        <div class="row">
            <input type="hidden" name="action" id="action" value="send"/>
            <input type="submit" name="submit" id="submit" value="Submit"/>

        </div>
        <div class="row">
          <div id="upload-indicator" class="hidden">Upload Indicator</div>
        </div>
    </form>
    <div class="row">
      <div id="message-box" class="messages"></div>
    </div>

<?php else: ?>

    <p><?php echo $success_message; ?></p>

<?php endif; ?>

</div>
<?php endif; ?>

<?php if(!$ajax): ?>
<?php include('partials/footer-js.inc');	?>

</body>
</html>
<?php endif; ?>

form-process.php[Bearbeiten]

<?php namespace ProcessWire;

// ------------------------------ FORM Processing ---------------------------------------

$errors            = null;
$success           = false;
$messages          = null;
$ajaxResponse      = array( 'success' => false, 'errors' => null, 'messages' => array() );

// helper function to format form errors
function showError($e){
    return "<p class='error'>$e</p>";
}

// dump some variables
//var_dump($_FILES,$_POST,$_SESSION);

/**
 * Cast and save field values in array $form_fields
 * this is also done even form not submited to make populating the form later easier.
 *
 * Also used for populating page when form was valid
 */
$required_fields = array();
foreach($form_fields as $key => $f){
    if($f['type'] == 'text'){
        $form_fields[$key]['value'] = $sanitizer->text($input->post->$key);
    }
    if($f['type'] == 'textarea'){
        $form_fields[$key]['value'] = $sanitizer->textarea($input->post->$key);
    }
    if($f['type'] == 'email'){
        $form_fields[$key]['value'] = $sanitizer->email($input->post->$key);
    }
    if($f['type'] == 'checkbox'){
        $form_fields[$key]['value'] = isset($input->post->$key) ? 1 : 0;
    }
    // store required fields in array
    if($f['required']) $required_fields[] = $key;
}




/**
 * form was submitted, start processing the form
 */
if($input->post->action == 'send'){

    // validate CSRF token first to check if it's a valid request
    if(!$session->CSRF->hasValidToken()){
        $errors['csrf'] = "Form submit was not valid, please try again.";
    }

    /**
     * Ceck for required fields and make sure they have a value
     */
    foreach($required_fields as $req){

        // required upload file field
        if($form_fields[$req]['type'] == 'file'){
            if(empty($_FILES[$req]['name'][0])){
                $errors[$req] = "Select files to upload.";
            }
        // reqired checkbox fields
        } else if($form_fields[$req]['type'] == 'checkbox'){
            if($form_fields[$req]['value'] == 0){
                $errors[$req] = "Field required";
            }
        // reqired text fields
        } else if($form_fields[$req]['type'] == 'text'
                  || $form_fields[$req]['type'] == 'textarea'
                  || $form_fields[$req]['type'] == 'email'){
            if(!strlen($form_fields[$req]['value'])){
                $errors[$req] = "Field required";
            }
            // reqired email fields
            if($form_fields[$req]['type'] == 'email'){
                if($form_fields[$req]['value'] != $input->post->$req){
                    $errors[$req] = "Please enter a valid Email address.";
                }
            }
        }
    }


    /**
     * if ajax request and we have errors sende errors back
     */
    if($ajax && !empty($errors)){
      //var_dump($errors);
      $ajaxResponse['success'] = false;
      $ajaxResponse['errors'] = $errors;
      echo(json_encode($ajaxResponse));
    }
    /**
     * if no required errors found yet continue file upload form processing
     */
    if(empty($errors)) {

        // RC: create temp path if it isn't there already
        if(!is_dir($upload_path)) {
            if(!wireMkdir($upload_path)) throw new WireException("No upload path!");
        }

        // setup new wire upload
        $u = new WireUpload($file_field);
        $u->setMaxFiles($max_files);
        $u->setMaxFileSize($max_upload_size);
        $u->setOverwrite($overwrite);
        $u->setDestinationPath($upload_path);
        $u->setValidExtensions($file_extensions);

        // start the upload of the files
        $files = $u->execute();

        // if no errors when uploading files
        if(!$u->getErrors()){

            // create the new page to add field values and uploaded images
            $uploadpage = new Page();
						//print_r($template);
						//var_dump($parent);
            $uploadpage->template = $template;
            $uploadpage->parent = $parent;

            // add title/name and make it unique with time and uniqid
            $uploadpage->title = date("Y-m-d_H:i:s") . " - " . uniqid();
            $uploadpage->save();
            // populate page fields with values using $page_fields array
            foreach($page_fields as $pf){
                if($templates->get($template)->hasField($pf)){
                    //var_dump($form_fields[$pf]);
                    $uploadpage->$pf = $form_fields[$pf]['value'];
                } else {
                    throw new WireException("Template '$template' has no field: $pf");
                }
            }

            // RC: for safety, only add user uploaded files to an unpublished page, for later approval
            // RC: also ensure that using v2.3+, and $config->pagefileSecure=true; in your /site/config.php
            $uploadpage->addStatus(Page::statusUnpublished);
            $uploadpage->save();

            // Now page is created we can add images upload to the page file field
            foreach($files as $filename) {
                $uploadpage->$file_field = $upload_path . $filename;
                // remove tmp file uploaded
                unlink($upload_path . $filename);
            }
            $uploadpage->save();

            // $success_message .= "<p>Page created: <a href='$uploadpage->url'>$uploadpage->title</a></p>";
            $success = true;

            // reset the token so no double posts happen
            // also prevent submit button to from double clicking is a good pratice
            $session->CSRF->resetToken();

            if($ajax){
              $ajaxResponse['success'] = $success;
              $ajaxResponse['messages'][] = $success_message;
              //var_dump($errors);
              echo(json_encode($ajaxResponse));
            }

        } else {
            // errors found
            $success = false;

            // remove files uploaded
            foreach($files as $filename) unlink($upload_path . $filename);

            // get the errors
            if(count($u->getErrors()) > 1){ // if multiple error
                foreach($u->getErrors() as $e) {
                    $errors[$file_field][] = $e;
                }
            } else { // if single error
                $errors[$file_field] = $u->getErrors();
            }
            if($ajax){
              $ajaxResponse['success'] = $success;
              $ajaxResponse['errors'] = $errors;
              $ajaxResponse['messages'][] = 'Bilder konnten nicht gespeichert werden. Versuchen Sie es später noch einmal.';
              //var_dump($errors);
              echo(json_encode($ajaxReponse));
            }
        }
    }
}

Helferlein[Bearbeiten]

Aufräumen von Temporären Files[Bearbeiten]

Durch fehlgelaufene Skripts können sich im temporären Verzeichnis alte Dateien ansammeln. Mit einem Cronjob oder auch bei jedem Anlegen einer neuen Seite, könnte man z.B. ältere Dateien löschen.

/*
 * Delete older files (default 86400s = 24h)- use regularly
 * string $dir - path to files
 * int $seconds - all files older then $seconds
 * string $fileExtension only files with this extension (i.e. .jpg)
 */
function deleteOlderFiles( $dir='temp/', $seconds=86400, $fileExtension='' ){
	foreach (glob($dir."*".$fileExtension) as $file) {
		//For jpg images this would be: glob($dir."*.jpg")
		if(time() - filectime($file) > $seconds){
			unlink($file);
		}
	}
}

Alte Seiten löschen[Bearbeiten]

Auch die angelegten Seiten sollte man nach einer gewissen Zeit löschen. Nicht nur um den Server zu schonen, sondern auch aus Datenschutzgründen.

TODO