diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | config.example.toml | 1 | ||||
| -rw-r--r-- | public/index.php | 21 | ||||
| -rw-r--r-- | src/App.php | 350 | ||||
| -rw-r--r-- | src/Builder.php | 80 | ||||
| -rw-r--r-- | src/Controllers/EntriesController.php | 84 | ||||
| -rw-r--r-- | src/Controllers/FieldsController.php | 25 | ||||
| -rw-r--r-- | src/Controllers/SubmissionController.php | 49 | ||||
| -rw-r--r-- | src/Controllers/ValidationController.php | 26 | ||||
| -rw-r--r-- | src/HookManager.php | 42 | ||||
| -rw-r--r-- | src/HttpException.php | 6 | ||||
| -rw-r--r-- | src/PluginLoader.php | 29 | ||||
| -rw-r--r-- | src/PreLoader.php | 15 | ||||
| -rw-r--r-- | src/Utilities.php | 40 | ||||
| -rw-r--r-- | src/Validator.php | 94 | 
15 files changed, 550 insertions, 313 deletions
| @@ -1,4 +1,5 @@  /vendor/  /content/ +/plugins/  /config.toml diff --git a/config.example.toml b/config.example.toml index 64ecc4a..7d589d1 100644 --- a/config.example.toml +++ b/config.example.toml @@ -1,2 +1,3 @@  [app]  contentFolderPath = './content' +pluginsFolderPath = './plugins' diff --git a/public/index.php b/public/index.php index b943298..8a7ec7d 100644 --- a/public/index.php +++ b/public/index.php @@ -1,11 +1,16 @@  <?php  use FlatFileForms\App; +use FlatFileForms\PluginLoader; +use FlatFileForms\PreLoader;  use Yosymfony\Toml\Toml;  require_once dirname(__DIR__) . '/vendor/autoload.php'; -function findAppConfigFile($path) +/** + * Find the config file + */ +function findAppConfigFile(string $path): string  {    $currentDirectory = $path;    while ($currentDirectory !== '/') { @@ -20,17 +25,31 @@ function findAppConfigFile($path)    die('config.toml missing');  } +// find and parse config  $configFile = findAppConfigFile(dirname(__DIR__));  $config = Toml::parseFile($configFile); +// prepare possibly relative folders path  chdir(dirname($configFile)); +  $contentDirectoryPath = realpath($config['app']['contentFolderPath']);  $contentDirectoryPath === false && die('Content folder "' . $config['app']['contentFolderPath'] . '" missing');  $config['app']['contentFolderPath'] = $contentDirectoryPath; + +$pluginsDirectoryPath = realpath($config['app']['pluginsFolderPath']); +$pluginsDirectoryPath === false && die('Plugins folder "' . $config['app']['pluginsFolderPath'] . '" missing'); +$config['app']['pluginsFolderPath'] = $pluginsDirectoryPath; +  chdir($_SERVER['DOCUMENT_ROOT']); +// set config values to global $_ENV  foreach ($config as $key => $value) {    $_ENV[$key] = $value;  } +// load +new PreLoader(); +new PluginLoader(); + +// run  new App(); diff --git a/src/App.php b/src/App.php index 92ceb4a..15be58b 100644 --- a/src/App.php +++ b/src/App.php @@ -2,6 +2,10 @@  namespace FlatFileForms; +use FlatFileForms\Controllers\EntriesController; +use FlatFileForms\Controllers\FieldsController; +use FlatFileForms\Controllers\SubmissionController; +use FlatFileForms\Controllers\ValidationController;  use Symfony\Component\HttpFoundation\Request;  use Symfony\Component\HttpFoundation\Response;  use Yosymfony\Toml\Toml; @@ -33,104 +37,34 @@ class App        // check api key        $apiKey = $_GET['key'] ?? $_POST['key'] ?? null;        if (empty($apiKey)) { -        $response->setStatusCode(Response::HTTP_BAD_REQUEST); -        throw new \Exception('API key missing'); +        throw new HttpException('API key missing', Response::HTTP_BAD_REQUEST);        }        if (! in_array($apiKey, $config['api']['keys'])) { -        $response->setStatusCode(Response::HTTP_UNAUTHORIZED); -        throw new \Exception('API key does not match'); +        throw new HttpException('API key does not match', Response::HTTP_UNAUTHORIZED);        }        // GET        if ($method == 'GET')  {          if (str_ends_with($path, '/fields')) { -          $this->formPath = $formPath = $contentRoot . str_replace('/fields', '', $path); +          $formPath = $contentRoot . str_replace('/fields', '', $path); -          $fields = $this->buildFields($formPath, $_GET['page'] ?? null); +          $builder = new Builder($formPath); -          // flatten paged form -          if ($this->isPagedFieldSet($fields) && isset($_GET['flat'])) { -            $fields = array_merge(...array_values($fields)); -          } +          $fieldsController = new FieldsController(); -          $content['data'] = $fields; +          $content = $fieldsController->getFields($builder);          }          else if (str_ends_with($path, '/entries')) {            if (! isset($_GET['dateFrom'])) { -            $response->setStatusCode(Response::HTTP_BAD_REQUEST); -            throw new \Exception('dateFrom parameter missing'); +            throw new HttpException('dateFrom parameter missing', Response::HTTP_BAD_REQUEST);            } -          $this->formPath = $formPath = $contentRoot . str_replace('/entries', '', $path); - -          $entries = []; - -          $dateFrom = new \DateTime($_GET['dateFrom']); -          $dateTo = new \DateTime($_GET['dateTo'] ?? 'now'); - -          $dateRangeYears = range($dateFrom->format('Y'), $dateTo->format('Y')); -          $dateRangeYearsCount = count($dateRangeYears); -          foreach ($dateRangeYears as $dateRangeYearIdx => $dateRangeYear) { -            $yearPath = "$formPath/entries/$dateRangeYear"; -            if (! is_dir($yearPath)) { -              continue; -            } - -            if ($dateRangeYearsCount === 1) { -              $dateRangeMonths = range($dateFrom->format('m'), $dateTo->format('m')); -            } -            else if ($dateRangeYearIdx === 0) { -              $dateRangeMonths = range($dateFrom->format('m'), 12); -            } -            else if ($dateRangeYearIdx === $dateRangeYearsCount - 1) { -              $dateRangeMonths = range(1, $dateTo->format('m')); -            } -            else { -              $dateRangeMonths = range(1, 12); -            } - -            $dateRangeMonthsCount = count($dateRangeMonths); -            foreach ($dateRangeMonths as $dateRangeMonthIdx => $dateRangeMonth) { -              $monthPath = "$yearPath/" . sprintf('%02d', $dateRangeMonth); -              if (! is_dir($monthPath)) { -                continue; -              } - -              if ($dateRangeMonthsCount === 1) { -                $dateRangeDays = range($dateFrom->format('d'), $dateTo->format('d')); -              } -              else if ($dateRangeYearIdx === 0 && $dateRangeMonthIdx === 0) { -                $dateRangeDays = range($dateFrom->format('d'), 31); -              } -              else if ($dateRangeYearIdx === $dateRangeYearsCount - 1 && $dateRangeMonthIdx === $dateRangeMonthsCount - 1) { -                $dateRangeDays = range(1, $dateTo->format('d')); -              } -              else { -                $dateRangeDays = range(1, 31); -              } - -              foreach ($dateRangeDays as $dateRangeDay) { -                $dayPath = "$monthPath/" . sprintf('%02d', $dateRangeDay); -                if (! is_dir($dayPath)) { -                  continue; -                } - -                $entriesForDay = $this->scandir($dayPath); -                foreach ($entriesForDay as $entryForDay) { -                  $entry = Toml::parseFile("$dayPath/$entryForDay"); -                  if (isset($_GET['flat'])) { -                    $entries[] = $entry; -                  } else { -                    $entries[$dateRangeYear][$dateRangeMonth][$dateRangeDay][] = $entry; -                  } -                } -              } - -            } -          } +          $formPath = $contentRoot . str_replace('/entries', '', $path); + +          $entriesController = new EntriesController(); -          $content['data'] = $entries; +          $content = $entriesController->getEntries();          }          else { @@ -141,46 +75,36 @@ class App        // POST        else if ($method == 'POST') {          if (str_ends_with($path, '/validate')) { -          $this->formPath = $formPath = $contentRoot . str_replace('/validate', '', $path); +          $formPath = $contentRoot . str_replace('/validate', '', $path); -          $fields = $this->buildFields($formPath, $_GET['page'] ?? null); +          $builder = new Builder($formPath); +          $validator = new Validator($formPath); -          $content = $this->validateRequest($fields); +          $validationController = new ValidationController(); + +          $content = $validationController->validateRequest($builder, $validator);          }          else if (str_ends_with($path, '/submit')) { -          $this->formPath = $formPath = $contentRoot . str_replace('/submit', '', $path); - -          $fields = $this->buildFields($formPath); - -          $content = $this->validateRequest($fields); - -          // if there were no validation errors then add entry -          if (empty($content['error'])) { -            $date = new \Datetime(); -            $entry = [ -              'fields' => $_POST, -              'date' => $date->format('c'), -            ]; - -            $builder = new TomlBuilder(); -            $builder->addValue('date', $entry['date']); -            $builder->addTable('fields'); -            foreach ($entry['fields'] as $entryKey => $entryValue) { -              $builder->addValue($entryKey, $entryValue); -            } - -            $entryDirectory = $formPath . '/entries/' . $date->format('Y/m/d'); -            @mkdir($entryDirectory, 0774, true); -            $entryFilename = $date->format('Ymd_Hi_') . hash('adler32', serialize($entry)) . '.toml'; -            file_put_contents( -              $entryDirectory . '/' . $entryFilename, -              $builder->getTomlString() -            ); +          $formPath = $contentRoot . str_replace('/submit', '', $path); + +          $builder = new Builder($formPath); +          $validator = new Validator($formPath); + +          $submissionController = new SubmissionController(); + +          $content = $submissionController->submit($builder, $validator, $formPath); + +          if (! empty($content['error'])) { +            throw new HttpException($content['error'], Response::HTTP_UNPROCESSABLE_ENTITY);            }          }        }      } catch (\Exception $exception) { +      if ($exception instanceof HttpException) { +        $response->setStatusCode($exception->getCode()); +      } +        $content['error'] = basename(get_class($exception)) . ': ' . $exception->getMessage();      } @@ -190,178 +114,10 @@ class App      $response->send();    } -  /** -   * @param array $fields -   */ -  public function validateRequest($fields) { -    $content = []; -    $hasInvalidFields = false; - -    $fields = $this->validateFields($fields); - -    if ($this->isPagedFieldSet($fields)) { -      // remove surplus field values from response -      $fields = array_map(function ($page) { -        return array_map(function ($field) { -          return array_intersect_key($field, array_flip([ -            'is_valid', -          ])); -        }, $page); -      }, $fields); - -      $flattened = array_merge(...array_values($fields)); -      $hasInvalidFields = in_array(false, array_column($flattened, 'is_valid')); -    } else { -      // remove surplus field values from response -      $fields = array_map(function ($field) { -        return array_intersect_key($field, array_flip([ -          'is_valid', -        ])); -      }, $fields); - -      $hasInvalidFields = in_array(false, array_column($fields, 'is_valid')); -    } - -    $content['data'] = $fields; - -    if ($hasInvalidFields) { -      $content['error'] = 'Invalid fields'; -    } - -    return $content; -  } - -  /** -   * @param string $formPath -   * @param mixed $page -   */ -  public function buildFields($formPath, $page = null) -  { -    $parsed = Toml::parseFile($formPath . '/fields/_fields.toml'); -    $fields = []; - -    // if a page is requested -    if ($page) { -      if (! isset($parsed['page'])) { -        throw new \Exception('Form has no pages'); -      } - -      if (! isset($parsed['page'][$page])) { -        throw new \Exception('Form has no page ' . $page); -      } - -      $fields = $this->buildSinglePageFields($parsed['page'][$page], $formPath); -    } - -    // else get all fields -    else { -      // if form is paged -      if (isset($parsed['page'])) { -        $pages = $parsed['page']; -        foreach ($pages as $pageKey => $pageFields) { -          $fields[$pageKey] = $this->buildSinglePageFields($pageFields, $formPath); -        } -      } - -      // if form is not paged -      else { -        foreach ($parsed['field'] as $key => $field) { -          $fields[$key] = $this->buildSingleField($formPath, $key, $field); -        } -      } -    } - -    return $fields; -  } - -  /** -   * @param array $pageFields -   * @param string $formPath -   */ -  public function buildSinglePageFields($pageFields, $formPath) -  { -    $fields = []; - -    if (! empty($pageFields['file'])) { -      $pageFields = array_replace_recursive($pageFields, Toml::parseFile($formPath . '/fields/' . $pageFields['file'])); -    } - -    foreach ($pageFields['field'] as $key => $field) { -      $fields[$key] = $this->buildSingleField($formPath, $key, $field); -    } - -    return $fields; -  } - -  /** -   * @param string $formPath -   * @param string $key -   * @param string $field -   */ -  public function buildSingleField($formPath, $key, $field) -  { -    if (! empty($field['file'])) { -      $field = array_replace_recursive($field, Toml::parseFile($formPath . '/fields/' . $field['file'])); -    } - -    if (empty($field['name'])) { -      $field['name'] = $key; -    } - -    return $field; -  } - -  /** -   * @param array $fields -   */ -  public function validateFields($fields) -  { -    if ($this->isPagedFieldSet($fields)) { -      foreach ($fields as $pageKey => &$pageFields) { -        foreach ($pageFields as $key => &$field) { -          $field = $this->validateSingleField($field); -        } -      } -    } else { -      foreach ($fields as $key => &$field) { -        $field = $this->validateSingleField($field); -      } -    } - -    return $fields; -  } - -  /** -   * @param array $field -   */ -  public function validateSingleField($field) -  { -    $value = $_POST[$field['name']] ?? ''; -    $field['is_valid'] = true; - -    if (isset($field['required']) && empty($value)) { -      $field['is_valid'] = false; -    } - -    if (isset($field['validation']['pattern']) && preg_match_all('/' . $field['validation']['pattern'] . '/', $value) === 0) { -      $field['is_valid'] = false; -    } - -    $validationFunctionName = 'validate_' . basename($this->formPath) . '_' . $field['name']; -    if (function_exists($validationFunctionName)) { -      $field = call_user_func($validationFunctionName, $field, $value); -    } - -    return $field; -  } - -  /** -   * @param string $formPath -   */ -  public function buildConfig($formPath) +  public function buildConfig(string $requestPath): array    {      $config = []; -    $currentDirectory = $formPath; +    $currentDirectory = $requestPath;      while (true) {        $configFile = $currentDirectory . '/config/config.toml';        if (file_exists($configFile)) { @@ -387,34 +143,4 @@ class App      return $config;    } - -  /** -   * @param array $fields -   */ -  public function isPagedFieldSet($fields) -  { -    $firstItem = reset($fields); - -    return ! (isset($firstItem['name']) && ! is_array($firstItem['name'])); -  } - -  /** -   * @param string|array $paths -   */ -  public function scandir($paths) -  { -    $paths = (array)$paths; - -    $scanned = []; -    foreach ($paths as $path) { -      $filtered = array_values( -        array_filter( -          scandir($path), fn ($item) => ! in_array($item, ['.', '..']) -        ) -      ); -      array_push($scanned, ...$filtered); -    } - -    return $scanned; -  }  } diff --git a/src/Builder.php b/src/Builder.php new file mode 100644 index 0000000..308fa82 --- /dev/null +++ b/src/Builder.php @@ -0,0 +1,80 @@ +<?php + +namespace FlatFileForms; + +use Yosymfony\Toml\Toml; + +class Builder +{ +  public function __construct( +    private string $formPath +  ) +  {} + +  public function buildFields(mixed $page = null) +  { +    $parsed = Toml::parseFile($this->formPath . '/fields/_fields.toml'); +    $fields = []; + +    // if a page is requested +    if ($page) { +      if (! isset($parsed['page'])) { +        throw new \Exception('Form has no pages'); +      } + +      if (! isset($parsed['page'][$page])) { +        throw new \Exception('Form has no page ' . $page); +      } + +      $fields = $this->buildSinglePageFields($parsed['page'][$page]); +    } + +    // else get all fields +    else { +      // if form is paged +      if (isset($parsed['page'])) { +        $pages = $parsed['page']; +        foreach ($pages as $pageKey => $pageFields) { +          $fields[$pageKey] = $this->buildSinglePageFields($pageFields); +        } +      } + +      // if form is not paged +      else { +        foreach ($parsed['field'] as $key => $field) { +          $fields[$key] = $this->buildSingleField($key, $field); +        } +      } +    } + +    return $fields; +  } + +  public function buildSinglePageFields(array $pageFields): array +  { +    $fields = []; + +    if (! empty($pageFields['file'])) { +      $pageFields = array_replace_recursive($pageFields, Toml::parseFile($this->formPath . '/fields/' . $pageFields['file'])); +    } + +    foreach ($pageFields['field'] as $key => $field) { +      $fields[$key] = $this->buildSingleField($key, $field); +    } + +    return $fields; +  } + +  public function buildSingleField(string $key, array $field): array +  { +    if (! empty($field['file'])) { +      $field = array_replace_recursive($field, Toml::parseFile($this->formPath . '/fields/' . $field['file'])); +    } + +    if (empty($field['name'])) { +      $field['name'] = $key; +    } + +    return $field; +  } +} diff --git a/src/Controllers/EntriesController.php b/src/Controllers/EntriesController.php new file mode 100644 index 0000000..13aa8e0 --- /dev/null +++ b/src/Controllers/EntriesController.php @@ -0,0 +1,84 @@ +<?php + +namespace FlatFileForms\Controllers; + +use Yosymfony\Toml\Toml; + +class EntriesController +{ +  public function getEntries() +  { +    /**@var Utilities $utilities*/ +    global $utilities; + +    $entries = []; + +    $dateFrom = new \DateTime($_GET['dateFrom']); +    $dateTo = new \DateTime($_GET['dateTo'] ?? 'now'); + +    $dateRangeYears = range($dateFrom->format('Y'), $dateTo->format('Y')); +    $dateRangeYearsCount = count($dateRangeYears); +    foreach ($dateRangeYears as $dateRangeYearIdx => $dateRangeYear) { +      $yearPath = "$formPath/entries/$dateRangeYear"; +      if (! is_dir($yearPath)) { +        continue; +      } + +      if ($dateRangeYearsCount === 1) { +        $dateRangeMonths = range($dateFrom->format('m'), $dateTo->format('m')); +      } +      else if ($dateRangeYearIdx === 0) { +        $dateRangeMonths = range($dateFrom->format('m'), 12); +      } +      else if ($dateRangeYearIdx === $dateRangeYearsCount - 1) { +        $dateRangeMonths = range(1, $dateTo->format('m')); +      } +      else { +        $dateRangeMonths = range(1, 12); +      } + +      $dateRangeMonthsCount = count($dateRangeMonths); +      foreach ($dateRangeMonths as $dateRangeMonthIdx => $dateRangeMonth) { +        $monthPath = "$yearPath/" . sprintf('%02d', $dateRangeMonth); +        if (! is_dir($monthPath)) { +          continue; +        } + +        if ($dateRangeMonthsCount === 1) { +          $dateRangeDays = range($dateFrom->format('d'), $dateTo->format('d')); +        } +        else if ($dateRangeYearIdx === 0 && $dateRangeMonthIdx === 0) { +          $dateRangeDays = range($dateFrom->format('d'), 31); +        } +        else if ($dateRangeYearIdx === $dateRangeYearsCount - 1 && $dateRangeMonthIdx === $dateRangeMonthsCount - 1) { +          $dateRangeDays = range(1, $dateTo->format('d')); +        } +        else { +          $dateRangeDays = range(1, 31); +        } + +        foreach ($dateRangeDays as $dateRangeDay) { +          $dayPath = "$monthPath/" . sprintf('%02d', $dateRangeDay); +          if (! is_dir($dayPath)) { +            continue; +          } + +          $entriesForDay = $utilities->scandir($dayPath); +          foreach ($entriesForDay as $entryForDay) { +            $entry = Toml::parseFile("$dayPath/$entryForDay"); +            if (isset($_GET['flat'])) { +              $entries[] = $entry; +            } else { +              $entries[$dateRangeYear][$dateRangeMonth][$dateRangeDay][] = $entry; +            } +          } +        } + +      } +    } + +    $content['data'] = $entries; + +    return $content; +  } +} diff --git a/src/Controllers/FieldsController.php b/src/Controllers/FieldsController.php new file mode 100644 index 0000000..c407604 --- /dev/null +++ b/src/Controllers/FieldsController.php @@ -0,0 +1,25 @@ +<?php + +namespace FlatFileForms\Controllers; + +use FlatFileForms\Builder; + +class FieldsController +{ +  public function getFields(Builder $builder): array +  { +    /**@var Utilities $utilities*/ +    global $utilities; + +    $fields = $builder->buildFields($_GET['page'] ?? null); + +    // flatten paged form +    if ($utilities->isPagedFieldSet($fields) && isset($_GET['flat'])) { +      $fields = array_merge(...array_values($fields)); +    } + +    $content['data'] = $fields; + +    return $content; +  } +} diff --git a/src/Controllers/SubmissionController.php b/src/Controllers/SubmissionController.php new file mode 100644 index 0000000..f2c4c80 --- /dev/null +++ b/src/Controllers/SubmissionController.php @@ -0,0 +1,49 @@ +<?php + +namespace FlatFileForms\Controllers; + +use FlatFileForms\Builder; +use FlatFileForms\Validator; +use Yosymfony\Toml\TomlBuilder; + +class SubmissionController +{ +  public function submit(Builder $builder, Validator $validator, $formPath) +  { +    $fields = $builder->buildFields(); + +    // run through validation +    $result = $validator->validateRequest($fields); + +    $content['data'] = $result['fields']; + +    // if there were no validation errors then add entry +    if (empty($result['error'])) { +      $date = new \Datetime(); +      $entry = [ +        'fields' => $_POST, +        'date' => $date->format('c'), +      ]; + +      $entryBuilder = new TomlBuilder(); +      $entryBuilder->addValue('date', $entry['date']); +      $entryBuilder->addTable('fields'); +      foreach ($entry['fields'] as $entryKey => $entryValue) { +        $entryBuilder->addValue($entryKey, $entryValue); +      } + +      $entryDirectory = $formPath . '/entries/' . $date->format('Y/m/d'); +      @mkdir($entryDirectory, 0774, true); +      $entryFilename = $date->format('Ymd_Hi_') . hash('adler32', serialize($entry)) . '.toml'; +      file_put_contents( +        $entryDirectory . '/' . $entryFilename, +        $entryBuilder->getTomlString() +      ); +    } +    else { +      $content['error'] = $result['error']; +    } + +    return $content; +  } +} diff --git a/src/Controllers/ValidationController.php b/src/Controllers/ValidationController.php new file mode 100644 index 0000000..f90359a --- /dev/null +++ b/src/Controllers/ValidationController.php @@ -0,0 +1,26 @@ +<?php + +namespace FlatFileForms\Controllers; + +use FlatFileForms\Builder; +use FlatFileForms\Validator; + +class ValidationController +{ +  public function validateRequest(Builder $builder, Validator $validator): array +  { +    /**@var Utilities $utilities*/ +    global $utilities; + +    $fields = $builder->buildFields($_GET['page'] ?? null); + +    $result = $validator->validateRequest($fields); + +    $content['data'] = $result['fields']; +    if (! empty($result['error'])) { +      $content['error'] = $result['error']; +    } + +    return $content; +  } +} diff --git a/src/HookManager.php b/src/HookManager.php new file mode 100644 index 0000000..96ca8d3 --- /dev/null +++ b/src/HookManager.php @@ -0,0 +1,42 @@ +<?php + +namespace FlatFileForms; + +class HookManager +{ +  private array $actions = []; +  private array $filters = []; + +  public function addAction(string $name, callable $function, int $priority = 10): void +  { +    $this->action[$name][$priority][] = compact('name', 'function', 'priority'); +  } + +  public function addFilter(string $name, callable $function, int $priority = 10): void +  { +    $this->filters[$name][$priority][] = compact('name', 'callable', 'priority'); +  } + +  public function doAction(string $name, mixed ...$arguments): void +  { +    foreach ($this->actions[$name] as $actions) { +      foreach ($actions as $action) { +        call_user_func_array($action['function'], $arguments); +      } +    } +  } + +  public function applyFilter(string $name, mixed $value, mixed ...$arguments): mixed +  { +    // set $value as first argument +    array_unshift($arguments, $value); + +    foreach ($this->filters[$name] as $filters) { +      foreach ($filters as $filter) { +        $value = call_user_func_array($filter['function'], $arguments); +      } +    } + +    return $value; +  } +} diff --git a/src/HttpException.php b/src/HttpException.php new file mode 100644 index 0000000..85d2854 --- /dev/null +++ b/src/HttpException.php @@ -0,0 +1,6 @@ +<?php + +namespace FlatFileForms; + +class HttpException extends \Exception +{} diff --git a/src/PluginLoader.php b/src/PluginLoader.php new file mode 100644 index 0000000..c8da5fd --- /dev/null +++ b/src/PluginLoader.php @@ -0,0 +1,29 @@ +<?php + +namespace FlatFileForms; + +class PluginLoader +{ +  public function __construct() +  { +    /**@var Utilities $utilities*/ +    global $utilities; + +    $pluginsDirectoryPath = $_ENV['app']['pluginsFolderPath']; + +    spl_autoload_register(function ($classname) use ($pluginsDirectoryPath) { +      $classname = str_replace('FlatFileForms\\Plugins\\', '', $classname); + +      require_once +        $pluginsDirectoryPath . '/' . +        str_replace('\\', '/', $classname) . +        '.php'; +    }); + +    $pluginDirectories = $utilities->scandir($pluginsDirectoryPath); +    foreach ($pluginDirectories as $directory) { +      $pluginClass = 'FlatFileForms\\Plugins\\' . $directory . '\\Plugin'; +      $plugin = new $pluginClass(); +    } +  } +} diff --git a/src/PreLoader.php b/src/PreLoader.php new file mode 100644 index 0000000..57a0f52 --- /dev/null +++ b/src/PreLoader.php @@ -0,0 +1,15 @@ +<?php + +namespace FlatFileForms; + +class PreLoader +{ +  public function __construct() +  { +    global $utilities; +    $utilities = new Utilities(); + +    global $hooks; +    $hooks = new HookManager(); +  } +} diff --git a/src/Utilities.php b/src/Utilities.php new file mode 100644 index 0000000..0462ad6 --- /dev/null +++ b/src/Utilities.php @@ -0,0 +1,40 @@ +<?php + +namespace FlatFileForms; + +class Utilities +{ +  public function isPagedFieldSet(array $fields): bool +  { +    $firstItem = reset($fields); + +    return ! (isset($firstItem['name']) && ! is_array($firstItem['name'])); +  } + +  public function scandir(string $path): array +  { +    $path = rtrim($path, '/'); + +    return array_values( +      array_map( +        fn ($item) => $path . '/' . $item, +        array_filter( +          scandir($path), fn ($item) => ! in_array($item, ['.', '..']) +        ) +      ) +    ); +  } + +  public function scandirMultiple(string|array $paths): array +  { +    $paths = (array)$paths; + +    $merged = []; +    foreach ($paths as $path) { +      $scanned = $this->scandir($path); +      array_push($merged, ...$scanned); +    } + +    return $merged; +  } +} diff --git a/src/Validator.php b/src/Validator.php new file mode 100644 index 0000000..33cdf34 --- /dev/null +++ b/src/Validator.php @@ -0,0 +1,94 @@ +<?php + +namespace FlatFileForms; + +class Validator +{ +  public function __construct( +    private string $formPath +  ) +  {} + +  public function validateRequest(array $fields): array +  { +    /**@var Utilities $utilities*/ +    global $utilities; + +    $result = []; +    $hasInvalidFields = false; + +    $fields = $this->validateFields($fields); + +    if ($utilities->isPagedFieldSet($fields)) { +      // remove surplus field values from response +      $fields = array_map(function ($page) { +        return array_map(function ($field) { +          return array_intersect_key($field, array_flip([ +            'is_valid', +          ])); +        }, $page); +      }, $fields); + +      $flattened = array_merge(...array_values($fields)); +      $hasInvalidFields = in_array(false, array_column($flattened, 'is_valid')); +    } else { +      // remove surplus field values from response +      $fields = array_map(function ($field) { +        return array_intersect_key($field, array_flip([ +          'is_valid', +        ])); +      }, $fields); + +      $hasInvalidFields = in_array(false, array_column($fields, 'is_valid')); +    } + +    $result['fields'] = $fields; + +    if ($hasInvalidFields) { +      $result['error'] = 'invalid fields'; +    } + +    return $result; +  } + +  public function validateFields(array $fields): array +  { +    /**@var Utilities $utilities*/ +    global $utilities; + +    if ($utilities->isPagedFieldSet($fields)) { +      foreach ($fields as $pageKey => &$pageFields) { +        foreach ($pageFields as $key => &$field) { +          $field = $this->validateSingleField($field); +        } +      } +    } else { +      foreach ($fields as $key => &$field) { +        $field = $this->validateSingleField($field); +      } +    } + +    return $fields; +  } + +  public function validateSingleField(array $field): array +  { +    $value = $_POST[$field['name']] ?? ''; +    $field['is_valid'] = true; + +    if (isset($field['required']) && empty($value)) { +      $field['is_valid'] = false; +    } + +    if (isset($field['validation']['pattern']) && preg_match_all('/' . $field['validation']['pattern'] . '/', $value) === 0) { +      $field['is_valid'] = false; +    } + +    $validationFunctionName = 'validate_' . basename($this->formPath) . '_' . $field['name']; +    if (function_exists($validationFunctionName)) { +      $field = call_user_func($validationFunctionName, $field, $value); +    } + +    return $field; +  } +} | 
