Jake Vanderwerf
2026-05-13 226b50642af0895948fbaa623a9b7180399a63b6
inc/rest/routes/FormRoutes.php
@@ -1,12 +1,14 @@
<?php
namespace JVBase\rest\routes;
use JVBase\rest\RestRouteManager;
use JVBase\meta\Sanitizer;
use JVBase\meta\Validator;
use JVBase\rest\PermissionHandler;
use JVBase\rest\Rest;
use JVBase\managers\Cache;
use JVBase\meta\MetaManager;
use JVBase\managers\CloudflareTurnstile;
use JVBase\blocks\FormBlock;
use JVBase\utility\Features;
use JVBase\rest\Route;
use JVBase\base\Site;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
@@ -21,18 +23,15 @@
 *
 * Handles REST API endpoints for form submissions
 */
class FormRoutes extends RestRouteManager
class FormRoutes extends Rest
{
   protected Cache $cache;
   protected FormBlock $form_block;
   protected CloudflareTurnstile|null $turnstile;
   public function __construct()
   {
      $this->cacheName = 'forms';
      $this->cacheTtl = HOUR_IN_SECONDS;
      parent::__construct();
      $this->action = 'form-';
      $this->cache = Cache::for('forms', HOUR_IN_SECONDS);
      // Add query vars
      add_filter('query_vars', [$this, 'addQueryVars']);
@@ -45,35 +44,29 @@
    */
   public function registerRoutes(): void
   {
      // Form submission endpoint
      register_rest_route($this->namespace, '/forms', [
         [
            'methods' => 'POST',
            'callback' => [$this, 'submitForm'],
            'permission_callback' => [$this, 'checkRateLimit'], // Public endpoint, rate limited
         ],
         [
            'methods' => 'GET',
            'callback' => [$this, 'getForms'],
            'permission_callback' => [$this, 'checkPermission']
         ]
      ]);
      // ['actionNonce'=>'dash-']
      Route::for('forms')
         ->post([$this, 'submitForm'])
         ->args([
            'form_type' => 'string|required',
            'form_id' => 'string|required',
            'timestamp' => 'string',
            'cf-turnstile-response' => 'string',
         ])
         ->auth('public')
         ->rateLimit(5) // 5 submissions per minute
         ->get([$this, 'getForms'])
         ->auth(PermissionHandler::combine(['logged_in', ['actionNonce'=>'dash-']]))
         ->rateLimit(30)
         ->register();
      // Get specific form configuration
      register_rest_route($this->namespace, '/forms/(?P<form_type>[a-zA-Z0-9_-]+)', [
         [
            'methods' => 'GET',
            'callback' => [$this, 'getForm'],
            'permission_callback' => [$this, 'checkPermission'],
            'args' => [
               'form_type' => [
                  'required' => true,
                  'type' => 'string',
                  'sanitize_callback' => 'sanitize_text_field'
               ]
            ]
         ]
      ]);
      Route::for(Route::pattern('forms/{form_type}'))
         ->get([$this, 'getForm'])
         ->arg('form_type', 'string|required')
         ->auth('logged_in')
         ->rateLimit(30)
         ->register();
   }
   /**
@@ -100,31 +93,35 @@
         $result = $this->handleFormSubmission($form_type, $form_id, $form_data, $files);
         if (is_wp_error($result)) {
            return new WP_REST_Response([
               'success'   => false,
               'message'   => $result->get_error_message()
            ]);
            return $this->error(
               $result->get_error_message(),
               $result->get_error_code(),
               400
            );
         }
         if (array_key_exists('success', $result)){
            return new WP_REST_Response($result);
            return $this->validationError($result);
         }
         return new WP_REST_Response([
            'success' => true,
            'data'   => $result
         ], 200);
         return $this->success($result);
      } catch (Exception $e) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'An error occurred while processing your submission.'
         ], 500);
         $this->logError('Form submission error', [
            'message' => $e->getMessage(),
            'trace' => $e->getTraceAsString()
         ]);
         return $this->error(
            'An error occurred while processing your submission.',
            'submission_error',
            500
         );
      }
   }
   protected function verifyTurnstile(string $token): bool
   {
      if (!Features::hasIntegration('cloudflare') || !JVB()->connect('cloudflare')->isSetUp()) {
      if (!Site::hasIntegration('cloudflare') || !JVB()->connect('cloudflare')->isSetUp()) {
         return true;
      }
@@ -168,7 +165,6 @@
      } catch (\Exception $e) {
         return new WP_Error('validation_failed', 'Data validation error: ' . $e->getMessage());
      }
      if (array_key_exists('success', $processed_data) && $processed_data['success'] === false) {
         return $processed_data;
      }
@@ -201,7 +197,9 @@
    */
   protected function validateAndSanitizeData(array $form_config, array $form_data): array|WP_REST_Response
   {
      $meta = new MetaManager(null, 'form');
      $validator = new Validator();
      $sanitizer = new Sanitizer();
      $processed_data = [];
      $errors = [];
@@ -242,7 +240,7 @@
         $field_config['name'] = $field_name;
         // Validate field
         if (!$meta->validator->validate($value, $field_config)) {
         if (!$validator->validate($value, $field_config)) {
            $label = $field_config['label'] ?? ucfirst(str_replace('_', ' ', $field_name));
            $errors['errors'][$field_name] = [
               'message' => sprintf('Field "%s" contains invalid data.', $label)
@@ -251,7 +249,7 @@
         }
         // Sanitize field
         $processed_data[$field_name] = $meta->sanitizer->sanitize($value, $field_config);
         $processed_data[$field_name] = $sanitizer->sanitize($value, $field_config);
      }
      if (!empty($errors)) {
@@ -281,16 +279,19 @@
         $submitter_name = $form_data['name'];
      }
      if (!array_key_exists('preheader', $form_config)) {
      if (array_key_exists('preheader', $form_config)) {
         $preheader = $form_config['preheader'];
      } else {
         $submitter_name = $submitter_name?:'website visitor';
         $preheader = sprintf(
            'New %s submission from %s',
            $form_config['title'],
            $submitter_name ?: ($submitter_name ?: 'website visitor')
            $submitter_name
         );
      }
      $subject .= ' ' . $submitter_name;
      // Email headers
      $headers = [];
@@ -712,7 +713,7 @@
         ];
      }
      return new WP_REST_Response($public_forms, 200);
      return $this->success($public_forms);
   }
   /**
@@ -720,18 +721,16 @@
    */
   public function getForm(WP_REST_Request $request): WP_REST_Response
   {
      $form_type = $request->get_param('form_type');
      $form_type = sanitize_text_field($request->get_param('form_type'));
      $form_config = FormBlock::getForm($form_type);
      if (!$form_config) {
         return new WP_REST_Response([
            'error' => 'Form not found'
         ], 404);
         return $this->notFound('Form not found');
      }
      // Remove sensitive data
      unset($form_config['email_to']);
      return new WP_REST_Response($form_config, 200);
      return $this->success($form_config);
   }
}