# contact-forms/2.2.32/AccuaForm.php

Contact Forms by Cimatti, version 2.2.32. 2,275 lines.

- Page: https://pluginprobe.com/plugins/contact-forms/2.2.32/code/AccuaForm.php
- Raw: https://pluginprobe.com/plugins/contact-forms/2.2.32/raw/AccuaForm.php
- Modified: 2026-07-13T10:05:26+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/contact-forms/2.2.32/code/AccuaForm.php#L10-L20`.

```php
<?php
if ( ! defined( 'ABSPATH' ) ) exit;

// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.Security.EscapeOutput.HeredocOutputNotEscaped, PluginCheck.CodeAnalysis.Heredoc.NotAllowed, WordPress.PHP.DevelopmentFunctions.error_log_trigger_error, WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.WP.AlternativeFunctions.rename_rename -- PFBC Form extension class with controlled HTML output, deprecation notices, debug logging, file operations
class AccuaForm extends Form {
  protected static $submitted = null;
  protected static $valid = null;
  protected static $submittedID = null;
  protected static $submittedBuildID = null;
  protected static $submittedForm = null;
  protected static $submittedFormUsed = false;
  protected static $submittedData = null;
  protected static $rawData = null;
  /**
   * Per-form submitted messages array.
   * Keys are form IDs, values are message strings.
   * This allows multiple forms on the same page to have separate messages.
   * @var array<string, string>
   */
  protected static $submittedMessages = array();

  protected $formID = null;
  protected $buildID = null;
  protected $validate_functions = array();
  protected $submit_functions = array();
  protected $elements_sleep;
  public $stats = array();
  protected $accua_ajax;
  protected $locale = null;
  protected $language = null;
  protected $files = array();
  protected $ga_track = array();
  protected $gads_conversion_tracking_code = '';

  protected $original_locale = null;
  protected $original_language = null;
  protected $original_l10n = null;
  protected $forced_language = false;
  protected $elementsByName = array();
  protected $elementCounter = 0;

  public function force_language() {
    if ((!$this->forced_language) && $this->language && function_exists('qtrans_getLanguage')) {
      global $q_config;
      $this->original_language = $q_config['language'];

      if ($this->language != $this->original_language) {
        $this->original_locale =& $GLOBALS['wp_locale'];
        $this->original_l10n =& $GLOBALS['l10n'];

        unset($GLOBALS['wp_locale']);
        unset($GLOBALS['l10n']);
        $GLOBALS['l10n'] = array();

        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- qTranslate plugin integration
        $GLOBALS['q_config']['language'] = $this->language;
        load_default_textdomain();
        // phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound -- Intentionally forces language at runtime for qTranslate email delivery
        load_plugin_textdomain( 'contact-forms', false, ACCUA_FORM_API_PLUGIN_TEXTDOMAIN_PATH);
        require_once( ABSPATH . WPINC . '/locale.php' );
        $GLOBALS['wp_locale'] = new WP_Locale();
        $GLOBALS['wp_locale']->register_globals();

        $this->forced_language = true;
      }
    }
  }

  public function restore_language() {
    if ($this->forced_language) {
      unset($GLOBALS['wp_locale']);
      unset($GLOBALS['l10n']);

      // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- qTranslate plugin integration
      $GLOBALS['q_config']['language'] = $this->original_language;
      $GLOBALS['l10n'] =& $this->original_l10n;
      $GLOBALS['wp_locale'] =& $this->original_locale;
      if ($GLOBALS['wp_locale']) {
        $GLOBALS['wp_locale']->register_globals();
      }

      $this->forced_language = false;
    }
  }

  public function __sleep() {
    $this->elements_sleep = $this->getElements();
    return array('attributes', 'elements_sleep', 'error', 'view', 'prefix', 'widthSuffix', 'ajax', 'ajaxCallback', 'jQueryUITheme', 'resourcesPath', 'prevent', 'width', 'formID', 'buildID', 'validate_functions', 'submit_functions', 'stats', 'accua_ajax', 'locale', 'language', 'files');
  }

  public function __wakeup() {
    foreach ($this->elements_sleep as $element) {
      $this->addElement($element);
    }
    unset($this->elements_sleep);
    if ($this->view) {
    	$this->view->setForm($this);
    }
    if ($this->error) {
    	$this->error->setForm($this);
    }
  }

  public function addElement(Element $element) {
    $name = $element->getName();
    if ($name) {
      $this->elementsByName[$name] = $element;
    }
		$id = $element->getID();
		if(empty($id)) {
			$element->setID($this->attributes["id"] . "-element-" . $this->elementCounter);
		}
		$this->elementCounter++;
    return parent::addElement($element);
  }

  public function getElementByName($name) {
    if (isset($this->elementsByName[$name])) {
      return $this->elementsByName[$name];
    } else {
      return null;
    }
  }

  public function removeElement($element) {
    foreach ($this->elements as $k => $e) {
      if ($e === $element) {
        $name = $element->getName();
        if ($name) {
          unset ($this->elementsByName[$name]);
        }
        unset($this->elements[$k]);
        return true;
      }
    }
    return false;
  }

  public static function sessionID() {
    $sessionid = session_id();
    if ($sessionid === '' && !defined('DOING_CRON')) {
      $started = session_start();
      if (!$started) {
        $file = $line = '';
        if(headers_sent($file,$line)) {
          error_log("headers already sent at {$file}:{$line}");
        }
      }
      $sessionid = session_id();
      // Close the session immediately after getting the ID to prevent REST API interference
      session_write_close();
    }
    return $sessionid;
  }

  public static function getBaseURL() {
    static $ret = null;
    if ($ret === null) {
      // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Building internal URL from server variables
      $s = empty($_SERVER["HTTPS"]) ? '' : (($_SERVER["HTTPS"] == "on") ? "s" : "");
      // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Building internal URL from server variables
      $sp = isset($_SERVER["SERVER_PROTOCOL"]) ? strtolower(sanitize_text_field(wp_unslash($_SERVER["SERVER_PROTOCOL"]))) : 'http/1.1';
      $protocol = substr($sp, 0, strpos($sp, "/")) . $s;
      // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Building internal URL from server variables
      $port = (!isset($_SERVER["SERVER_PORT"]) || $_SERVER["SERVER_PORT"] == "80") ? "" : (":" . absint($_SERVER["SERVER_PORT"]));
      // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Building internal URL from server variables
      $ret = $protocol . "://" . (isset($_SERVER['SERVER_NAME']) ? sanitize_text_field(wp_unslash($_SERVER['SERVER_NAME'])) : 'localhost') . $port;
    }
    return $ret;
  }

  public static function create($baseid = 'pfbc', $params = array()) {
    if (self::isSubmit() && (!self::isValid()) && ($baseid === self::$submittedID) && (!self::$submittedFormUsed)) {
      self::$submittedFormUsed = true;
      if (!(self::isValid() || empty(self::$submittedForm))) {
        $form = self::$submittedForm;
        $form->setValues(self::$submittedData);
        return $form;
      }
      $form = new AccuaForm ($baseid, $params, self::$submittedBuildID);
      $form->setValues(self::$submittedData);
    } else {
      $form = new AccuaForm($baseid, $params);
    }
    if (function_exists($baseid)) {
      call_user_func($baseid, $form);
    }
    do_action('accua_form_alter', $baseid, $form);
    return $form;
  }

  function __construct($id = 'pfbc', $params = array()) {
    if (func_num_args() > 2) {
      $buildid = (string) func_get_arg(2);
    } else {
      $buildid = '';
    }
    if ($buildid === '') {
      $buildid = 'accua-form_' . $id . '_' . uniqid();
    }

    $predefined_params = array(
      'width' => '',
      'layout' => 'sidebyside',
      'title' => '',
      'track_submit' => false,
      'track_fields' => false,
      'gads_conversion_tracking_code' => '',
    );
    if (is_array($params)) {
      $params += $predefined_params;
      $width = $params['width'];
    } else {
      $width = $params;
      $params = $predefined_params;
      $params['width'] = $width;
    }
    // Always initialize the property to avoid undefined property warnings
    $this->gads_conversion_tracking_code = $params['gads_conversion_tracking_code'];

    $class = 'accua-form ' . $id;
    switch ($params['layout']) {
      case 'toplabel':
        $this->view = new AccuaForm_View_Standard();
        $class .= ' accua-form-view-standard';
      break;
      case 'inlinelabel':
        $this->view = new AccuaForm_View_InlineLabel();
        $class .= ' accua-form-view-inlinelabel';
        // Prevent PFBC from adding inline width styles for inline label layout
        $width = '';
      break;
      case 'sidebyside':
      default:
        $this->view = new AccuaForm_View_SideBySide(
          '19',
          array('labelPaddingRight' => '1')
        );
        $class .= ' accua-form-view-sidebyside';
    }
    $this->error = new AccuaForm_Error_Standard(array(
      'errorfound' => '',
      'errorsfound' => '',
    ));
    // For inline label view, we don't set default width
    // The CSS handles all widths with width: 100%
    if ($width === "" && $params['layout'] !== 'inlinelabel'){
      $width = "100%";
    }
    $this->attributes = array(
      'class' => $class,
      'novalidate' => 'novalidate',
    );
    foreach (array('title', 'track_submit', 'track_fields') as $i) {
      $this->ga_track[$i] = $params[$i];
    }

    parent::__construct($buildid, $width);


    $this->formID = $id;
    $this->buildID = $buildid;
    $this->addElement(new Element_Hidden('_AccuaForm_ID', $id));
    $this->addElement(new Element_Hidden('_AccuaForm_buildID', $buildid));
    $this->addElement(new Element_Hidden('_AccuaForm_wpnonce', wp_create_nonce( $buildid )));
    $this->addElement(new Element_Hidden('_AccuaForm_jsuuid', ''));
    $this->addElement(new Element_Hidden('_AccuaForm_referrer', ''));
    $this->addElement(new Element_Hidden('_AccuaForm_user_agent', ''));
    $this->addElement(new Element_Hidden('_AccuaForm_platform', ''));
    $this->addElement(new Element_Hidden('_AccuaForm_tentatives', '0'));
    $this->addElement(new Element_Hidden('_AccuaForm_submit_method', 'normal'));
    $this->addElement(new Element_Hidden('_AccuaForm_hash', ''));
    $this->addElement(new Element_Hidden('_AccuaForm_iv', ''));
    $this->addElement(new Element_Hidden('_AccuaForm_data', ''));
    /*
     * - Prima di generare l'html del form, salva una copia serializzata compreso di codice SHA2, criptato, in _AccuaForm_serialized
     * - Quando ricevi il form, decripta _AccuaForm_serialized e controlla che sia valido
     *
     * */

    $this->prevent = array('jQuery', 'jQueryUI', 'jQueryUIButtons', 'focus', 'style');
    $this->configure(array('action' => '#'));

    if (function_exists('qtrans_getLanguage')) {
      $this->language = qtrans_getLanguage();
      $this->locale = $GLOBALS['q_config']['locale'][$this->language];
    } else {
      $this->locale = get_locale();
      $this->language = explode('_', $this->locale);
      $this->language = $this->language[0];
    }

    global $post;
    $pid = empty($post->ID) ? 0 : $post->ID;

    // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- URL stored for logging, escaped on output
    $uri = isset($GLOBALS['q_config']['url_info']['original_url']) ? $GLOBALS['q_config']['url_info']['original_url'] : (isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '');

    $url = self::getBaseURL() . $uri ;
    // Set the form action to the current page URL so non-AJAX forms submit cleanly
    // (AJAX forms override this later in the JS to admin-ajax.php)
    $this->configure(array('action' => $url));

    // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Referrer stored for logging, escaped on output
    $referrer = isset($_SERVER['HTTP_REFERER']) ? esc_url_raw(wp_unslash($_SERVER['HTTP_REFERER'])) : '';

    // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- IP stored for logging, escaped on output
    $remote_addr = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '';

    $this->stats = array(
      'pid' => $pid,
      'ip' => $remote_addr, //updated after submit
      'original_ip' => $remote_addr, //unreliable if using a static page caching system
      'uri' => $uri,
      'url' => $url,
      'referrer' => $referrer, //updated after submit using javascript
      'original_referrer' => $referrer, //unreliable if using a static page caching system
      'lang' => $this->language,
      'locale' => $this->locale,
      'created' => time(),
      'submitted' => null,
      'user_agent' => '',
      'platform' => '',
      'tentatives' => '',
      'submit_method' => '',
    );

    $this->view->setForm($this);
    $this->error->setForm($this);
  }

  public static function isSubmit() {
    if (self::$submitted !== null) {
      return self::$submitted;
    }
    self::$submitted = false;
    $sessid = self::sessionID();

    // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Checking request method for form handling
    $request_method = isset($_SERVER['REQUEST_METHOD']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_METHOD'])) : 'GET';
    if ($request_method === 'POST') {
      self::$rawData = stripslashes_deep($_POST);
    } else {
      self::$rawData = stripslashes_deep($_GET);
    }

    // Ensure basic fields for nonce verification are present.
    if (empty(self::$rawData['_AccuaForm_buildID']) || empty(self::$rawData['_AccuaForm_wpnonce'])) {
      return false;
    }

    // Verify nonce for CSRF protection (moved earlier).
    // The $id variable (action for nonce) is self::$rawData['_AccuaForm_buildID'].
    $requested_buildID = self::$rawData['_AccuaForm_buildID'];
    if (!wp_verify_nonce(self::$rawData['_AccuaForm_wpnonce'], $requested_buildID)) {
      return false;
    }

    // Now check for fields required for form recovery and other validations.
    if (empty(self::$rawData['_AccuaForm_ID'])
        || empty(self::$rawData['_AccuaForm_hash']) || empty(self::$rawData['_AccuaForm_iv']) || empty(self::$rawData['_AccuaForm_data'])) {
      return false;
    }

    // $id was self::$rawData['_AccuaForm_buildID'], now using $requested_buildID for clarity.
    $form = self::wp_recover(self::$rawData['_AccuaForm_hash'], self::$rawData['_AccuaForm_iv'], self::$rawData['_AccuaForm_data']);

    if (empty($form)) {
      return false;
    }

    if ($form->formID !== self::$rawData['_AccuaForm_ID'] || $form->buildID !== $requested_buildID ) {
      return false;
    }

    /* check if already submitted from uuid and other parameters */
    $already_submitted = false;
    if (!empty(self::$rawData['_AccuaForm_jsuuid'])) {
      if (preg_match("/^[a-z0-9]{25}$/", self::$rawData['_AccuaForm_jsuuid'])) {
        $already_submitted_data = get_transient('accuaformsub_'.self::$rawData['_AccuaForm_jsuuid']);
        if ($already_submitted_data && $already_submitted_data['buildID'] == $form->buildID) {
          // Only use cached result for successful submissions to prevent duplicate processing
          if (!empty($already_submitted_data['valid'])) {
            $form = $already_submitted_data['form'];
            self::$submittedMessages = $already_submitted_data['submittedMessages'];
            self::$valid = true;
            $already_submitted = true;
          } else {
            // For failed validations, delete the transient so form can be re-validated
            // This fixes the bug where correcting a field and resubmitting caused form to disappear
            delete_transient('accuaformsub_'.self::$rawData['_AccuaForm_jsuuid']);
          }
        }
      } else {
        return false;
      }
    }

    if (!$already_submitted) {
      $form->stats['submitted'] = time();
      // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- IP stored for logging, escaped on output
      $form->stats['ip'] = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '';
      foreach (array('referrer', 'jsuuid', 'user_agent', 'platform', 'tentatives', 'submit_method') as $i){
        $form->stats[$i] = isset(self::$rawData["_AccuaForm_{$i}"]) ? ((string)self::$rawData["_AccuaForm_{$i}"]) : '';
      }
    }
    self::$submitted = true;
    self::$submittedID = $form->formID;
    self::$submittedBuildID = $form->buildID;
    self::$submittedForm = $form;
    return true;
  }

  public static function isValid($id = "pfbc", $clearValues = true) {
    if (!self::isSubmit()){
      return null;
    }
    if (self::$valid !== null) {
      return self::$valid;
    }

    //$subdata = array();
    $id = self::$submittedBuildID;
    //$form = self::wp_recover($id);
    $form = self::$submittedForm;
    $valid = true;

    $form->force_language();

    /*Any values/errors stored in the session for this form are cleared.*/
    self::clearValues($id);
    self::clearErrors($id);

    self::$submittedData = array();
    /*Each element's value is saved in the session and checked against any validation rules applied
    to the element.*/
    $elements = $form->getElements();
    if(!empty($elements)) {
      foreach($elements as $element) {
        $invalidFile = false;
        $name = $element->getName();
        if(substr($name, -2) == "[]") {
          $name = substr($name, 0, -2);
        }

        /*The File element must be handled differently b/c it uses the $_FILES superglobal and
        not $_GET or $_POST.*/
        if($element instanceof AccuaForm_Element_File) {
          if (!empty($form->files[$name]['name'])) {
            $value = $form->files[$name]['name'];
          // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- File handling uses $_FILES, nonce verified earlier
          } else if ((!empty($_FILES[$name])) && (isset($_FILES[$name]['error']) && $_FILES[$name]['error'] != UPLOAD_ERR_NO_FILE)) {
            //$file = $_FILES[$name];
            //include_once( ABSPATH . '/wp-admin/includes/file.php' );
            //$overrides = array( 'test_form' => false );
            //$file = wp_handle_upload( $file, $overrides );

            // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- File array passed to WordPress handler
            $file = $element->handle_upload($_FILES[$name]);
            $value = $file['name'];

            if (empty($file['errors'])) {
              $form->files[$name] = $file;
            } else {
              self::setError($id, $file['errors'] , $name);
              $valid = false;
              $invalidFile = true;
            }
          } else {
            $value = null;
          }
        } else if ($element instanceof Element_File){
          // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Legacy file handling
          $value = isset($_FILES[$name]["name"]) ? sanitize_file_name($_FILES[$name]["name"]) : '';
        } else if (isset(self::$rawData[$name])) {
          $value = self::$rawData[$name];
          if(is_array($value)) {
            foreach($value as $key => $value_i) {
              // Sanitize null bytes to prevent mail() ValueError in PHP 8+
              $value[$key] = str_replace("\0", '', (string) $value_i);
            }
          } else {
            // Sanitize null bytes to prevent mail() ValueError in PHP 8+
            $value = str_replace("\0", '', (string) $value);
          }
        } else {
          $value = null;
        }

        //self::setSessionValue($id, $name, $value);

        /*If a validation error is found, the error message is saved in the session along with
        the element's name.*/
        if(!$element->isValid($value)) {
          self::setError($id, $element->getErrors(), $name);
          $valid = false;
          if ($element instanceof AccuaForm_Element_File) {
            $invalidFile = true;
          }
        }

        if($invalidFile) {
          // File cleanup is handled in handle_upload() when validation fails
          // The uploaded tmp file is already deleted by PHP after request completes
          unset($form->files[$name]);
        } else if($name !== '') {
          self::$submittedData[$name] = $value;
        }
      }
    }
    $_SESSION["pfbc"][$id]["values"] = self::$submittedData;


    if (function_exists(self::$submittedID.'_validate')) {
      $valid = call_user_func(self::$submittedID.'_validate', $valid, self::$submittedID, self::$submittedData, $form);
    }
    $valid = apply_filters('accua_form_validate', $valid, self::$submittedID, self::$submittedData, $form);

    asort($form->validate_functions);
    foreach($form->validate_functions as $func => $priority){
      if (function_exists($func)) {
        $valid = call_user_func($func, $valid, self::$submittedID, self::$submittedData, $form);
      }
    }

    if ($valid) {
      if (function_exists(self::$submittedID.'_submit')) {
        call_user_func(self::$submittedID.'_submit', self::$submittedID, self::$submittedData, $form);
      }
      do_action('accua_form_submit', self::$submittedID, self::$submittedData, $form);
      asort($form->submit_functions);
      foreach($form->submit_functions as $func => $priority){
        if (function_exists($func)) {
          call_user_func($func, self::$submittedID, self::$submittedData, $form);
        }
      }
    }

    /*Apply errors from session to form elements for accessibility (ARIA attributes).
    This must be done BEFORE session_write_close() is called, so errors persist in element objects.*/
    // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Session data for form error handling
    if (!$valid && !empty($_SESSION["pfbc"][$id]["errors"])) {
      // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Session data for form error handling
      $sessionErrors = $_SESSION["pfbc"][$id]["errors"];
      $elements = $form->getElements();
      if (!empty($elements)) {
        foreach ($elements as $element) {
          $name = $element->getName();
          if (substr($name, -2) == "[]")
            $name = substr($name, 0, -2);
          
          if (isset($sessionErrors[$name]) && is_array($sessionErrors[$name])) {
            $element->setErrors($sessionErrors[$name]);
          }
        }
      }
    }

    $form->restore_language();

    /*Store form with errors in transient for accessibility on redirect.
    Both successful and failed submissions are stored so the form can be properly re-rendered with ARIA attributes.*/
    if (self::$rawData['_AccuaForm_jsuuid'] ) {
      set_transient('accuaformsub_'.self::$rawData['_AccuaForm_jsuuid'], array(
          'buildID' => self::$submittedBuildID,
          'form' => self::$submittedForm,
          'submittedMessages' => self::$submittedMessages,
          'valid' => $valid,
        ), 86400);
    }

    return self::$valid = (bool)$valid;
  }

  public static function getSubmittedData() {
    self::isValid();
    return self::$submittedData;
  }

  public static function getSumbittedID() {
    trigger_error('Use getSubmittedID() instead', (defined('E_USER_DEPRECATED')?E_USER_DEPRECATED:E_USER_NOTICE));
    return self::getSubmittedID();
  }

  public static function getSubmittedID() {
    if (self::isSubmit()) {
      return self::$submittedID;
    } else {
      return null;
    }
  }

  public static function getSubmittedForm() {
    if (self::isSubmit()) {
      return self::$submittedForm;
    } else {
      return null;
    }
  }

  /*This method restores the serialized form instance.*/
  protected static function wp_recover($hash,$iv,$data) {
    /*
    if(!empty($_SESSION["pfbc"][$id]["form"]))
      return unserialize($_SESSION["pfbc"][$id]["form"]);
    */
    /*
    $storename = 'accua_form_' . md5($id);
    if ($stored = get_transient($storename)) {
      return unserialize($stored);
    }
    */

    $keys = get_option('accua_form_api_keys', array());
    @ $hash = base64_decode($hash);
    @ $iv = base64_decode($iv);
    @ $data = base64_decode($data);

    if (!($hash && $iv && $data)) {
      return;
    }

    if (!class_exists('Crypt_Hash')) {
      require_once('phpseclib-crypt/Hash.php');
    }
    $hasher = new Crypt_Hash('sha1');
    $hasher->setKey($keys['hash']);
    @ $hash2 = $hasher->hash($iv.$data);

    if ($hash !== $hash2) {
      return;
    }

    if (!class_exists('Crypt_AES')) {
      require_once('phpseclib-crypt/AES.php');
    }

    $cipher = new Crypt_AES();
    $cipher->setPassword($keys['aes']);
    @ $cipher->setIV($iv);
    @ $data = $cipher->decrypt($data);

    if ($data) {
      @ $form = unserialize($data);
      if ($form) {
        return $form;
      }
    }
  }

  protected function wp_save() {
    /*
    $storename = 'accua_form_' . md5($this->buildID);
    //$serialized = isset($_SESSION["pfbc"][$this->buildID]["form"]) ? $_SESSION["pfbc"][$this->buildID]["form"] : serialize($this);
    $serialized = serialize($this);
    set_transient($storename, $serialized, 2764800); // 32 days
    */

    if (!class_exists('Crypt_Hash')) {
      require_once('phpseclib-crypt/Hash.php');
    }
    if (!class_exists('Crypt_AES')) {
      require_once('phpseclib-crypt/AES.php');
    }

    $keys = get_option('accua_form_api_keys', array());
    if (!(isset($keys['aes']) && isset($keys['hash']))) {
      accua_form_api_install();
      $keys = get_option('accua_form_api_keys', array());
    }

    $data = serialize($this);
    $iv = wp_generate_password(64,true,true);

    $cipher = new Crypt_AES();
    $cipher->setPassword($keys['aes']);
    $cipher->setIV($iv);
    $data = $cipher->encrypt($data);

    $hasher = new Crypt_Hash('sha1');
    $hasher->setKey($keys['hash']);
    $hash = $hasher->hash($iv.$data);

    $ret = array(
      '_AccuaForm_hash' => base64_encode($hash),
      '_AccuaForm_iv' => base64_encode($iv),
      '_AccuaForm_data' => base64_encode($data),
    );
    // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Session data for form values
    if (isset($_SESSION["pfbc"][$this->buildID]["values"])) {
      // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Session data for form values
      $_SESSION["pfbc"][$this->buildID]["values"] = $ret + $_SESSION["pfbc"][$this->buildID]["values"];
    }
    $this->setValues($ret);
    return $ret;
  }

  public function getLocale() {
    return $this->locale;
  }

  public function getLanguage() {
    return $this->language;
  }

  public function addValidateFunction($function_name, $priority = 0) {
    $this->validate_functions[$function_name] = $priority;
  }

  public function addSubmitFunction($function_name, $priority = 0) {
    $this->submit_functions[$function_name] = $priority;
  }

  /**
   * Get submitted messages for a specific form or all forms.
   * @param string|null $formId Optional form ID to get message for specific form
   * @return string|array If $formId provided, returns string message (or empty string). Otherwise returns all messages as array.
   */
  public static function getSubmittedMessages($formId = null){
    if ($formId !== null) {
      return isset(self::$submittedMessages[$formId]) ? self::$submittedMessages[$formId] : '';
    }
    // Backwards compatibility: if old code expects a string, join all messages
    if (is_array(self::$submittedMessages)) {
      return implode('', self::$submittedMessages);
    }
    return self::$submittedMessages;
  }

  /**
   * Set submitted message for a specific form.
   * @param string $msg The message content
   * @param string|null $formId Optional form ID. If null, uses currently submitted form ID.
   * @return string The message that was set
   */
  public static function setSubmittedMessages($msg, $formId = null){
    if ($formId === null) {
      $formId = self::$submittedID ?: '__default__';
    }
    self::$submittedMessages[$formId] = $msg;
    return $msg;
  }

  /**
   * Append to submitted message for a specific form.
   * @param string $msg The message content to append
   * @param string|null $formId Optional form ID. If null, uses currently submitted form ID.
   * @return string The full message after appending
   */
  public static function appendSubmittedmessages($msg, $formId = null){
    if ($formId === null) {
      $formId = self::$submittedID ?: '__default__';
    }
    if (!isset(self::$submittedMessages[$formId])) {
      self::$submittedMessages[$formId] = '';
    }
    self::$submittedMessages[$formId] .= $msg;
    return self::$submittedMessages[$formId];
  }

  protected static function get_anchor_id($id) {
    static $used_anchor_id = array();
    if (substr($id, 0, 14) === '__accua-form__') {
      $id = substr($id, 14);
    }
    $id = preg_replace('/[^a-zA-Z0-9]+/m','_',$id);
    if (empty($used_anchor_id[$id])) {
      $used_anchor_id[$id] = 1;
    } else {
      $used_anchor_id[$id]++;
      $id = $id . '-' . $used_anchor_id[$id];
    }
    return $id;
  }
  public function render($returnHTML = false) {
    $this->wp_save();
    if($returnHTML) {
      ob_start();
    }
    
    parent::render(false);
    if (!empty($this->accua_ajax)) {
      $ajax_url = _accua_forms_json_encode(admin_url('admin-ajax.php?action=accua_form_submit'));
      $submit_fail_message = _accua_forms_json_encode('<li>'.__('Form submission failed. Please try again.', 'contact-forms').'</li>');

      /* translators: %s is the field name/label */
      $required_message_template = _accua_forms_json_encode(__('%s is a required field', 'contact-forms'));
      /* translators: %s is the field name/label */
      $valid_mail_message_template = _accua_forms_json_encode(__('%s: please enter a valid email address', 'contact-forms'));
      /* translators: %s is the field name/label */
      $valid_phone_message_template = _accua_forms_json_encode(__('%s: please enter a valid phone number (e.g. +39 333 1234567)', 'contact-forms'));
      $error_summary_header = _accua_forms_json_encode(__('Check the following fields to continue:', 'contact-forms'));
      $error_type_required = _accua_forms_json_encode(__('required field', 'contact-forms'));
      $error_type_invalid_email = _accua_forms_json_encode(__('invalid email address', 'contact-forms'));
      $error_type_invalid_phone = _accua_forms_json_encode(__('invalid phone number', 'contact-forms'));
      $success_message = _accua_forms_json_encode(__('All fields are valid. Ready to submit!', 'contact-forms'));
      $sending_message = _accua_forms_json_encode(__('Sending...', 'contact-forms'));
      $loading_summary_message = _accua_forms_json_encode(__('Submitting your form, please wait...', 'contact-forms'));
      $js_buildid = preg_replace('/[^a-zA-Z0-9_]/m','_',$this->buildID);
      $formid = $this->getId();
      // $hostname = _accua_forms_json_encode($_SERVER['SERVER_NAME']); - rimosso perché non affidabile con proxy reversi e CDN
      // Extract hostname from ajax_url for proper cross-domain detection
      $ajax_url_parsed = wp_parse_url(admin_url('admin-ajax.php?action=accua_form_submit'));
      $ajax_hostname = _accua_forms_json_encode($ajax_url_parsed['host']);

      $post_url = _accua_forms_json_encode($this->stats['url']);
      $js_ga_track = _accua_forms_json_encode($this->ga_track);

      $anchor_id = _accua_forms_json_encode(self::get_anchor_id($this->formID));

      echo <<<JS
<script type="text/javascript">
<!--
var _handle_ajax_submit_{$js_buildid} = function() {return true;}
var _handle_ajax_submit_complete_{$js_buildid} = function() {return false;}
var _handle_ajax_submit_timeout_{$js_buildid} = function() {return false;}
var _handle_ajax_submit_message_{$js_buildid} = function() {}
var _handle_ajax_submit_response_{$js_buildid} = function() {}

jQuery(function($) {
  var thisform = $("#{$this->buildID}");
  var ajax_enabled = {$ajax_hostname} == location.hostname ;
  var anchor_id = $anchor_id ;

  var response_messages = $("#_response_messages_{$this->buildID}");
  if (! response_messages.length) {
    // Create all three anchors for success, invalid, and error states
    thisform.before('\\x3Ca id="formSubmitSuccess-'+anchor_id+'" name="formSubmitSuccess-'+anchor_id+'" class="accua-form-anchor" /\\x3E');
    thisform.before('\\x3Ca id="formSubmitInvalid-'+anchor_id+'" name="formSubmitInvalid-'+anchor_id+'" class="accua-form-anchor" /\\x3E');
    thisform.before('\\x3Ca id="formSubmitError-'+anchor_id+'" name="formSubmitError-'+anchor_id+'" class="accua-form-anchor" /\\x3E');
    response_messages = $('\\x3Cdiv id="_response_messages_{$this->buildID}" class="accua-form-messages"\\x3E\\x3C/div\\x3E');
    thisform.before(response_messages);
  }
  
  // Smooth scroll helper function
  var smoothScrollToElement = function(elementId) {
    var target = document.getElementById(elementId);
    if (target) {
      target.scrollIntoView({ behavior: 'smooth', block: 'start' });
    }
  };

  var _ajax_submitting_{$js_buildid} = false;
  var timeout_handler = false;
  var timeout_count = 0;
  var fail_count = 0;
  var disabled_fields = false;
  var submitBtn = $('button[type="submit"]', thisform);
  var submitBtnOriginalText = submitBtn.text();
  var submitBtnSendingText = $sending_message;

  var jsuuid_field = $('input[name="_AccuaForm_jsuuid"]', thisform);
  var jsuuid = jsuuid_field.val();
  if (jsuuid == '') {
    var chars = '0123456789abcdefghijklmnopqrstuvwxyz'.split('');
    var radix = chars.length
    for (i = 0; i < 25; i++) {
      jsuuid += chars[0 | Math.random()*radix];
    }
    jsuuid_field.val(jsuuid);
  }

  var ga_track = {$js_ga_track} ;
  var ga_event, ga_submit_event, ga_field_event, ga_field_events_fired = {};
  ga_event = function(eventCategory, eventAction){
    /* matomo */ 
    if (window._mtm && (typeof window._mtm.push == 'function')) {
      window._mtm.push({'event': 'ContactForms', 'eventAction': eventAction, 'eventCategory': eventCategory, 'eventLabel': ga_track.title});
    }
      
    if (typeof window.gtag == 'function') {
      window.gtag('event', eventAction, {'event_category': eventCategory, 'event_label': ga_track.title});
    } else if (window.dataLayer && (typeof window.dataLayer.push == 'function')) {
      dataLayer.push({'event': 'ContactForms', 'eventAction': eventAction, 'eventCategory': eventCategory, 'eventLabel': ga_track.title});
      //backward compatibility
      var gtag = function(){window.dataLayer.push(arguments);}
      gtag('event', eventAction, {'event_category': eventCategory, 'event_label': ga_track.title});
    } else if (typeof window.ga == 'function') {
      window.ga('send', 'event', eventCategory, eventAction, ga_track.title);
    } else if (window._gaq && (typeof window._gaq.push == 'function')) {
      window._gaq.push(['_trackEvent', eventCategory, eventAction, ga_track.title]);
    } else if (window.gaq && (typeof window.gaq.push == 'function')) {
      window.gaq.push(['_trackEvent', eventCategory, eventAction, ga_track.title]);
    } else if (window.pageTracker && (typeof window.pageTracker._trackEvent == 'function')) {
      window.pageTracker._trackEvent(eventCategory, eventAction, ga_track.title);
    }
  }
  ga_submit_event = function(eventAction){
    if (ga_track.track_submit) {
      ga_event('ContactFormsSubmit', eventAction);
    }
    thisform.trigger('ContactFormsSubmit', [eventAction]);
  }
  ga_field_event = function(field_name){
    if (!ga_field_events_fired[field_name]){
      if (ga_track.track_fields) {
        ga_event('ContactFormsFieldFilledIn', field_name);
        ga_field_events_fired[field_name] = true;
      }
      thisform.trigger('ContactFormsFieldFilledIn', [field_name]);
    }
  }
  $('input, textarea, select', thisform).change(function(){
    ga_field_event($(this).attr('name'));
  });
  
  // Disable submit button during form submission
  var disableSubmitButton = function() {
    submitBtn.prop('disabled', true).attr('aria-busy', 'true').text(submitBtnSendingText);
  };
  
  // Re-enable submit button (on failure or completion)
  var enableSubmitButton = function() {
    submitBtn.prop('disabled', false).removeAttr('aria-busy').text(submitBtnOriginalText);
  };
  
  // Get field label from the field's container element
  var getFieldLabel = function(field) {
    var container = field.closest('.pfbc-element');
    if (!container.length) {
      return '';
    }
    
    // Try floating label first (inline labels mode)
    var label = container.find('.pfbc-floating-label').first();
    if (!label.length) {
      // Try standard label in .pfbc-label
      label = container.find('.pfbc-label label').first();
    }
    if (!label.length) {
      // Try any label element
      label = container.find('label').first();
    }
    
    if (label.length) {
      // Get text content, removing the required asterisk
      var text = label.clone().find('.pfbc-required').remove().end().text().trim();
      return text;
    }
    
    return '';
  };
  
  // Build required field error message with field name
  // Checks per-field data-custom-required-msg attribute first, then falls back to global template
  var getRequiredMessage = function(fieldLabel, field) {
    var template = $required_message_template;
    if (field) {
      var custom = field.attr('data-custom-required-msg');
      if (custom) {
        template = custom;
      }
    }
    if (fieldLabel) {
      return template.replace('%s', fieldLabel);
    }
    return template.replace('%s', '');
  };
  
  // Build email validation error message with field name
  // Checks per-field data-custom-format-msg attribute first, then falls back to global template
  var getEmailMessage = function(fieldLabel, field) {
    var template = $valid_mail_message_template;
    if (field) {
      var custom = field.attr('data-custom-format-msg');
      if (custom) {
        template = custom;
      }
    }
    if (fieldLabel) {
      return template.replace('%s', fieldLabel);
    }
    return template.replace('%s', '');
  };
  
  // Build phone validation error message with field name
  // Checks per-field data-custom-format-msg attribute first, then falls back to global template
  var getPhoneMessage = function(fieldLabel, field) {
    var template = $valid_phone_message_template;
    if (field) {
      var custom = field.attr('data-custom-format-msg');
      if (custom) {
        template = custom;
      }
    }
    if (fieldLabel) {
      return template.replace('%s', fieldLabel);
    }
    return template.replace('%s', '');
  };
  
  // Check if a telephone field contains only a country prefix (e.g. "+39")
  // These values should be treated as empty — the user hasn't entered a real number.
  var isTelephonePrefixOnly = function(field) {
    if (!field.hasClass('accuaform-telephone')) return false;
    var val = field.val();
    if (!val) return false;
    val = val.trim();
    if (val === '' || val.charAt(0) !== '+') return false;
    return val.replace(/\D/g, '').length <= 4;
  };
  
  // Scroll to first invalid field, then focus after scroll completes
  var focusFirstInvalidField = function() {
    var firstInvalidContainer = $('#{$this->buildID} .pfbc-element-has-error').first();
    if (!firstInvalidContainer.length) {
      return;
    }
    
    // Try to find a focusable element in the invalid container
    var focusTarget = null;
    
    // Check for radio/checkbox groups first
    var radioOrCheckbox = firstInvalidContainer.find('input[type="radio"], input[type="checkbox"]').first();
    if (radioOrCheckbox.length) {
      focusTarget = radioOrCheckbox;
    }
    
    // Check for file input (focus the dropzone button if available)
    if (!focusTarget) {
      var fileDropzone = firstInvalidContainer.find('.accua-file-dropzone');
      if (fileDropzone.length) {
        focusTarget = fileDropzone;
      } else {
        var fileInput = firstInvalidContainer.find('input[type="file"]');
        if (fileInput.length) {
          focusTarget = fileInput;
        }
      }
    }
    
    // Check for custom select buttons (post select, etc.)
    if (!focusTarget) {
      var customSelectBtn = firstInvalidContainer.find('.pfbc-post-select-trigger, button[aria-haspopup="listbox"]').first();
      if (customSelectBtn.length) {
        focusTarget = customSelectBtn;
      }
    }
    
    // Standard inputs (text, email, select, textarea)
    if (!focusTarget) {
      focusTarget = firstInvalidContainer.find('input:not([type="hidden"]), textarea, select').first();
    }
    
    if (focusTarget && focusTarget.length) {
      // Scroll into view first with smooth animation
      if (focusTarget[0].scrollIntoView) {
        focusTarget[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
      }
      // Focus after scroll animation completes (typical scroll animation is ~300-500ms)
      setTimeout(function() {
        focusTarget.focus();
      }, 500);
    }
  };
  
  // Scroll to a specific field by ID and focus it
  var scrollToFieldAndFocus = function(fieldId) {
    var element = $('#' + fieldId);
    if (!element.length) {
      return;
    }
    
    var focusTarget = element;
    
    // If it's already an input/select/textarea, use it directly
    if (element.is('input, select, textarea')) {
      focusTarget = element;
    }
    // For containers (pfbc-element), find the first focusable element
    else if (element.hasClass('pfbc-element')) {
      var radioOrCheckbox = element.find('input[type="radio"], input[type="checkbox"]').first();
      if (radioOrCheckbox.length) {
        focusTarget = radioOrCheckbox;
      } else {
        var fileDropzone = element.find('.accua-file-dropzone');
        if (fileDropzone.length) {
          focusTarget = fileDropzone;
        } else {
          focusTarget = element.find('input:not([type="hidden"]), textarea, select').first();
        }
      }
    }
    // For file dropzone wrapper, focus the dropzone
    else if (element.find('.accua-file-dropzone').length) {
      focusTarget = element.find('.accua-file-dropzone').first();
    }
    
    if (focusTarget && focusTarget.length) {
      if (focusTarget[0].scrollIntoView) {
        focusTarget[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
      }
      setTimeout(function() {
        focusTarget.focus();
      }, 500);
    }
  };
  
  // Track if form submission has been attempted
  var submitAttempted = false;
  
  // Array to collect field errors for summary
  var fieldErrorsList = [];
  
  // Get the error ID for a radio/checkbox group (DRY helper)
  var getGroupErrorId = function(field) {
    var groupWrapper = field.closest('.pfbc-radio-buttons, .pfbc-checkboxes').parent();
    var groupId = groupWrapper.attr('id') || field.attr('id');
    return groupId + '-error';
  };
  
  // Remove an error element with smooth animation (generic helper for any field type)
  var removeErrorAnimated = function(errorId) {
    var errorEl = $('#' + errorId);
    if (errorEl.length) {
      errorEl.addClass('pfbc-error-removing');
      setTimeout(function() {
        errorEl.remove();
      }, 150); // Match CSS animation duration
    }
  };
  
  // Remove all existing errors for a group with smooth fade-out (handles both potential IDs)
  var removeGroupErrors = function(field, fieldName, animate) {
    var groupContainer = field.closest('.pfbc-element, .pfbc-fieldwrap');
    var errors = groupContainer.find('.pfbc-inline-error');
    
    // Also collect errors by potential IDs
    var groupWrapper = field.closest('.pfbc-radio-buttons, .pfbc-checkboxes').parent();
    var wrapperId = groupWrapper.attr('id');
    if (wrapperId) {
      errors = errors.add($('#' + wrapperId + '-error'));
    }
    var firstField = $("[name='"+fieldName+"']", thisform).first();
    if (firstField.attr('id')) {
      errors = errors.add($('#' + firstField.attr('id') + '-error'));
    }
    
    if (animate && errors.length) {
      // Smooth fade-out animation
      errors.addClass('pfbc-error-removing');
      setTimeout(function() {
        errors.remove();
      }, 150); // Match CSS transition duration
    } else {
      errors.remove();
    }
  };
  
  // Show or update the error/success/loading summary area
  // state: true (success), false (error), 'loading' (submitting)
  var updateSummaryArea = function(state) {
    var summaryArea = $('#{$this->buildID}-validation-summary');
    
    if (!submitAttempted && state !== 'loading') {
      summaryArea.remove();
      return;
    }
    
    // Ensure summary area exists
    if (!summaryArea.length) {
      summaryArea = $('<div id="{$this->buildID}-validation-summary" class="pfbc-validation-summary" role="status" aria-live="polite"></div>');
      thisform.find('.pfbc-error').remove();
      thisform.append(summaryArea);
    }
    
    if (state === 'loading') {
      // Show loading state - neutral blue with spinner and text
      summaryArea
        .removeClass('pfbc-validation-error pfbc-validation-success')
        .addClass('pfbc-validation-loading')
        .attr('role', 'status')
        .attr('aria-live', 'polite')
        .attr('aria-busy', 'true')
        .html('<span class="pfbc-summary-spinner" aria-hidden="true"></span>' + $loading_summary_message);
    } else if (state === true) {
      // Show success state
      summaryArea
        .removeClass('pfbc-validation-error pfbc-validation-loading')
        .addClass('pfbc-validation-success')
        .attr('role', 'status')
        .attr('aria-live', 'polite')
        .removeAttr('aria-busy')
        .html('<span class="pfbc-summary-icon">✓</span> ' + $success_message);
    } else {
      // Show error state with field list
      summaryArea
        .removeClass('pfbc-validation-success pfbc-validation-loading')
        .addClass('pfbc-validation-error')
        .attr('role', 'alert')
        .attr('aria-live', 'assertive')
        .removeAttr('aria-busy');
      
      var html = '<p class="pfbc-summary-header">' + $error_summary_header + '</p><ul class="pfbc-summary-list">';
      for (var i = 0; i < fieldErrorsList.length; i++) {
        var err = fieldErrorsList[i];
        html += '<li><a href="#' + err.fieldId + '" class="pfbc-summary-link" data-field-id="' + err.fieldId + '">' + err.label + '</a> – ' + err.errorType + '</li>';
      }
      html += '</ul>';
      summaryArea.html(html);
      
      // Attach click handlers to links
      summaryArea.find('.pfbc-summary-link').on('click', function(e) {
        e.preventDefault();
        var fieldId = $(this).data('field-id');
        scrollToFieldAndFocus(fieldId);
      });
    }
  };
  
  var show_error_messages = function(message) {
    // Show error message in the validation summary area
    // Used when AJAX submission fails (network error, server error, etc.)
    var summaryArea = thisform.find('.pfbc-validation-summary');
    if (summaryArea.length) {
      summaryArea.html('<div class="pfbc-validation-error" role="alert"><strong>' + accua_forms_i18n.check_fields + '</strong><ul>' + message + '</ul></div>').show();
    }
  }
  
  // Update summary area with server-side errors (called from AJAX error response)
  var updateSummaryWithServerErrors = function(errors, elementErrors) {
    // Collect server errors for the summary
    fieldErrorsList = [];
    
    if (elementErrors) {
      jQuery.each(elementErrors, function(fieldName, fieldErrors) {
        var field = jQuery('[name="' + fieldName + '"]', thisform);
        var fieldId = field.attr('id') || fieldName;
        var fieldLabel = getFieldLabel(field);
        
        // If no label found, try to get from the field container or use field name
        if (!fieldLabel || fieldLabel === fieldName) {
          var container = field.closest('.pfbc-element, .pfbc-fieldwrap');
          fieldLabel = container.find('label').first().text().replace(/\s*\*\s*$/, '').trim();
          if (!fieldLabel) {
            // Fallback: humanize the field name
            fieldLabel = fieldName.replace(/[-_]/g, ' ').replace(/\b\w/g, function(l){ return l.toUpperCase(); });
          }
        }
        
        for (var i = 0; i < fieldErrors.length; i++) {
          fieldErrorsList.push({
            fieldId: fieldId,
            label: fieldLabel,
            errorType: fieldErrors[i]
          });
        }
      });
    }
    
    // Also add any general errors from the errors array
    if (errors && errors.length > 0) {
      for (var i = 0; i < errors.length; i++) {
        // Check if this error is already in fieldErrorsList
        var alreadyAdded = false;
        for (var j = 0; j < fieldErrorsList.length; j++) {
          if (fieldErrorsList[j].errorType === errors[i]) {
            alreadyAdded = true;
            break;
          }
        }
        if (!alreadyAdded) {
          fieldErrorsList.push({
            fieldId: '',
            label: '',
            errorType: errors[i]
          });
        }
      }
    }
    
    // Update the summary area to show errors
    if (fieldErrorsList.length > 0) {
      updateSummaryArea(false);
    }
  }

  _handle_ajax_submit_{$js_buildid} = function() {
    if (_ajax_submitting_{$js_buildid}) {
      return false;
    }

JS;
    $this->error->clear();
    echo <<<JS

    // Mark that submit was attempted
    submitAttempted = true;
    
    var valid_empty = true;
    var valid_mail = true;
    var valid_phone = true;
    var fieldErrors = {};
    
    // Reset field errors list for summary
    fieldErrorsList = [];

    $("#{$this->buildID} .pfbc-element").removeClass('pfbc-invalid');
    $("#{$this->buildID} .pfbc-element").removeClass('pfbc-element-has-error');
    $("#{$this->buildID} input, #{$this->buildID} textarea, #{$this->buildID} select").attr('aria-invalid', 'false');
    $("#{$this->buildID} .pfbc-inline-error").remove();

    var processedGroups = {}; // Track radio/checkbox groups to avoid duplicate errors

    $('.accuaforms-field-required', thisform).each(function(){
      var field = $(this);
      var type = field.attr('type');
      var fieldName = field.attr('name');

      if (type === 'checkbox' || type === 'radio') {
        // Skip if we've already processed this group
        if (processedGroups[fieldName]) {
          return true;
        }
        processedGroups[fieldName] = true;

        if ($("[name='"+fieldName+"']:checked", "#{$this->buildID}").length > 0) {
          return true;
        }

        valid_empty = false;
        
        // Find the container for the radio/checkbox group
        var groupContainer = field.closest('.pfbc-element, .pfbc-fieldwrap');
        groupContainer.addClass('pfbc-invalid pfbc-element-has-error');
        
        // Remove any existing errors before adding new one (prevents duplicates)
        removeGroupErrors(field, fieldName);
        
        // Get consistent error ID using helper
        var errorId = getGroupErrorId(field);
        
        // Find the last radio/checkbox in the group
        var lastInGroup = $("[name='"+fieldName+"']", "#{$this->buildID}").last();
        
        // Apply ARIA attributes to all inputs in the group
        $("[name='"+fieldName+"']", "#{$this->buildID}").attr('aria-invalid', 'true');
        $("[name='"+fieldName+"']", "#{$this->buildID}").attr('aria-describedby', errorId);
        
        // Get field label and build error message
        var fieldLabel = getFieldLabel(field);
        var firstInput = $("[name='"+fieldName+"']", "#{$this->buildID}").first();
        var errorMessage = getRequiredMessage(fieldLabel, firstInput);
        
        // Add to field errors list for summary (use first input ID for focusing)
        fieldErrorsList.push({
          fieldId: firstInput.attr('id'),
          label: fieldLabel,
          errorType: $error_type_required
        });
        
        // Add error message after the last item in the group
        var inlineError = $('<div class="pfbc-inline-error" id="' + errorId + '" role="alert" aria-live="polite"><div class="pfbc-error-message">' + errorMessage + '</div></div>');
        
        // Insert after the last radio/checkbox wrapper
        var lastWrapper = lastInGroup.closest('.pfbc-radio, .pfbc-checkbox');
        if (lastWrapper.length) {
          lastWrapper.after(inlineError);
        } else {
          lastInGroup.after(inlineError);
        }
      } else {
        var val = field.val();
        if (typeof(val) == "string") {
          // Treat "-" and "Select..." as invalid only for dropdowns
          // For telephone fields, prefix-only values (e.g. "+39") are also empty
          var isSelect = field.is('select');
          if (! val.match(/^\s*$/) && (!isSelect || (val !== "Select..." && val !== "-")) && !isTelephonePrefixOnly(field)) {
            return true;
          }
        } else if (Array.isArray(val)) {
          // Multiselect: empty array means no selection
          if (val.length > 0) {
            return true;
          }
        } else if (val) {
          return true;
        }

        valid_empty = false;
        var parent = field.closest('.pfbc-element, .pfbc-fieldwrap');
        parent.addClass('pfbc-invalid pfbc-element-has-error');
        
        // Apply ARIA attributes
        field.attr('aria-invalid', 'true');
        var errorId = field.attr('id') + '-error';
        field.attr('aria-describedby', errorId);
        
        // Get field label and build error message
        var fieldLabel = getFieldLabel(field);
        var errorMessage = getRequiredMessage(fieldLabel, field);
        fieldErrorsList.push({
          fieldId: field.attr('id'),
          label: fieldLabel,
          errorType: $error_type_required
        });
        
        var inlineError = $('<div class="pfbc-inline-error" id="' + errorId + '" role="alert" aria-live="polite"><div class="pfbc-error-message">' + errorMessage + '</div></div>');
        
        // For file inputs with help text, insert error after help text
        var helpText = field.siblings('.pfbc-help').last();
        if (field.is('[type="file"]') && helpText.length) {
          helpText.after(inlineError);
        } else {
          field.after(inlineError);
        }
      }
    });

    $('.pfbc-textbox[type="email"]', thisform).each(function(){
      var field = $(this);

      if (field.val().match(/^\s*$/)) {
        return true;
      }

      if (field.val().match( /^([a-zA-Z0-9_.+%-])+@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9])+$/ )) {
        return true;
      }

      valid_mail = false;
      var parent = field.closest('.pfbc-element, .pfbc-fieldwrap');
      parent.addClass('pfbc-invalid pfbc-element-has-error');
      
      // Apply ARIA attributes
      field.attr('aria-invalid', 'true');
      var errorId = field.attr('id') + '-error';
      field.attr('aria-describedby', errorId);
      
      // Get field label and build error message
      var fieldLabel = getFieldLabel(field);
      var errorMessage = getEmailMessage(fieldLabel, field);
      
      // Add to field errors list for summary (use field ID for focusing)
      fieldErrorsList.push({
        fieldId: field.attr('id'),
        label: fieldLabel,
        errorType: $error_type_invalid_email
      });
      
      var inlineError = $('<div class=\"pfbc-inline-error\" id=\"' + errorId + '\" role=\"alert\" aria-live=\"polite\"><div class=\"pfbc-error-message\">' + errorMessage + '</div></div>');
      
      // For file inputs with help text, insert error after help text
      var helpText = field.siblings('.pfbc-help').last();
      if (field.is('[type=\"file\"]') && helpText.length) {
        helpText.after(inlineError);
      } else {
        field.after(inlineError);
      }

    });

    // Phone validation - runs for all non-empty phone fields
    $('.accuaform-telephone', thisform).each(function(){
        var field = $(this);
        var value = field.val();
        
        // Skip empty fields (Required validation handles mandatory)
        if (!value || value.trim() === '') {
          return true;
        }
        
        // Skip prefix-only values (e.g. "+39") — treated as empty
        var phoneTrimmed = value.trim();
        if (phoneTrimmed.charAt(0) === '+' && phoneTrimmed.replace(/\D/g, '').length <= 4) {
          return true;
        }
        
        // Get country code from data attribute
        var countryCode = field.attr('data-country') || 'IT';
        
        // Use AccuaPhoneValidation if available, otherwise skip validation
        if (typeof window.AccuaPhoneValidation !== 'undefined' && window.AccuaPhoneValidation.isValid) {
          if (window.AccuaPhoneValidation.isValid(value, countryCode)) {
            return true;
          }
        } else {
          // Fallback: basic validation matching server-side Phone.php
          var trimmed = value.trim();
          // Check for invalid characters
          if (!/^[\d\s\-\.\/\(\)\+]+$/.test(trimmed)) {
            // Invalid characters - fail validation
          } else {
            var plusIndex = trimmed.indexOf('+');
            if (plusIndex > 0 || (trimmed.match(/\+/g) || []).length > 1) {
              // Plus in wrong position or multiple plus signs - fail validation
            } else {
              var digitCount = trimmed.replace(/\D/g, '').length;
              // Prefix-only (1-4 digits with +) treated as empty
              if (trimmed.charAt(0) === '+' && digitCount <= 4) {
                return true;
              }
              // Valid if 5-15 digits (matches server-side Phone.php)
              if (digitCount >= 5 && digitCount <= 15) {
                return true;
              }
            }
          }
        }
        
        valid_phone = false;
        var parent = field.closest('.pfbc-element, .pfbc-fieldwrap');
        parent.addClass('pfbc-invalid pfbc-element-has-error');
        
        // Remove any existing blur validation error to avoid duplicates
        $('#' + field.attr('id') + '-phone-error').remove();
        
        // Apply ARIA attributes
        field.attr('aria-invalid', 'true');
        var errorId = field.attr('id') + '-error';
        field.attr('aria-describedby', errorId);
        
        // Get field label and build error message
        var fieldLabel = getFieldLabel(field);
        var errorMessage = getPhoneMessage(fieldLabel, field);
        
        // Add to field errors list for summary
        fieldErrorsList.push({
          fieldId: field.attr('id'),
          label: fieldLabel,
          errorType: $error_type_invalid_phone
        });
        
        var inlineError = $('<div class="pfbc-inline-error pfbc-phone-format-error" id="' + errorId + '" role="alert" aria-live="polite"><div class="pfbc-error-message">' + errorMessage + '</div></div>');
        
        var helpText = field.siblings('.pfbc-help').last();
        if (helpText.length) {
          helpText.after(inlineError);
        } else {
          field.after(inlineError);
        }
      });

    if (valid_empty && valid_mail && valid_phone) {
      // Show loading state in summary during AJAX submission
      updateSummaryArea('loading');
      
      _ajax_submitting_{$js_buildid} = true;
      disableSubmitButton();
      $('input[name="_AccuaForm_tentatives"]', thisform).val(fail_count);
      disabled_fields = $("input, textarea, button, select", thisform).not('[type="submit"]').not(':disabled');
      disabled_fields.attr('readonly','readonly');
      timeout_count = 0;
      if (ajax_enabled) {
        $("#submit_target_{$js_buildid}").attr('src','').removeAttr('src');
        timeout_handler = setTimeout(_handle_ajax_submit_timeout_{$js_buildid}, 5000);
      }
      return true;
    } else {
      ga_submit_event('formSubmitInvalid');
      
      // Update URL hash to reflect invalid state (for GA tracking and bookmarkability)
      if (history.replaceState) {
        history.replaceState(null, '', '#formSubmitInvalid-'+anchor_id);
      }
      
      // Update summary area with error list
      updateSummaryArea(false);
      
      // Focus on first invalid field for accessibility
      focusFirstInvalidField();
      
      return false;
    }
  }


  _handle_ajax_submit_timeout_{$js_buildid} = function() {
    if (_ajax_submitting_{$js_buildid}) {
      if (timeout_count < 60) {
        timeout_count++;
        timeout_handler = setTimeout(_handle_ajax_submit_timeout_{$js_buildid}, 500);
        _handle_ajax_submit_complete_{$js_buildid}();
      } else {
        timeout_handler = false;
        _handle_ajax_submit_complete_{$js_buildid}();
        if (_ajax_submitting_{$js_buildid}) {
          _handle_ajax_submit_response_{$js_buildid}(false);
        }
      }
    }
  }

  _handle_ajax_submit_complete_{$js_buildid} = function() {
    if (_ajax_submitting_{$js_buildid}) {
      var response = false;
      try {
        var responsedoc = frames['submit_target_{$js_buildid}'].document;
        if (responsedoc.getElementById("accua-form-ajax-response-loaded")) {
          response = $.parseJSON(responsedoc.getElementById("accua-form-ajax-response").innerHTML);
        }
      } catch (err) {
        response = false;
      }
      if (response) {
        return _handle_ajax_submit_response_{$js_buildid} (response);
      }
    }
  }

  _handle_ajax_submit_message_{$js_buildid} = function(message) {
    if (_ajax_submitting_{$js_buildid}) {
      var response = false;
      try {
        response = $.parseJSON(message.data);
        // Accept response if jsuuid matches AND buildID matches or is null (server rejection)
        if (response.jsuuid != jsuuid || (response.buildID != null && response.buildID != "{$this->buildID}")) {
          response = false;
        }
      } catch (err) {
        response = false;
      }
      if (response) {
        return _handle_ajax_submit_response_{$js_buildid} (response);
      }
    }
  }

  _handle_ajax_submit_response_{$js_buildid} = function(response) {
    if (_ajax_submitting_{$js_buildid}) {
      if(response && typeof(response) == "object" && typeof(response.submitted) == "boolean") {
        // Only show message container if there's actual content
        if (response.messages && response.messages.trim() !== '') {
          response_messages.html(response.messages).show();
        } else {
          response_messages.empty().hide();
        }
        if (response.submitted) {
          if (response.valid) {
            var gads_track_code = "{$this->gads_conversion_tracking_code}";
            if(gads_track_code != ''){            
              gtag('event', 'conversion', {'send_to': gads_track_code});
            }
            
            ga_submit_event('formSubmitSuccess');
            smoothScrollToElement('formSubmitSuccess-'+anchor_id);
            
            
JS;
            /*A callback function can be specified to handle any post submission events.*/
            if(!empty($this->ajaxCallback)) {
              echo $this->ajaxCallback, "(response);";
            } else {
              echo "$('#{$this->buildID}').hide();";
            }
            echo <<<JS
          } else {
            ga_submit_event('formSubmitInvalid');
            smoothScrollToElement('formSubmitInvalid-'+anchor_id);
JS;
            if (method_exists($this->error,'applyAjaxErrorResponseUsingShowErrorMessages')) {
              $this->error->applyAjaxErrorResponseUsingShowErrorMessages();
            } else {
              $this->error->applyAjaxErrorResponse();
            }
            echo <<<JS

            for (var name in response.files) {
              $(".pfbc-fieldwrap:has(input[type='file'][name='"+name+"'])", thisform).html(response.files[name]);
            }

            $("input[name='_AccuaForm_hash']",thisform).val(response._AccuaForm_hash);
            $("input[name='_AccuaForm_iv']",  thisform).val(response._AccuaForm_iv);
            $("input[name='_AccuaForm_data']",thisform).val(response._AccuaForm_data);

            disabled_fields.removeAttr('readonly');
            enableSubmitButton();
          }
        } else {
          // Server did not recognize the form submission (e.g. expired nonce, stale cached page)
          ga_submit_event('formSubmitError');
          smoothScrollToElement('formSubmitError-'+anchor_id);
          fail_count++;
          if (fail_count > 2) {
            ajax_enabled = false;
            thisform.attr("action", {$post_url} );
            thisform.removeAttr("target");
            $('input[name="_AccuaForm_submit_method"]', thisform).val('fallback');
          }
          show_error_messages( $submit_fail_message );
          disabled_fields.removeAttr('readonly');
          enableSubmitButton();
        }
      } else {
        ga_submit_event('formSubmitError');
        smoothScrollToElement('formSubmitError-'+anchor_id);
        fail_count++;
        if (fail_count > 2) {
          ajax_enabled = false;
          thisform.attr("action", {$post_url} );
          thisform.removeAttr("target");
          $('input[name="_AccuaForm_submit_method"]', thisform).val('fallback');
        }
        show_error_messages( $submit_fail_message );
        enableSubmitButton();
      }
      $('.accua_forms_show_recaptcha_button', thisform).click();
      if (((typeof accuaform_recaptcha2_initialized) != 'undefined') && accuaform_recaptcha2_initialized) {
        $('.accua_forms_recaptcha2_container', thisform).each(function(){
          accua_forms_reload_recaptcha2($(this).attr('id'));
        });
      }
      _ajax_submitting_{$js_buildid} = false;
      if (timeout_handler) {
        clearTimeout(timeout_handler);
        timeout_handler = false;
      }
    }
  }

  if (ajax_enabled) {
    thisform.attr("action", {$ajax_url} );
    thisform.attr("target","submit_target_{$js_buildid}");
    try {
      window.addEventListener('message', _handle_ajax_submit_message_{$js_buildid}, false);
    } catch (e) { }
    $('input[name="_AccuaForm_submit_method"]', thisform).val('iframe');
  } else {
    thisform.attr("action", {$post_url} );
  }
  thisform.attr("onsubmit","return _handle_ajax_submit_{$js_buildid}()");
  
  // Real-time validation for better UX - use change only for checkbox/radio to avoid double-firing
  $('.accuaforms-field-required', thisform).on('change', function() {
    var field = $(this);
    var type = field.attr('type');
    if (type !== 'checkbox' && type !== 'radio') return; // Only handle checkbox/radio on change
    
    var fieldName = field.attr('name');
    var isChecked = $("[name='"+fieldName+"']:checked", thisform).length > 0;
    var groupContainer = field.closest('.pfbc-element, .pfbc-fieldwrap');
    var hasError = groupContainer.hasClass('pfbc-element-has-error');
    
    // Only act if state actually changed to avoid flashing
    if (isChecked && hasError) {
      // Valid now - remove error with animation
      groupContainer.removeClass('pfbc-invalid pfbc-element-has-error');
      $("[name='"+fieldName+"']", thisform).attr('aria-invalid', 'false');
      $("[name='"+fieldName+"']", thisform).removeAttr('aria-describedby');
      removeGroupErrors(field, fieldName, true); // animate=true
    } else if (!isChecked && !hasError && submitAttempted) {
      // Invalid now and we've attempted submit - show error
      var errorId = getGroupErrorId(field);
      groupContainer.addClass('pfbc-invalid pfbc-element-has-error');
      $("[name='"+fieldName+"']", thisform).attr('aria-invalid', 'true');
      $("[name='"+fieldName+"']", thisform).attr('aria-describedby', errorId);
      
      var fieldLabel = getFieldLabel(groupContainer);
      var firstField = $("[name='"+fieldName+"']", thisform).first();
      var errorMessage = getRequiredMessage(fieldLabel, firstField);
      var inlineError = $('<div class="pfbc-inline-error" id="' + errorId + '" role="alert" aria-live="polite"><div class="pfbc-error-message">' + errorMessage + '</div></div>');
      
      var lastInGroup = $("[name='"+fieldName+"']", thisform).last();
      var lastWrapper = lastInGroup.closest('.pfbc-radio, .pfbc-checkbox');
      if (lastWrapper.length) {
        lastWrapper.after(inlineError);
      } else {
        lastInGroup.after(inlineError);
      }
    }
  });
  
  // Blur handler for text-like fields only
  $('.accuaforms-field-required', thisform).on('blur', function() {
    var field = $(this);
    var type = field.attr('type');
    var fieldName = field.attr('name');
    var isEmpty = false;
    
    // Skip checkbox/radio - handled by change event above
    if (type === 'checkbox' || type === 'radio') return;
    
    var val = field.val();
      if (typeof(val) == "string") {
        // Treat "-" and "Select..." as invalid only for dropdowns
        // For telephone fields, prefix-only values (e.g. "+39") are also empty
        var isSelect = field.is('select');
        isEmpty = val.match(/^\s*$/) || (isSelect && (val === "Select..." || val === "-")) || isTelephonePrefixOnly(field);
      } else if (typeof(val) == "object") {
        isEmpty = !val || val.length === 0;
      } else {
        isEmpty = !val;
      }
      
      var parent = field.closest('.pfbc-element, .pfbc-fieldwrap');
      var errorId = field.attr('id') + '-error';
      
      if (isEmpty) {
        // For telephone: phone-validation.js blur handler runs AFTER this one and may
        // still have pfbc-element-has-error set from a previous format error. Check for
        // the specific required error div instead of the parent class to avoid skipping.
        var alreadyHasError = field.hasClass('accuaform-telephone')
          ? $('#' + errorId).length > 0
          : parent.hasClass('pfbc-element-has-error');
        if (!alreadyHasError) {
          // For telephone: remove leftover phone format error since the field is now
          // empty (required error takes priority). phone-validation.js will also clean
          // up on its blur, but this handler runs first.
          if (field.hasClass('accuaform-telephone')) {
            $('#' + field.attr('id') + '-phone-error').remove();
          }
          parent.addClass('pfbc-invalid pfbc-element-has-error');
          field.attr('aria-invalid', 'true');
          field.attr('aria-describedby', errorId);
          
          // Get field label for error message
          var fieldLabel = getFieldLabel(parent);
          var errorMessage = getRequiredMessage(fieldLabel, field);
          var inlineError = $('<div class="pfbc-inline-error" id="' + errorId + '" role="alert" aria-live="polite"><div class="pfbc-error-message">' + errorMessage + '</div></div>');
          
          // For file inputs with help text, insert error after help text
          var helpText = field.siblings('.pfbc-help').last();
          if (field.is('[type="file"]') && helpText.length) {
            helpText.after(inlineError);
          } else {
            field.after(inlineError);
          }
        }
      } else {
        // Field is not empty — clear required-related errors.
        if (field.hasClass('accuaform-telephone')) {
          // For telephone fields: clear only the required error ({id}-error).
          // phone-validation.js has already run on this same blur event and set the
          // correct error state (phone-error or clean). We must not undo its work.
          // Only remove the required-error div; preserve phone-validation.js state.
          $('#' + errorId).remove();
          // If phone-validation.js left no errors, clear the container state too
          if (!parent.find('.pfbc-inline-error').length) {
            parent.removeClass('pfbc-invalid pfbc-element-has-error');
            field.attr('aria-invalid', 'false');
            field.removeAttr('aria-describedby');
          }
        } else {
          // For non-telephone fields: animated removal for smoother UX
          removeErrorAnimated(errorId);
          parent.removeClass('pfbc-invalid pfbc-element-has-error');
          field.attr('aria-invalid', 'false');
          field.removeAttr('aria-describedby');
        }
      }
  });
  
  $('.pfbc-textbox[type="email"]', thisform).on('blur change', function() {
    var field = $(this);
    var val = field.val();
    
    if (val.match(/^\s*$/)) {
      return; // Empty is handled by required validation
    }
    
    var isValid = val.match(/^([a-zA-Z0-9_.+%-])+@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9])+$/);
    var parent = field.closest('.pfbc-element, .pfbc-fieldwrap');
    var errorId = field.attr('id') + '-error';
    
    if (!isValid) {
      if (!$('#' + errorId + ':not(.pfbc-error-removing)').length) {
        $('#' + errorId).remove();
        parent.addClass('pfbc-invalid pfbc-element-has-error');
        field.attr('aria-invalid', 'true');
        field.attr('aria-describedby', errorId);
        
        // Get field label for error message
        var fieldLabel = getFieldLabel(parent);
        var errorMessage = getEmailMessage(fieldLabel, field);
        var inlineError = $('<div class=\"pfbc-inline-error\" id=\"' + errorId + '\" role=\"alert\" aria-live=\"polite\"><div class=\"pfbc-error-message\">' + errorMessage + '</div></div>');
        
        // For file inputs with help text, insert error after help text
        var helpText = field.siblings('.pfbc-help').last();
        if (field.is('[type=\"file\"]') && helpText.length) {
          helpText.after(inlineError);
        } else {
          field.after(inlineError);
        }
      }
    } else {
      parent.removeClass('pfbc-invalid pfbc-element-has-error');
      field.attr('aria-invalid', 'false');
      field.removeAttr('aria-describedby');
      removeErrorAnimated(errorId);
    }
  });
});
// -->
</script>
<iframe id="submit_target_{$js_buildid}" title="Notification Message" name="submit_target_{$js_buildid}" onload="_handle_ajax_submit_complete_{$js_buildid}()" onerror="_handle_ajax_submit_complete_{$js_buildid}()" style="width:0;height:0;border:0px solid #fff"></iframe>
JS;
    }

    echo <<<JSREFERRER
<script type="text/javascript">
<!--
jQuery(function($){
  var referrerfield = $("#{$this->buildID} input[name='_AccuaForm_referrer']");
  if (referrerfield.val() == '') {
    referrerfield.val(document.referrer);
  }
  $("#{$this->buildID} input[name='_AccuaForm_user_agent']").val(navigator.userAgent);
  $("#{$this->buildID} input[name='_AccuaForm_platform']").val(navigator.platform);
});
// -->
</script>
JSREFERRER;

    if($returnHTML) {
      $html = ob_get_contents();
      ob_end_clean();
      return $html;
    }
  }

  protected function renderJS() {
    $this->renderJSFiles();

    echo <<<JS
<script type="text/javascript">
<!-- 

JS;
    $this->view->renderJS();
    foreach($this->elements as $element)
      $element->renderJS();

    $id = $this->attributes["id"];

    echo 'jQuery(document).ready(function() {';
    /*jQuery is used to set the focus of the form's initial element.*/
    if(!in_array("focus", $this->prevent))
      echo 'jQuery("#', $id, ' :input:visible:enabled:first").focus();';
    
    // Accessibility: Focus management for validation errors on page load
    echo <<<JS
    
    // If there are errors on page load, focus the error summary or first invalid field
    if (jQuery('.pfbc-error', '#{$id}').length > 0) {
      setTimeout(function() {
        var errorContainer = jQuery('.pfbc-error', '#{$id}').first();
        errorContainer.attr('tabindex', '-1').focus();
      }, 100);
    } else if (jQuery('.pfbc-element-has-error', '#{$id}').length > 0) {
      setTimeout(function() {
        var firstInvalidField = jQuery('.pfbc-element-has-error :input:visible:enabled:first', '#{$id}').first();
        if (firstInvalidField.length) {
          firstInvalidField.focus();
        }
      }, 100);
    }
    
JS;

    $this->view->jQueryDocumentReady();
    foreach($this->elements as $element) {
      $element->jQueryDocumentReady();
    }

    /*For ajax, an anonymous onsubmit javascript function is bound to the form using jQuery.  jQuery's
     serialize function is used to grab each element's name/value pair.* /
    if(!empty($this->ajax)) {
      echo 'jQuery("#', $id, '").bind("submit", function() {';
      $this->error->clear();
      echo <<<JS
			jQuery.ajax({
				url: "{$this->attributes["action"]}",
				type: "{$this->attributes["method"]}",
				data: jQuery("#$id").serialize(),
				success: function(response) {
					if(response != undefined && typeof response == "object" && response.errors) {
JS;
      $this->error->applyAjaxErrorResponse();
      echo <<<JS
						jQuery("html, body").animate({ scrollTop: jQuery("#$id").offset().top }, 500 );
					}
					else {
JS;
      /*A callback function can be specified to handle any post submission events.* /
      if(!empty($this->ajaxCallback))
        echo $this->ajaxCallback, "(response);";
      echo <<<JS
					}
				}
			});
			return false;
		});

JS;
    }
  */
    echo <<<JS
	});
// -->
</script>
JS;

    // Add JavaScript for inline label floating behavior
    if (strpos($this->attributes['class'], 'accua-form-view-inlinelabel') !== false) {
      echo <<<INLINELABELJS
<script type="text/javascript">
<!--
jQuery(function($) {
  var form = $('#{$this->attributes["id"]}');
  
  /**
   * Inline Label Floating Behavior
   * 
   * Handles the Material Design floating label animation:
   * - Floats label up when field is focused
   * - Keeps label up when field has value
   * - Returns label to inline position when field is empty and unfocused
   * 
   * Accessibility features:
   * - Maintains proper ARIA relationships
   * - Works with keyboard navigation
   * - Compatible with screen readers
   * - Supports autofill detection
   */
  
  // Function to check if field has value
  function hasValue(field) {
    var val = field.val();
    // For select elements, check if selected value is not empty
    if (field.is('select')) {
      return val !== null && val !== '' && val !== undefined;
    }
    // For date inputs, check if value is set (format: YYYY-MM-DD)
    if (field.attr('type') === 'date') {
      return val !== null && val !== '' && val !== undefined;
    }
    // For text inputs and textareas
    return val !== null && val !== '' && val.trim() !== '';
  }
  
  // Function to update wrapper state
  function updateWrapperState(wrapper) {
    var field = wrapper.find('.pfbc-textbox, .pfbc-textarea, .pfbc-select').first();
    var postSelectWrapper = wrapper.find('.pfbc-post-select-wrapper');
    
    // Check if this is a post-select field
    if (postSelectWrapper.length && postSelectWrapper.attr('data-enhanced') === 'true') {
      // For enhanced post-select, check the hidden native select for value
      var nativeSelect = postSelectWrapper.find('select');
      var trigger = postSelectWrapper.find('.pfbc-post-select-trigger');
      var container = postSelectWrapper.find('.pfbc-post-select-container');
      
      var isFocused = trigger.is(':focus') || container.hasClass('open');
      var fieldHasValue = nativeSelect.length && hasValue(nativeSelect);
      
      wrapper.toggleClass('is-focused', isFocused);
      wrapper.toggleClass('has-value', fieldHasValue);
      return;
    }
    
    if (!field.length) return;
    
    var isFocused = field.is(':focus');
    var fieldHasValue = hasValue(field);
    
    // Update wrapper classes
    wrapper.toggleClass('is-focused', isFocused);
    wrapper.toggleClass('has-value', fieldHasValue);
    
    // For date inputs, add class directly to field for Firefox CSS support
    if (field.attr('type') === 'date') {
      field.toggleClass('has-value', fieldHasValue);
    }
    
    // Accessibility: Update ARIA state
    var label = wrapper.find('.pfbc-floating-label');
    if (label.length) {
      // Ensure label is always associated with field
      var fieldId = field.attr('id');
      if (fieldId && !field.attr('aria-labelledby')) {
        // Label is already associated via for/id
        // Additional ARIA not needed, but we ensure proper semantics
      }
    }
  }
  
  // Initialize all inline label wrappers
  form.find('.pfbc-inline-label-wrapper').each(function() {
    var wrapper = $(this);
    updateWrapperState(wrapper);
  });
  
  // Handle focus events
  form.on('focus', '.pfbc-inline-label-wrapper .pfbc-textbox, .pfbc-inline-label-wrapper .pfbc-textarea, .pfbc-inline-label-wrapper .pfbc-select', function() {
    var wrapper = $(this).closest('.pfbc-inline-label-wrapper');
    updateWrapperState(wrapper);
  });
  
  // Handle blur events
  form.on('blur', '.pfbc-inline-label-wrapper .pfbc-textbox, .pfbc-inline-label-wrapper .pfbc-textarea, .pfbc-inline-label-wrapper .pfbc-select', function() {
    var wrapper = $(this).closest('.pfbc-inline-label-wrapper');
    // Small delay to allow value to be set
    setTimeout(function() {
      updateWrapperState(wrapper);
    }, 10);
  });
  
  // Handle input/change events to detect value changes
  form.on('input change', '.pfbc-inline-label-wrapper .pfbc-textbox, .pfbc-inline-label-wrapper .pfbc-textarea, .pfbc-inline-label-wrapper .pfbc-select', function() {
    var wrapper = $(this).closest('.pfbc-inline-label-wrapper');
    updateWrapperState(wrapper);
  });
  
  // Handle post-select trigger focus/blur events
  form.on('focus', '.pfbc-inline-label-wrapper .pfbc-post-select-trigger', function() {
    var wrapper = $(this).closest('.pfbc-inline-label-wrapper');
    updateWrapperState(wrapper);
  });
  
  form.on('blur', '.pfbc-inline-label-wrapper .pfbc-post-select-trigger', function() {
    var wrapper = $(this).closest('.pfbc-inline-label-wrapper');
    setTimeout(function() {
      updateWrapperState(wrapper);
    }, 50);
  });
  
  // Handle post-select value changes (native select change event)
  form.on('change', '.pfbc-inline-label-wrapper .pfbc-post-select-wrapper select', function() {
    var wrapper = $(this).closest('.pfbc-inline-label-wrapper');
    updateWrapperState(wrapper);
  });
  
  // Observe post-select container for open/close state changes
  if (window.MutationObserver) {
    form.find('.pfbc-inline-label-wrapper .pfbc-post-select-container').each(function() {
      var container = this;
      var wrapper = $(container).closest('.pfbc-inline-label-wrapper');
      var containerObserver = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
          if (mutation.attributeName === 'class') {
            updateWrapperState(wrapper);
          }
        });
      });
      containerObserver.observe(container, {
        attributes: true,
        attributeFilter: ['class']
      });
    });
  }
  
  // Handle browser autofill (multiple browser support)
  // Chrome/Safari autofill detection
  if (window.MutationObserver) {
    var observer = new MutationObserver(function(mutations) {
      mutations.forEach(function(mutation) {
        if (mutation.attributeName === 'value' || mutation.attributeName === 'class') {
          var target = $(mutation.target);
          if (target.hasClass('pfbc-textbox') || target.hasClass('pfbc-textarea') || target.hasClass('pfbc-select')) {
            var wrapper = target.closest('.pfbc-inline-label-wrapper');
            if (wrapper.length) {
              updateWrapperState(wrapper);
            }
          }
        }
      });
    });
    
    form.find('.pfbc-inline-label-wrapper .pfbc-textbox, .pfbc-inline-label-wrapper .pfbc-textarea, .pfbc-inline-label-wrapper .pfbc-select').each(function() {
      observer.observe(this, {
        attributes: true,
        attributeFilter: ['value', 'class']
      });
    });
  }
  
  // Fallback autofill detection with animation frame checking
  setTimeout(function() {
    form.find('.pfbc-inline-label-wrapper').each(function() {
      updateWrapperState($(this));
    });
  }, 100);
  
  // Additional check for autofill after a short delay
  setTimeout(function() {
    form.find('.pfbc-inline-label-wrapper').each(function() {
      updateWrapperState($(this));
    });
  }, 500);
});
// -->
</script>
INLINELABELJS;
    }
  }

  protected function renderCSS() {

  }

  public function getAjax() {
    return $this->accua_ajax;
  }

  public function getFile($fieldname) {
    if (isset($this->files[$fieldname])) {
      return $this->files[$fieldname];
    }
  }

  public function renameFile($fieldname, $newname) {
    if (isset($this->files[$fieldname])) {
      $file = $this->files[$fieldname];
      @ $renamed = rename($file['dest_path'].$file['tmp_name'], $file['dest_path'].$newname);
      if ($renamed) {
        $this->files[$fieldname]['new_name'] = $newname;
        return true;
      }
    }
    return false;
  }

  public static function renderAjaxErrorResponse($unused = 'pfbc') {
    if ($form = self::$submittedForm) {
      $form->error->setForm($form);
      return $form->error->renderAjaxErrorResponse();
    }
  }

  public static function getAjaxErrorResponse() {
    if ($form = self::$submittedForm) {
      $form->error->setForm($form);
      return $form->error->getAjaxErrorResponse();
    }
  }

  public function setFormError($errors, $element = '') {
    return self::setError($this->buildID, $errors, $element);
  }

  public function setClass($class) {
    if(!empty($this->attributes["class"]))
      $this->attributes["class"] .= " " . $class;
    else
      $this->attributes["class"] = $class;
  }

  public static function ajaxSubmit() {
    $ret = array(
        'valid' => false,
        'jsuuid' => self::$rawData['_AccuaForm_jsuuid'],
        'buildID' => self::$submittedBuildID,
        'files' => array(),
    );
    if ($ret['submitted'] = self::isSubmit()){
      if ($ret['valid'] = self::isValid()) {

      } else {
        $errorResponse = self::getAjaxErrorResponse();
        // Support modern error format with both flat list and structured data for ARIA
        if (is_array($errorResponse) && isset($errorResponse['errors'])) {
          $ret = array_merge($ret, $errorResponse);
        } else {
          // Backwards compatibility: old format returned just the flat array
          $ret['errors'] = $errorResponse;
        }
      }
      $form = self::$submittedForm;
      foreach ($form->files as $fieldname => $file) {
        if (!empty($file['name'])) {
          $ret['files'][$fieldname] = $form->getElementByName($fieldname)->getAlreadySubmittedText();
        }
      }
      $ret += $form->wp_save();
      // Get messages AFTER isValid() and wp_save() have completed
      // This ensures email sending results are captured in the messages
      $ret['messages'] = self::getSubmittedMessages(self::$submittedID);
    } else {
      // Form not submitted yet - no messages to show
      $ret['messages'] = '';
    }
    return $ret;
  }
}

```
