[], ]; $contentRoot = $_ENV['app']['contentFolderPath']; $method = $request->getMethod(); $path = $request->getPathInfo(); try { $config = $this->buildConfig($contentRoot . $path); // check api key $apiKey = $_GET['key'] ?? $_POST['key'] ?? null; if (empty($apiKey)) { $response->setStatusCode(Response::HTTP_BAD_REQUEST); throw new \Exception('API key missing'); } if (! in_array($apiKey, $config['api']['keys'])) { $response->setStatusCode(Response::HTTP_UNAUTHORIZED); throw new \Exception('API key does not match'); } // GET if ($method == 'GET') { if (str_ends_with($path, '/fields')) { $this->formPath = $formPath = $contentRoot . str_replace('/fields', '', $path); $fields = $this->buildFields($formPath, $_GET['page'] ?? null); // flatten paged form if ($this->isPagedFieldSet($fields) && isset($_GET['flat'])) { $fields = array_merge(...array_values($fields)); } $content['data'] = $fields; } else { $content['data'] = Toml::parseFile($contentRoot . $path . '.toml'); } } // POST else if ($method == 'POST') { if (str_ends_with($path, '/validate')) { $this->formPath = $formPath = $contentRoot . str_replace('/validate', '', $path); $fields = $this->buildFields($formPath, $_GET['page'] ?? null); $content = $this->validateRequest($fields); } 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 . '/' . $_ENV['form']['entriesFolderName'] . '/' . $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() ); } } } } catch (\Exception $exception) { $content['error'] = $exception->getMessage(); } $response->headers->set('Content-Type', 'application/json'); $response->headers->set('Access-Control-Allow-Origin', implode(',', $config['api']['cors']['origins'])); $response->setContent(json_encode($content)); $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) { $config = []; $currentDirectory = $formPath; while (true) { $configFile = $currentDirectory . '/config/config.toml'; if (file_exists($configFile)) { $parsedConfig = Toml::parseFile($configFile); $apiKeys = array_merge($parsedConfig['api']['keys'] ?? [], $config['api']['keys'] ?? []); $config = array_replace_recursive($parsedConfig, $config); $config['api']['keys'] = $apiKeys; } // include custom functions $functionsFile = $currentDirectory . '/config/functions.php'; if (file_exists($functionsFile)) { include_once $functionsFile; } if (str_ends_with($currentDirectory, '/' . basename($_ENV['app']['contentFolderPath'])) || $currentDirectory == '/') { break; } $currentDirectory = dirname($currentDirectory); } return $config; } /** * @param array $fields */ public function isPagedFieldSet($fields) { $firstItem = reset($fields); return ! (isset($firstItem['name']) && ! is_array($firstItem['name'])); } }