. * */ namespace Vvveb\Controller; use function Vvveb\config; use function Vvveb\isSecure; use function Vvveb\reconstructJson; use function Vvveb\siteSettings; use Vvveb\System\Cache; use Vvveb\System\Core\View; use Vvveb\System\Event; use Vvveb\System\Functions\Plural; use Vvveb\System\Sites; use Vvveb\System\Sqlp\Sqlp; use function Vvveb\url; #[\AllowDynamicProperties] class Index extends Base { function type($type) { $type = strtolower($type); switch ($type) { case 'int': case 'tinyint': $type = 'integer'; break; case 'varchar': case 'char': case 'datetime': case 'longtext': $type = 'string'; break; } return $type; } function paths() { $routes = config('rest-routes'); $paths = []; $sqlp = new Sqlp(); $dirSQL = DIR_ROOT . 'admin' . DS . 'sql' . DS . DB_ENGINE . DS; $routeSchemas = []; foreach ($routes as $route => $options) { if (isset($options['schema'])) { $routeSchemas[$options['schema']] = $options['schema']; } } $routeSchemas = array_values($routeSchemas); $this->tables = []; $db = \Vvveb\System\Db::getInstance(); $tables = $db->getTableNames(DB_NAME); $routesNew = []; foreach ($tables as $table) { $cols = []; $meta = $db->getColumnsMeta($table, true); foreach ($meta as $col) { $cols[$col['name']] = $col; } $this->tables[$table] = $cols; if (! in_array($table, $routeSchemas)) { if (file_exists($dirSQL . $table . '.sql')) { $plural = Plural::tablePlural($table); $routes['/rest/' . $plural] = ['module' => 'default', 'methods' => ['get', 'post'], 'schema' => $table]; $routes['/rest/' . $plural . '/#' . $table . '_id#'] = ['module' => 'default/crud/index', 'methods' => ['get', 'post', 'put', 'patch', 'delete'], 'schema' => $table]; } } } //echo var_export($routesNew, true); foreach ($routes as $route => $options) { $route = str_replace(['/rest', '/#', '#'], ['', '/{', '}'], $route); $data = []; $methods = $options['methods'] ?? ['get']; $actions = ['get' => 'get', 'post' => 'add', 'patch' => 'edit', 'put' => 'edit', 'delete' => 'delete']; foreach ($methods as $method) { $getParams = []; if (preg_match_all('/#([a-zA-Z]\w+)#/', $route, $matches)) { foreach ($matches[1] as $match) { $getParams[$match] = ['name' => $match, 'in' => 'path', 'schema' => ['type' => 'integer']]; } } if (preg_match_all('/{(\w+)}/', $route, $matches)) { foreach ($matches[1] as $match) { $getParams[$match] = ['name' => $match, 'in' => 'path', 'schema' => ['type' => 'string']]; } } //$responses $meth = ['responses' => []]; $ok = [ 'description' => 'OK', ]; $params = []; $encoding = []; if (isset($options['schema'])) { $ok['content'] = [ 'application/json' => [ 'schema' => [ '$ref' => '#/components/schemas/' . $options['schema'], ], ], ]; $sqlp->parseSqlPfile($dirSQL . $options['schema'] . '.sql'); $tree = $sqlp->getModel(); $action = $actions[$method]; if ($method == 'get' && ! isset($methods['delete'])) { $action = 'getAll'; } if (($tree && isset($tree[$action])) && ($method != 'get' || ($method == 'get' && ! $getParams))) { foreach ($tree[$action]['params'] as $p) { if ($p['in_out'] == 'OUT') { continue; } $param = []; if ($method == 'post') { $name = $p['name']; if ($name == $options['schema'] . '_id') { continue; } $params[$name]['type'] = $this->type($p['type'] ?? 'integer'); if ($params[$name]['type'] == 'array' && isset($this->tables[$name])) { $encoding[$name]['explode'] = true; $encoding[$name]['style'] = 'deepObject'; //$encoding[$name]['contentType'] = 'application/x-www-form-urlencoded'; $params[$name]['type'] = 'object'; $params[$name]['style'] = 'deepObject'; $params[$name]['explode'] = true; //$params[$name]['contentEncoding'] = 'text/plain'; $properties = []; foreach ($this->tables[$name] as $col) { if ($col['name'] == $options['schema'] . '_id') { continue; } //skip auto increment if ((isset($col['e']) && $col['e'] == 'auto_increment')) { continue; } $properties[$col['name']] = ['type' => $this->type($col['t'])]; if ($col['d'] === NULL || $col['d'] == 'NULL') { $properties[$col['name']]['required'] = true; } else { $properties[$col['name']]['required'] = false; } } if (in_array($name,['post_content', 'product_content'])) { $params[$name]['type'] = 'array'; $params[$name]['items']['type'] = 'object'; $params[$name]['items']['explode'] = true; $params[$name]['items']['style'] = 'deepObject'; $params[$name]['items']['properties'] = $properties; $encoding[$name]['items']['explode'] = true; $encoding[$name]['items']['style'] = 'deepObject'; //$encoding[$name]['items']['type'] = 'object'; //$encoding[$name]['type'] = 'array'; } else { $params[$name]['properties'] = $properties; } } if ($params[$name]['type'] == 'array' && substr_compare($name, '_id', -3 ,3) === 0) { $params[$name]['items']['type'] = 'integer'; //$params[$name]['items']['explode'] = true; $params[$name]['style'] = 'deepObject'; $params[$name]['explode'] = true; $encoding[$name]['items']['explode'] = true; $encoding[$name]['items']['style'] = 'deepObject'; $encoding[$name]['explode'] = true; $encoding[$name]['style'] = 'deepObject'; //$encoding[$name]['items']['style'] = 'deepObject'; } } else { $param['name'] = $p['name']; $param['in'] = 'query'; $param['required'] = 'false'; $param['description'] = $p['comment'] ?? ''; $param['schema']['type'] = $this->type($p['type'] ?? 'integer'); if ($param['schema']['type'] == 'array' && isset($this->tables[$param['name']])) { $param['schema']['$ref'] = '#/components/schemas/' . $param['name']; $properties = []; $param['explode'] = true; $param['style'] = 'deepObject'; $param['schema']['type'] = 'object'; foreach ($this->tables[$param['name']] as $col) { $properties[$col['name']] = ['type' => $this->type($col['t'])]; if ($col['d'] === NULL || $col['d'] == 'NULL') { $properties[$col['name']]['required'] = true; } else { $properties[$col['name']]['required'] = false; } } //$param['name'] .= '[]'; if ($param['name'] == 'post_content') { $param['schema']['type'] = 'array'; $param['schema']['items']['type'] = 'object'; $param['schema']['items']['explode'] = true; $param['schema']['items']['style'] = 'deepObject'; $param['schema']['items']['properties'] = $properties; } else { $param['schema']['properties'] = $properties; } } if (! isset($getParams[$param['name']])) { $getParams[$param['name']] = $param; } } } } if ($method == 'post') { $meth['requestBody'] = [ 'content' => [ 'application/x-www-form-urlencoded' => [ 'encoding' => $encoding, 'schema' => [ 'type' => 'object', 'properties' => $params, ], ], ], ]; } if ($getParams) { $meth['parameters'] = array_values($getParams); } } $meth['responses']['200'] = $ok; $data[$method] = $meth; } $paths[$route] = $data; } list($paths) = Event :: trigger(__CLASS__,__FUNCTION__, $paths); return $paths; $this->response->setType('json'); $this->response->output($paths); } function schemas() { $db = \Vvveb\System\Db::getInstance(); $tables = $db->getTableNames(DB_NAME); $schemas = []; foreach ($tables as $table) { $meta = $db->getColumnsMeta($table, true); $cols = []; foreach ($meta as $column) { $col = []; $col['name'] = $column['name'] ?? ''; $col['description'] = $column['c'] ?? ''; $col['type'] = $this->type($column['t'] ?? 'integer'); switch ($col['type']) { case 'datetime': $col['format'] = 'date-time'; } if (isset($column['c']) && $column['c'] == 'YES') { $col['type'] = [$col['type'], 'null']; } $cols[$column['name']] = $col; } $data = [ 'title' => $table, 'type' => 'object', '$schema' => 'http://json-schema.org/draft-04/schema#', 'properties' => $cols, ]; $schemas[$table] = $data; } //var_export($schemas); list($schemas) = Event :: trigger(__CLASS__,__FUNCTION__, $schemas); return $schemas; $this->response->setType('json'); $this->response->output($schemas); } public static function getControllerList($app = APP) { $files = []; $path = [DIR_ROOT . $app . '/controller/*', DIR_PLUGINS . '*/' . $app . '/controller/*']; while (count($path) > 0) { $next = array_shift($path); //echo $next . "\n"; foreach (glob("$next/*.json") as $file) { if (is_dir($file)) { $path[] = $file; } if (is_file($file)) { $files[] = $file; } } } list($files) = Event :: trigger(__CLASS__,__FUNCTION__, $files); return $files; } function renderJson() { $htmlView = new View(); //$htmlView = clone $this->view; $htmlView->setTheme(); $htmlView->set(['seo' => []]); $htmlView->template('openapi.json'); $xml = $htmlView->render(true, false, true); if ($xml) { $sxml = \simplexml_load_string($xml, null, LIBXML_NOCDATA | LIBXML_DTDATTR); $array = reconstructJson($sxml, true); return $array; $json = json_encode($array, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT); echo $json; } } function api() { $cache = Cache::getInstance(); $json = $cache->cache(APP,'openapi',function () { $site = siteSettings(); $data = Sites :: getSiteData(); $json = $this->renderJson(); $json['paths'] = $this->paths(); $json['components']['schemas'] = $this->schemas(); $site = siteSettings(SITE_ID, $this->global['language_id']); $urlParams = ['host' => $data['host'], 'scheme' => isSecure() ? 'https' : 'http']; $url = url('/',$urlParams); $json['info']['title'] = $site['description']['title']; $json['info']['contact']['email'] = $site['contact-email']; $json['info']['contact']['url'] = $url; $json['servers'][0]['url'] = url('',$urlParams); return $json; }, 259200); $this->response->setType('json'); $this->response->output($json); } function index() { return $this->api(); } }