007. Envío Formulario y CORS con Fetch y PHP

En este capítulo trabajaremos del lado del servidor con PHP para poder enviar nuestros propios formularios sin necesidad de APIs externas. Trabajaremos con el archivo del capítulo anterior, sobre el que modificaremos algunos detalles como son la petición del endpoint al que consulta nuestra función fetch(). Todo lo demás funciona exactamente igual.

Veremos un concepto muy importante denominado CORS (Cross-Origin Resource Sharing) o intercambio de recursos de origen cruzado, es una política de normas que nos dice que todas las peticiones AJAX en teoría tendrían que estar en el mismo servidor. Tendríamos que activar en nuestro archivo html y php la petición CORS, es decir el intercambio entre dominios.

Al archivo del capítulo anterior, en este capítulo lo vamos a llamar contact-form-php.html. Y también vamos a crear un archivo PHP denominado send_mail.php, que será el que envíe el formulario. Veamos la sintaxis de nuestros archivos.

Sintaxis del archivos contact-form-php.html.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Envío de formulario con Fetch y PHP</title>
    <style>
      html {
        box-sizing: border-box;
        font-family: sans-serif;
        font-size: 16px;
      }

      *,
      *::before,
      *::after {
        box-sizing: inherit;
      }

      /* **** ContactForm Validations **** */
      .contact-form {
        --form-ok-color: #4caf50;
        --form-error-color: #f44336;
        margin-left: auto;
        margin-right: auto;
        width: 80%;
      }

      .contact-form > * {
        padding: 0.5rem;
        margin: 1rem auto;
        display: block;
        width: 100%;
      }

      .contact-form textarea {
        resize: none;
      }

      .contact-form legend,
      .contact-form-response {
        font-size: 1.5rem;
        font-weight: bold;
        text-align: center;
      }

      .contact-form input,
      .contact-form textarea {
        font-size: 1rem;
        font-family: sans-serif;
      }

      .contact-form input[type="submit"] {
        width: 50%;
        font-weight: bold;
        cursor: pointer;
      }

      .contact-form *::placeholder {
        color: #000;
      }

      .contact-form [required]:valid {
        border: thin solid var(--form-ok-color);
      }

      .contact-form [required]:invalid {
        border: thin solid var(--form-error-color);
      }

      .contact-form-error {
        margin-top: -1rem;
        font-size: 80%;
        background-color: var(--form-error-color);
        color: #fff;
        transition: all 800ms ease;
      }

      .contact-form-error.is-active {
        display: block;
        animation: show-message 1s 1 normal 0s ease-out both;
      }

      .contact-form-loader {
        text-align: center;
      }

      .none {
        display: none;
      }

      @keyframes show-message {
        0% {
          visibility: hidden;
          opacity: 0;
        }
        100% {
          visibility: visible;
          opacity: 1;
        }
      }
    </style>
  </head>

  <body>
    <form class="contact-form">
      <legend>Envía tus comentarios</legend>
      <input
        type="text"
        name="name"
        placeholder="Escribe tu nombre"
        title="Nombre sólo acepta letras y espacios en blanco"
        pattern="^[A-Za-zÑñÁáÉéÍíÓóÚúÜü\s]+$"
        required
      />
      <input
        type="email"
        name="email"
        placeholder="Escribe tu correo"
        title="Email incorrecto"
        pattern="^[a-z0-9]+(\.[_a-z0-9]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,15})$"
        required
      />
      <input
        type="text"
        name="subject"
        placeholder="Asunto a tratar"
        title="El asunto es requerido"
        required
      />
      <textarea
        name="comments"
        cols="50"
        rows="5"
        placeholder="Escribe tus comentarios"
        data-pattern="^.{1,255}$"
        title="Tu comentario no debe sobrepasar los 255 caracteres"
        required
      ></textarea>
      <input type="submit" value="Enviar" />
      <div class="contact-form-loader none">
        <img src="loader.svg" alt="Cargando" />
      </div>
      <div class="contact-form-response none">
        <p>Los datos han sido enviados</p>
      </div>
    </form>

    <script>
      const d = document;
     
       function contactForm() {
        const $form = d.querySelector(".contact-form"),
          $inputs = d.querySelectorAll(".contact-form [required]");
        // console.log($imputs);
        $inputs.forEach((input) => {
          const $span = d.createElement("span");
          $span.id = input.name;
          $span.textContent = input.title;
          $span.classList.add("contact-form-error", "none");
          input.insertAdjacentElement("afterend", $span);
        });

        d.addEventListener("keyup", (e) => {
          if (e.target.matches(".contact-form [required]")) {
            let $input = e.target,
              pattern = $input.pattern || $input.dataset.pattern;
            // console.log($input, pattern);
            if (pattern && $input.value !== "") {
              // console.log("El input tiene patrón");
              let regex = new RegExp(pattern);
              return !regex.exec($input.value)
                ? d.getElementById($input.name).classList.add("is-active")
                : d.getElementById($input.name).classList.remove("is-active");
            }

            if (!pattern) {
              // console.log("El input NO tiene patrón");
              return $input.value === ""
                ? d.getElementById($input.name).classList.add("is-active")
                : d.getElementById($input.name).classList.remove("is-active");
            }
          }
        });

        d.addEventListener("submit", (e) => {
          e.preventDefault();
          const $loader = d.querySelector(".contact-form-loader"),
            $response = d.querySelector(".contact-form-response");
          $loader.classList.remove("none");
          fetch("send_mail.php", {
            method: "POST",
            body: new FormData(e.target),
            mode: "cors",
          })
            .then((res) => (res.ok ? res.json() : Promise.reject(res)))
            .then((json) => {
              $loader.classList.add("none");
              $response.classList.remove("none");
              $response.innerHTML = `<p>${json.message}</p>`;
              $form.reset();
            })
            .catch((err) => {
              console.log(err);
              let message =
                err.statusText || "Ocurrió un error al enviar el formulario";
              $response.innerHTML = `<p>Error ${err.status}: ${message}</p>`;
            })
            .finally(() => {
              setTimeout(() => {
                $response.classList.add("none");
                $response.innerHTML = ``;
              }, 3000);
            });
        });
      }

      d.addEventListener("DOMContentLoaded", contactForm());
    </script>
  </body>
</html>

Sintaxis del archivo send_mail.php.

<?php
if(isset($_POST)){
    error_reporting(0);

    // Variables que vienen del formulario
    $name = $_POST["name"];
    $email = $_POST["email"];
    $subject = $_POST["subject"];
    $comments = $_POST["comments"];

    // Variables que necesito para enviar la petición
    $domain = $_POST["HTTP_HOST"];
    $to = "franwebsite@gmail.com";
    $subject = "Contacto desde el formulario del sitio $domain: $subject";
    $message = "
    <p>
    Datos enviados desde el formulario del sitio <b>$domain</b>
    </p>

    <ul>
        <li>Nombre: <b>$name</b></li>
        <li>Email: <b>$email</b></li>
        <li>Asunto: $subject</li>
        <li>Comentarios: $comments</li>
    </ul>
    ";

    $headers = "MIME - Version 1.0 \r\n"."Content-Type: text/html; charset=utf-8\r\n".
    "From: Envío Automático No Responder <no-replay@$domain>";
    $send_mail = mail($to, $subject, $message, $headers);

    if($send_mail) {
        $res = [
            "err" => false,
            "message" => "Tus datos han sido enviados"
        ];
    } else {
        $res = [
            "err" => true,
            "message" => "Error al enviar los datos, inténtalo nuevamente"
        ];
    }

    // header("Access-Control-Allow-Origin: https://sutilweb.eu")
    header("Access-Control-Allow-Origin: *");
    header("Content-Type: application/json");
    echo json_encode($res);
}
Scroll al inicio