*/ protected array $filters = []; /** * @var array */ protected array $payload = []; /** * @var bool */ protected static bool $showSensitive = false; protected SwooleHTTPResponse $swoole; /** * Response constructor. * * @param SwooleHTTPResponse $response Native response to be passed to parent constructor */ public function __construct(SwooleHTTPResponse $response) { $this->swoole = $response; $this // General ->setModel(new None()) ->setModel(new Any()) ->setModel(new Error()) ->setModel(new ErrorDev()) // Lists ->setModel(new BaseList('Rows List', self::MODEL_ROW_LIST, 'rows', self::MODEL_ROW)) ->setModel(new BaseList('Documents List', self::MODEL_DOCUMENT_LIST, 'documents', self::MODEL_DOCUMENT)) ->setModel(new BaseList('Tables List', self::MODEL_TABLE_LIST, 'tables', self::MODEL_TABLE)) ->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'collections', self::MODEL_COLLECTION)) ->setModel(new BaseList('Databases List', self::MODEL_DATABASE_LIST, 'databases', self::MODEL_DATABASE)) ->setModel(new BaseList('Indexes List', self::MODEL_INDEX_LIST, 'indexes', self::MODEL_INDEX)) ->setModel(new BaseList('Column Indexes List', self::MODEL_COLUMN_INDEX_LIST, 'indexes', self::MODEL_COLUMN_INDEX)) ->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER)) ->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION)) ->setModel(new BaseList('Identities List', self::MODEL_IDENTITY_LIST, 'identities', self::MODEL_IDENTITY)) ->setModel(new BaseList('Logs List', self::MODEL_LOG_LIST, 'logs', self::MODEL_LOG)) ->setModel(new BaseList('Files List', self::MODEL_FILE_LIST, 'files', self::MODEL_FILE)) ->setModel(new BaseList('Buckets List', self::MODEL_BUCKET_LIST, 'buckets', self::MODEL_BUCKET)) ->setModel(new BaseList('Resource Tokens List', self::MODEL_RESOURCE_TOKEN_LIST, 'tokens', self::MODEL_RESOURCE_TOKEN)) ->setModel(new BaseList('Teams List', self::MODEL_TEAM_LIST, 'teams', self::MODEL_TEAM)) ->setModel(new BaseList('Memberships List', self::MODEL_MEMBERSHIP_LIST, 'memberships', self::MODEL_MEMBERSHIP)) ->setModel(new BaseList('Sites List', self::MODEL_SITE_LIST, 'sites', self::MODEL_SITE)) ->setModel(new BaseList('Site Templates List', self::MODEL_TEMPLATE_SITE_LIST, 'templates', self::MODEL_TEMPLATE_SITE)) ->setModel(new BaseList('Functions List', self::MODEL_FUNCTION_LIST, 'functions', self::MODEL_FUNCTION)) ->setModel(new BaseList('Function Templates List', self::MODEL_TEMPLATE_FUNCTION_LIST, 'templates', self::MODEL_TEMPLATE_FUNCTION)) ->setModel(new BaseList('Installations List', self::MODEL_INSTALLATION_LIST, 'installations', self::MODEL_INSTALLATION)) ->setModel(new BaseList('Framework Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST, 'frameworkProviderRepositories', self::MODEL_PROVIDER_REPOSITORY_FRAMEWORK)) ->setModel(new BaseList('Runtime Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST, 'runtimeProviderRepositories', self::MODEL_PROVIDER_REPOSITORY_RUNTIME)) ->setModel(new BaseList('Branches List', self::MODEL_BRANCH_LIST, 'branches', self::MODEL_BRANCH)) ->setModel(new BaseList('Frameworks List', self::MODEL_FRAMEWORK_LIST, 'frameworks', self::MODEL_FRAMEWORK)) ->setModel(new BaseList('Runtimes List', self::MODEL_RUNTIME_LIST, 'runtimes', self::MODEL_RUNTIME)) ->setModel(new BaseList('Deployments List', self::MODEL_DEPLOYMENT_LIST, 'deployments', self::MODEL_DEPLOYMENT)) ->setModel(new BaseList('Executions List', self::MODEL_EXECUTION_LIST, 'executions', self::MODEL_EXECUTION)) ->setModel(new BaseList('Projects List', self::MODEL_PROJECT_LIST, 'projects', self::MODEL_PROJECT, true, false)) ->setModel(new BaseList('Webhooks List', self::MODEL_WEBHOOK_LIST, 'webhooks', self::MODEL_WEBHOOK, true, false)) ->setModel(new BaseList('API Keys List', self::MODEL_KEY_LIST, 'keys', self::MODEL_KEY, true, false)) ->setModel(new BaseList('Dev Keys List', self::MODEL_DEV_KEY_LIST, 'devKeys', self::MODEL_DEV_KEY, true, false)) ->setModel(new BaseList('Auth Providers List', self::MODEL_AUTH_PROVIDER_LIST, 'platforms', self::MODEL_AUTH_PROVIDER, true, false)) ->setModel(new BaseList('Platforms List', self::MODEL_PLATFORM_LIST, 'platforms', self::MODEL_PLATFORM, true, false)) ->setModel(new BaseList('Countries List', self::MODEL_COUNTRY_LIST, 'countries', self::MODEL_COUNTRY)) ->setModel(new BaseList('Continents List', self::MODEL_CONTINENT_LIST, 'continents', self::MODEL_CONTINENT)) ->setModel(new BaseList('Languages List', self::MODEL_LANGUAGE_LIST, 'languages', self::MODEL_LANGUAGE)) ->setModel(new BaseList('Currencies List', self::MODEL_CURRENCY_LIST, 'currencies', self::MODEL_CURRENCY)) ->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE)) ->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false)) ->setModel(new BaseList('Variables List', self::MODEL_VARIABLE_LIST, 'variables', self::MODEL_VARIABLE)) ->setModel(new BaseList('Status List', self::MODEL_HEALTH_STATUS_LIST, 'statuses', self::MODEL_HEALTH_STATUS)) ->setModel(new BaseList('Rule List', self::MODEL_PROXY_RULE_LIST, 'rules', self::MODEL_PROXY_RULE)) ->setModel(new BaseList('Locale codes list', self::MODEL_LOCALE_CODE_LIST, 'localeCodes', self::MODEL_LOCALE_CODE)) ->setModel(new BaseList('Provider list', self::MODEL_PROVIDER_LIST, 'providers', self::MODEL_PROVIDER)) ->setModel(new BaseList('Message list', self::MODEL_MESSAGE_LIST, 'messages', self::MODEL_MESSAGE)) ->setModel(new BaseList('Topic list', self::MODEL_TOPIC_LIST, 'topics', self::MODEL_TOPIC)) ->setModel(new BaseList('Subscriber list', self::MODEL_SUBSCRIBER_LIST, 'subscribers', self::MODEL_SUBSCRIBER)) ->setModel(new BaseList('Target list', self::MODEL_TARGET_LIST, 'targets', self::MODEL_TARGET)) ->setModel(new BaseList('Transaction List', self::MODEL_TRANSACTION_LIST, 'transactions', self::MODEL_TRANSACTION)) ->setModel(new BaseList('Migrations List', self::MODEL_MIGRATION_LIST, 'migrations', self::MODEL_MIGRATION)) ->setModel(new BaseList('Migrations Firebase Projects List', self::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', self::MODEL_MIGRATION_FIREBASE_PROJECT)) ->setModel(new BaseList('Specifications List', self::MODEL_SPECIFICATION_LIST, 'specifications', self::MODEL_SPECIFICATION)) ->setModel(new BaseList('VCS Content List', self::MODEL_VCS_CONTENT_LIST, 'contents', self::MODEL_VCS_CONTENT)) // Entities ->setModel(new Database()) // Collection API Models ->setModel(new Collection()) ->setModel(new Attribute()) ->setModel(new AttributeList()) ->setModel(new AttributeString()) ->setModel(new AttributeInteger()) ->setModel(new AttributeFloat()) ->setModel(new AttributeBoolean()) ->setModel(new AttributeEmail()) ->setModel(new AttributeEnum()) ->setModel(new AttributeIP()) ->setModel(new AttributeURL()) ->setModel(new AttributeDatetime()) ->setModel(new AttributeRelationship()) ->setModel(new AttributePoint()) ->setModel(new AttributeLine()) ->setModel(new AttributePolygon()) // Table API Models ->setModel(new Table()) ->setModel(new Column()) ->setModel(new ColumnList()) ->setModel(new ColumnString()) ->setModel(new ColumnInteger()) ->setModel(new ColumnFloat()) ->setModel(new ColumnBoolean()) ->setModel(new ColumnEmail()) ->setModel(new ColumnEnum()) ->setModel(new ColumnIP()) ->setModel(new ColumnURL()) ->setModel(new ColumnDatetime()) ->setModel(new ColumnRelationship()) ->setModel(new ColumnPoint()) ->setModel(new ColumnLine()) ->setModel(new ColumnPolygon()) ->setModel(new Index()) ->setModel(new ColumnIndex()) ->setModel(new Row()) ->setModel(new ModelDocument()) ->setModel(new Log()) ->setModel(new User()) ->setModel(new AlgoMd5()) ->setModel(new AlgoSha()) ->setModel(new AlgoPhpass()) ->setModel(new AlgoBcrypt()) ->setModel(new AlgoScrypt()) ->setModel(new AlgoScryptModified()) ->setModel(new AlgoArgon2()) ->setModel(new Account()) ->setModel(new Preferences()) ->setModel(new Session()) ->setModel(new Identity()) ->setModel(new Token()) ->setModel(new JWT()) ->setModel(new Locale()) ->setModel(new LocaleCode()) ->setModel(new File()) ->setModel(new Bucket()) ->setModel(new ResourceToken()) ->setModel(new Team()) ->setModel(new Membership()) ->setModel(new Site()) ->setModel(new TemplateSite()) ->setModel(new TemplateFramework()) ->setModel(new Func()) ->setModel(new TemplateFunction()) ->setModel(new TemplateRuntime()) ->setModel(new TemplateVariable()) ->setModel(new Installation()) ->setModel(new ProviderRepository()) ->setModel(new ProviderRepositoryFramework()) ->setModel(new ProviderRepositoryRuntime()) ->setModel(new DetectionFramework()) ->setModel(new DetectionRuntime()) ->setModel(new VcsContent()) ->setModel(new Branch()) ->setModel(new Runtime()) ->setModel(new Framework()) ->setModel(new FrameworkAdapter()) ->setModel(new Deployment()) ->setModel(new Execution()) ->setModel(new Project()) ->setModel(new Webhook()) ->setModel(new Key()) ->setModel(new DevKey()) ->setModel(new MockNumber()) ->setModel(new AuthProvider()) ->setModel(new Platform()) ->setModel(new Variable()) ->setModel(new Country()) ->setModel(new Continent()) ->setModel(new Language()) ->setModel(new Currency()) ->setModel(new Phone()) ->setModel(new HealthAntivirus()) ->setModel(new HealthQueue()) ->setModel(new HealthStatus()) ->setModel(new HealthCertificate()) ->setModel(new HealthTime()) ->setModel(new HealthVersion()) ->setModel(new Metric()) ->setModel(new MetricBreakdown()) ->setModel(new UsageDatabases()) ->setModel(new UsageDatabase()) ->setModel(new UsageTable()) ->setModel(new UsageCollection()) ->setModel(new UsageUsers()) ->setModel(new UsageStorage()) ->setModel(new UsageBuckets()) ->setModel(new UsageFunctions()) ->setModel(new UsageFunction()) ->setModel(new UsageSites()) ->setModel(new UsageSite()) ->setModel(new UsageProject()) ->setModel(new Headers()) ->setModel(new Specification()) ->setModel(new Rule()) ->setModel(new TemplateSMS()) ->setModel(new TemplateEmail()) ->setModel(new ConsoleVariables()) ->setModel(new MFAChallenge()) ->setModel(new MFARecoveryCodes()) ->setModel(new MFAType()) ->setModel(new MFAFactors()) ->setModel(new Provider()) ->setModel(new Message()) ->setModel(new Topic()) ->setModel(new Transaction()) ->setModel(new Subscriber()) ->setModel(new Target()) ->setModel(new Migration()) ->setModel(new MigrationReport()) ->setModel(new MigrationFirebaseProject()) // Tests (keep last) ->setModel(new Mock()); parent::__construct($response); } /** * HTTP content types */ public const CONTENT_TYPE_YAML = 'application/x-yaml'; public const CONTENT_TYPE_NULL = 'null'; public const CONTENT_TYPE_MULTIPART = 'multipart/form-data'; /** * List of defined output objects */ protected $models = []; /** * Set Model Object * * @return self */ public function setModel(Model $instance): Response { $this->models[$instance->getType()] = $instance; return $this; } /** * Get Model Object * * @param string $key * @return Model * @throws Exception */ public function getModel(string $key): Model { if (!isset($this->models[$key])) { throw new Exception('Undefined model: ' . $key); } return $this->models[$key]; } /** * Get Models List * * @return Model[] */ public function getModels(): array { return $this->models; } public function applyFilters(array $data, string $model): array { foreach ($this->filters as $filter) { $data = $filter->parse($data, $model); } return $data; } /** * Validate response objects and outputs * the response according to given format type * * @param Document $document * @param string $model * * return void * @throws Exception */ public function dynamic(Document $document, string $model): void { $output = $this->output(clone $document, $model); $output = $this->applyFilters($output, $model); switch ($this->getContentType()) { case self::CONTENT_TYPE_JSON: try { $this->json(!empty($output) ? $output : new \stdClass()); } catch (JsonException $e) { throw new Exception('Failed to parse response: ' . $e->getMessage(), 400); } break; case self::CONTENT_TYPE_YAML: $this->yaml(!empty($output) ? $output : new \stdClass()); break; case self::CONTENT_TYPE_NULL: break; case self::CONTENT_TYPE_MULTIPART: $this->multipart(!empty($output) ? $output : new \stdClass()); break; default: if ($model === self::MODEL_NONE) { $this->noContent(); } else { $this->json(!empty($output) ? $output : new \stdClass()); } break; } } /** * Generate valid response object from document data * * @param Document $document * @param string $model * * return array * @return array * @throws Exception */ public function output(Document $document, string $model): array { $data = clone $document; $model = $this->getModel($model); $output = []; $data = $model->filter($data); if ($model->isAny()) { $this->payload = $data->getArrayCopy(); return $this->payload; } foreach ($model->getRules() as $key => $rule) { if (!$data->isSet($key) && $rule['required']) { // do not set attribute in response if not required if (\array_key_exists('default', $rule)) { $data->setAttribute($key, $rule['default']); } else { throw new Exception('Model ' . $model->getName() . ' is missing response key: ' . $key); } } if (!$data->isSet($key) && !$rule['required']) { // set output key null if data key is not set and required is false $output[$key] = null; continue; } if ($rule['array']) { if (!is_array($data[$key])) { throw new Exception($key . ' must be an array of type ' . $rule['type']); } foreach ($data[$key] as $index => $item) { if ($item instanceof Document) { if (\is_array($rule['type'])) { foreach ($rule['type'] as $type) { $condition = false; foreach ($this->getModel($type)->conditions as $attribute => $val) { $condition = $item->getAttribute($attribute) === $val; if (!$condition) { break; } } if ($condition) { $ruleType = $type; break; } } } else { $ruleType = $rule['type']; } if (!array_key_exists($ruleType, $this->models)) { throw new Exception('Missing model for rule: ' . $ruleType); } $data[$key][$index] = $this->output($item, $ruleType); } } } else { if ($data[$key] instanceof Document) { $data[$key] = $this->output($data[$key], $rule['type']); } } if ($rule['sensitive']) { $roles = Authorization::getRoles(); $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); if ((!$isPrivilegedUser && !$isAppUser) && !self::$showSensitive) { $data->setAttribute($key, ''); } } $output[$key] = $data[$key]; } $this->payload = $output; return $this->payload; } /** * Output response * * Generate HTTP response output including the response header (+cookies) and body and prints them. * * @param string $body * * @return void */ public function file(string $body = ''): void { $this->payload = [ 'payload' => $body ]; $this->send($body); } /** * YAML * * This helper is for sending YAML HTTP response. * It sets relevant content type header ('application/x-yaml') and convert a PHP array ($data) to valid YAML using native yaml_parse * * @see https://en.wikipedia.org/wiki/YAML * * @param array $data * * @return void * @throws Exception */ public function yaml(array $data): void { if (!extension_loaded('yaml')) { throw new Exception('Missing yaml extension. Learn more at: https://www.php.net/manual/en/book.yaml.php'); } $this ->setContentType(Response::CONTENT_TYPE_YAML) ->send(\yaml_emit($data, YAML_UTF8_ENCODING)); } /** * Multipart * * This helper is for sending multipart/form-data HTTP response. * It sets relevant content type header ('multipart/form-data') and convert a PHP array ($data) to valid Multipart using BodyMultipart * * @param array $data * * @return void */ public function multipart(array $data): void { $multipart = new BodyMultipart(); foreach ($data as $key => $value) { $multipart->setPart($key, $value); } $this ->setContentType($multipart->exportHeader()) ->send($multipart->exportBody()); } /** * JSON * * This helper is for sending JSON HTTP response. * It sets relevant content type header ('application/json') and convert a PHP array ($data) to valid JSON using native json_encode * * @see http://en.wikipedia.org/wiki/JSON * * @param mixed $data * @return void */ public function json($data): void { if (!is_array($data) && !$data instanceof \stdClass) { throw new \Exception('Response body is not a valid JSON object.'); } $this ->setContentType(Response::CONTENT_TYPE_JSON, self::CHARSET_UTF8) ->send(\json_encode($data, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR)); } /** * @return array */ public function getPayload(): array { return $this->payload; } /** * Function to add a response filter, the order of filters are first in - first out. * * @param $filter - the response filter to set * * @return void */ public function addFilter(Filter $filter): void { $this->filters[] = $filter; } /** * Return the currently set filter * * @return Filter */ public function getFilters(): array { return $this->filters; } /** * Reset filters * * @return void */ public function resetFilters(): void { $this->filters = []; } /** * Check if a filter has been set * * @return bool */ public function hasFilters(): bool { return !empty($this->filters); } /** * Static wrapper to show sensitive data in response * * @param callable The callback to show sensitive information for * @return array */ public static function showSensitive(callable $callback): array { try { self::$showSensitive = true; return $callback(); } finally { self::$showSensitive = false; } } }