diff --git a/composer.json b/composer.json index c8185b0..e7c207e 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,8 @@ { - "name": "symbiotic/full", + "name": "symbiotic/full-dev", "description": "Полная сборка пакетов фреймворка Symbiotic.", "license": "BSD-3-Clause", - "version": "1.2.12", + "version": "1.3.0", "authors": [{ "name": "Surkov Sergey", "role": "creator" @@ -22,6 +22,7 @@ "psr/simple-cache": "1.*", "nyholm/psr7": ">=1.3.2", "symbiotic/ui_http_kernel": "1.*", + "symbiotic/ui_form": "1.*", "ext-json": "*" }, "autoload": { @@ -29,10 +30,10 @@ "Symbiotic\\": "src/" }, "files": [ - "src/core_helpers.php", "src/apps_helpers.php", - "src/settings_helper.php", - "src/http_kernel_helpers.php" + "src/core_helpers.php", + "src/http_kernel_helpers.php", + "src/settings_helper.php" ] }, "suggest": { @@ -52,6 +53,8 @@ "symbiotic/event": "1.*", "symbiotic/event-contracts": "1.*", "symbiotic/filesystem": "1.*", + "symbiotic/form-contracts": "1.*", + "symbiotic/form": "1.*", "symbiotic/http": "1.*", "symbiotic/http-cookie": "1.*", "symbiotic/http-kernel": "1.*", diff --git a/src/Auth/AuthService.php b/src/Auth/AuthService.php index 893bcc6..630340e 100644 --- a/src/Auth/AuthService.php +++ b/src/Auth/AuthService.php @@ -148,7 +148,7 @@ public function clearIdentity() /** * Returns the identity from storage or null if no identity is available * - * @return mixed|null + * @return mixed|UserInterface|null * @throws \Exception */ public function getIdentity() @@ -164,4 +164,18 @@ public function getIdentity() } throw new \Exception('Некорректные данные сессии!'); } + + /** + * @param null $fullName + * @param null $id + * @return User + * @todo Надо перенести + */ + public function getGuestUser($fullName = null, $id = null) + { + if(!$fullName) { + $fullName = 'Guest'; + } + return new User(UserInterface::GROUP_GUEST, $fullName, $id); + } } \ No newline at end of file diff --git a/src/Auth/AuthServiceInterface.php b/src/Auth/AuthServiceInterface.php index 1b501af..f19524f 100644 --- a/src/Auth/AuthServiceInterface.php +++ b/src/Auth/AuthServiceInterface.php @@ -31,7 +31,7 @@ public function hasIdentity(): bool; /** * Returns the authenticated identity or null if no identity is available * - * @return mixed|null + * @return mixed|null|UserInterface * @throws */ public function getIdentity(); diff --git a/src/Auth/UserInterface.php b/src/Auth/UserInterface.php index 63631c7..bd8d8a5 100644 --- a/src/Auth/UserInterface.php +++ b/src/Auth/UserInterface.php @@ -9,7 +9,7 @@ interface UserInterface const GROUP_MANAGER = 1; // - const GROUP_ADMIN = 69; + const GROUP_ADMIN = 365; /** * @return int diff --git a/src/Container/BaseContainerInterface.php b/src/Container/BaseContainerInterface.php index 32e9340..5db0610 100644 --- a/src/Container/BaseContainerInterface.php +++ b/src/Container/BaseContainerInterface.php @@ -27,6 +27,7 @@ interface BaseContainerInterface * Get item by key * * @param string $key + * @return mixed|null */ public function get(string $key); diff --git a/src/Core/Core.php b/src/Core/Core.php index 5e75090..9d66ef9 100644 --- a/src/Core/Core.php +++ b/src/Core/Core.php @@ -219,7 +219,7 @@ public function then(\Closure $then): void * * @todo: Метод используется один раз, нужен ли он? */ - public function getBasePath($path = '') + public function getBasePath($path = ''):string { return $this->base_path . ($path ? \_S\DS . $path : $path); } diff --git a/src/Core/CoreInterface.php b/src/Core/CoreInterface.php index a225113..b2659f8 100644 --- a/src/Core/CoreInterface.php +++ b/src/Core/CoreInterface.php @@ -82,4 +82,14 @@ public function then(\Closure $loader):void; * @used-by Core::run() */ public function runNext():void; + + /** + * Get the base path of the Laravel installation. + * + * @param string $path Optionally, a path to append to the base path + * @return string + * + * @todo: Метод используется один раз, нужен ли он? + */ + public function getBasePath($path = ''):string; } diff --git a/src/Core/View/View.php b/src/Core/View/View.php index 85a54ee..29854f0 100644 --- a/src/Core/View/View.php +++ b/src/Core/View/View.php @@ -129,8 +129,7 @@ function render($template, array $vars = null) */ function lang(string $message, array $vars = null, $lang = null): string { - return $message; - // todo return \_S\lang(appendApp($message),$vars,$lang); + return \_S\lang(appendApp($message), $vars, $lang); } diff --git a/src/Form/FIelds/Boolean.php b/src/Form/FIelds/Boolean.php new file mode 100644 index 0000000..cc640bc --- /dev/null +++ b/src/Form/FIelds/Boolean.php @@ -0,0 +1,17 @@ + '', + 'description' => '', + 'name' => '', + 'meta' => [], + 'value' => null, + 'default' => null, + 'placeholder' => '', + 'attributes' => [], + 'validators' => [], + 'error' => null, + ]; + + + public function __construct(array $data = []) + { + $this->data = array_merge($this->data, $data); + } + + + public function setName(string $name): FillableInterface + { + $this->data['name'] = $name; + } + + public function setLabel(string $label = ''): FillableInterface + { + $this->data['label'] = $label; + } + + /** + * @param array|string $value + * @return $this + */ + public function setValue($value): FillableInterface + { + $this->data['value'] = $value; + return $this; + } + + /** + * @param string $description + * @return FillableInterface + */ + public function setDescription(string $description): FillableInterface + { + $this->data['description'] = $description; + return $this; + } + + /** + * @param array|string $default + * @return $this + */ + public function setDefault($default): FillableInterface + { + $this->data['default'] = $default; + + return $this; + } + + /** + * @param string $key + * @param $value + */ + public function setAttribute(string $key, $value) + { + $this->data['attributes'][$key] = $value; + } + + + /** + * @return string + */ + public function getName(): string + { + return $this->data['name']; + } + + /** + * @return string + */ + public function getLabel(): string + { + return $this->data['label']; + } + + /** + * @return array|mixed|string|null + */ + public function getValue() + { + return $this->data['value']; + } + + public function getDotName(): string + { + return trim(\str_replace(['][', ']', '['], ['.', '.', '.'], $this->data['name']), '.'); + } + + /** + * @return array|mixed|string|null + */ + public function getDefault() + { + return $this->data['default']; + } + + public function getDescription(): string + { + return $this->data['description']; + } + + public function getAttributesHtml(): string + { + $attributes = []; + foreach ($this->getAttributes() as $name => $value) { + // TODO: test and fix js attributes with code + $attributes[] = \htmlspecialchars($name) . '="' . \htmlspecialchars($value) . '"'; + } + return \implode(' ', $attributes); + } + + + public function getAttributes(): array + { + return $this->data['attributes']; + } + + + public function getValidators(): array + { + return $this->data['validators']; + } + + /** + * @param string|array $value + * @return bool + */ + public function validate($value): bool + { + /** + * @var Validator $validator + */ + foreach ($this->data['validators'] as $validator) { + if (!$validator->validate($value)) { + $this->data['error'] .= $validator->getError(); + return false; + // todo: array errors... + } + } + return true; + } + + /** + * @return string|null + */ + public function getError(): ?string + { + return $this->data['error']; + } + + public function placeholder(string $val): FillableInterface + { + $this->data['placeholder'] = $val; + } + + public function getPlaceholder(): string + { + return $this->data['placeholder']; + } + + + public function jsonSerialize() + { + return array_merge($this->data, ['template' => $this->template]); + } + + public function __toString() + { + + return $this->render(); + } + + public function render($template = null) + { + if (!$template) { + $template = $this->template; + if (false === strpos($template, '::')) { + $template = FormBuilder::getTemplatesPackageId() . '::' . $template; + } + } + + return view($template, ['field' => $this])->fetch(); + } +} \ No newline at end of file diff --git a/src/Form/FIelds/FieldSelectable.php b/src/Form/FIelds/FieldSelectable.php new file mode 100644 index 0000000..5d23a7c --- /dev/null +++ b/src/Form/FIelds/FieldSelectable.php @@ -0,0 +1,30 @@ +data['variants'] = []; + parent::__construct($data); + } + + /** + * @param array $variants + */ + public function variants(array $variants) + { + $this->data['variants'] = $variants; + } + + /** + * @return array + */ + public function getVariants(): array + { + return $this->data['variants']; + } +} \ No newline at end of file diff --git a/src/Form/FIelds/Input.php b/src/Form/FIelds/Input.php new file mode 100644 index 0000000..9826d16 --- /dev/null +++ b/src/Form/FIelds/Input.php @@ -0,0 +1,20 @@ +data['attributes']['type'] = 'text'; + parent::__construct($data); + } + + +} \ No newline at end of file diff --git a/src/Form/FIelds/Radio.php b/src/Form/FIelds/Radio.php new file mode 100644 index 0000000..45a63a3 --- /dev/null +++ b/src/Form/FIelds/Radio.php @@ -0,0 +1,13 @@ + '', + 'title' => '', + 'collapsed' => false, + 'fields' => [], + ]; + + protected string $template = 'fields/group'; + + public function __construct(array $data) + { + + $this->data = array_merge($this->data, $data); + } + + /** + * @param FieldInterface $item + * @return FieldInterface + */ + public function add(FieldInterface $item): FieldInterface + { + $this->data['fields'][] = $item; + } + + /** + * @param $data + */ + public function setValues(array $data) + { + $this->data['fields'] = (new FormBuilder())->setValues($this->data['fields'], $data); + } + + + public function getName(): string + { + return $this->data['name']; + } + + public function getTitle(): string + { + return $this->data['title']; + } + + public function isCollapsed(): bool + { + return (bool)$this->data['collapsed']; + } + + + /** + * @return FieldInterface[] + */ + public function getFields():array + { + return $this->data['fields']; + } + + /** + * @return FieldInterface[] + */ + public function getFieldsArray():array + { + $fields = []; + foreach ($this->data['fields'] as $field) { + if($field instanceof FieldsGroup) { + $fields = array_merge($fields, $field->getFields()); + } else { + $fields[] = $field; + } + } + return $fields; + } + + public function jsonSerialize() + { + return $this->data; + } + + public function __toString() + { + $template = $this->template; + if (false === \strpos($template, '::')) { + $template = FormBuilder::getTemplatesPackageId() . '::' . $template; + } + return view($template, ['field' => $this])->fetch(); + } +} \ No newline at end of file diff --git a/src/Form/FieldsValidator.php b/src/Form/FieldsValidator.php new file mode 100644 index 0000000..6c0334a --- /dev/null +++ b/src/Form/FieldsValidator.php @@ -0,0 +1,67 @@ +fields = $fields; + $this->setData($data); + } + + /** + * @param array $data + * @return $this + */ + public function setData(array $data):FieldsValidator + { + $this->data = collect($data); + + return $this; + } + + /** + * @return array + */ + public function getErrors():array + { + return $this->errors; + } + + public function validate(): bool + { + + $fillable = []; + foreach ($this->fields as $v) { + if($v instanceof FillableInterface) { + $fillable[$v->getDotName()] = $v; + } elseif ($v instanceof FieldsGroup) { + $fillable = array_merge($fillable, $v->getFieldsArray()); + } + } + foreach ($fillable as $field) { + $value = $this->data->get($field->getDotName()); + if(!$field->validate($value)) { + $this->errors[$field->getDotName()] = $field->getError(); + } + } + + return empty($this->errors); + } + +} \ No newline at end of file diff --git a/src/Form/FillableInterface.php b/src/Form/FillableInterface.php new file mode 100644 index 0000000..b215348 --- /dev/null +++ b/src/Form/FillableInterface.php @@ -0,0 +1,92 @@ + '', + 'method' => 'post', + 'encode' => self::ENCTYPE_MULTIPART, + 'fields' => [], + 'attributes' => [] + ]; + + protected $template = 'form/form'; + + public function __construct(array $data = [], FormBuilder $formBuilder = null) + { + $this->helper = $formBuilder ? $formBuilder : new FormBuilder(); + if (!empty($data['fields']) && is_array($data['fields'])) { + $data['fields'] = $this->helper->fromArray($data['fields']); + } + + $this->data = array_merge($this->data, $data); + } + + public function setAction(string $action): FormInterface + { + $this->data['action'] = $action; + + return $this; + } + + public function getAction():string + { + return $this->data['action']; + } + + public function getMethod():string{ + return $this->data['method']; + } + + public function setMethod(string $method): FormInterface + { + if(!in_array(strtolower($method),['get','post'])) { + throw new \Exception(' Invalid method ['.$method.'], only get, post!'); + } + + $this->data['method'] = strtolower($method); + } + + /** + * @param $type + * @param $data + * @return FieldInterface + */ + public function addField(string $type, array $data): FieldInterface + { + $field = $this->helper->createField($type, $data); + $this->data['fields'][] = $field; + + return $field; + } + + public function setValues(array $data) + { + $this->helper->setValues($this->data['fields'],$data); + } + + /** + * @return array + * @todo Надо будет сделать на классах поля и отдавать их коллекцию + * но пока и так сойдет) + */ + public function getFields(): array + { + return $this->data['fields']; + } + + public function getValidator(array $data = []): FieldsValidator + { + return new FieldsValidator($this->data['fields'], $data); + } + + public function render($template = null) + { + if (!$template) { + $template = $this->template; + if (false === strpos($template, '::')) { + $template = FormBuilder::getTemplatesPackageId() . '::' . $template; + } + } + + return view($template, ['form' => $this])->fetch(); + } + +} \ No newline at end of file diff --git a/src/Form/FormBuilder.php b/src/Form/FormBuilder.php new file mode 100644 index 0000000..efeeb0a --- /dev/null +++ b/src/Form/FormBuilder.php @@ -0,0 +1,170 @@ + Input::class, + 'select' => Select::class, + 'radio' => Radio::class, + 'checkbox' => Checkbox::class, + 'button' => Button::class, + 'bool' => Boolean::class, + /** and virtual input types {@see createField()} **/ + ]; + + if (function_exists('_S\\event')) { + \_S\event($this); + } + } + } + + public static function setTemplatesPackageId(string $package_id) + { + static::$templatesPackageId = $package_id; + } + + public static function getTemplatesPackageId(): string + { + return static::$templatesPackageId; + } + + /** + * @param string $type the field type must include the application prefix: filesystems::path + * @param string $class className implements {@see FieldInterface, FillableInterface} + */ + public function addType(string $type, string $class) + { + static::$types[$type] = $class; + } + + public function text(array $data = []): FieldInterface + { + return $this->createField(FieldInterface::TEXT, $data); + } + + /** + * @param string $type + * @param array $data = [ + * 'name' => 'string', + * 'label' => 'string', + * 'value' => null, + * 'attributes' => [], + * 'validators' => [], + * ] + * @return FieldInterface + * @throws \Exception + */ + public function createField(string $type, array $data): FieldInterface + { + $types = static::$types; + + + if (isset($types[$type])) { + $class = $types[$type]; + return new $class($data); + } elseif (in_array($type, ['text', 'hidden', 'file', 'number', 'submit', 'password', 'url', 'date', 'email'])) { + $class = $types['input']; + if (!isset($data['attributes'])) { + $data['attributes'] = []; + } + $data['attributes']['type'] = $type; + return new $class($data); + + } + throw new \Exception('Field type [' . $type . '] not found!'); + } + + /** + * @return array|FieldInterface[] + */ + public function fromArray(array $fields): array + { + if (!empty($fields) && is_array($fields)) { + foreach ($fields as &$v) { + if (is_array($v)) { + $v = $this->createField($v['type'], $v); + } + } + unset($v); + } + return $fields; + } + + + public function setValues(array $fields, array $data) + { + $data = collect($data); + foreach ($fields as $v) { + if($v instanceof FillableInterface) { + $name = $v->getDotName(); + $v->setValue($data->get($name)); + } elseif ($v instanceof FieldsGroup) { + $v->setValues($data); + } + } + + return $fields; + } + + public function textarea(array $data = []): FieldInterface + { + return $this->createField(FieldInterface::TEXTAREA, $data); + } + + public function select(array $data = []): FieldInterface + { + return $this->createField(FieldInterface::SELECT, $data); + } + + public function radio(array $data = []): FieldInterface + { + return $this->createField(FieldInterface::RADIO, $data); + } + + public function checkbox(array $data = []): FieldInterface + { + return $this->createField(FieldInterface::CHECKBOX, $data); + } + + public function hidden(array $data = []): FieldInterface + { + return $this->createField(FieldInterface::HIDDEN, $data); + } + + public function file(array $data = []): FieldInterface + { + return $this->createField(FieldInterface::FILE, $data); + } + + public function group(string $title, string $name = null, array $fields = []): FieldInterface + { + return $this->createField(FieldInterface::GROUP, compact('title', 'name', 'fields')); + } + + public function submit(array $data = []): FieldInterface + { + return $this->createField(FieldInterface::GROUP, $data); + } + +} \ No newline at end of file diff --git a/src/Form/FormInterface.php b/src/Form/FormInterface.php new file mode 100644 index 0000000..cb66d3a --- /dev/null +++ b/src/Form/FormInterface.php @@ -0,0 +1,32 @@ +data = $data; + } + + /** + * @return null|string + */ + public function getError() + { + return $this->data['message']; + } + + abstract public function validate($value): bool; + +} \ No newline at end of file diff --git a/src/Http/Kernel/RoutingHandler.php b/src/Http/Kernel/RoutingHandler.php index 666ccfe..b9383f2 100644 --- a/src/Http/Kernel/RoutingHandler.php +++ b/src/Http/Kernel/RoutingHandler.php @@ -43,6 +43,10 @@ public function handle(ServerRequestInterface $request): ResponseInterface $route = $app['router']->match($request->getMethod(), $path); } if ($route) { + foreach ($route->getParams() as $k => $v) { + $request = $request->withAttribute($k, $v); + } + /** * @todo наверно надо перенести отработку в {@see RouteHandler::handle()} после загрузки самого приложения */ diff --git a/src/Packages/PackageConfig.php b/src/Packages/PackageConfig.php index 0dd8214..19af4cd 100644 --- a/src/Packages/PackageConfig.php +++ b/src/Packages/PackageConfig.php @@ -24,7 +24,7 @@ class PackageConfig implements ArrayContainerInterface * 'settings_controller' => '\PAck\MySettingsController', * // or * 'settings' => [ - * ['field_name' => 'name', 'type' => 1 ], // {@see \Symbiotic\Settings\SettingsFormInterface} + * ['field_name' => 'name', 'type' => 1 ], // {@see \Symbiotic\Form\FormInterface} * ] * .... * ] diff --git a/src/Packages/ResourcesRepository.php b/src/Packages/ResourcesRepository.php index 2692f98..5980ec7 100644 --- a/src/Packages/ResourcesRepository.php +++ b/src/Packages/ResourcesRepository.php @@ -80,7 +80,7 @@ protected function getPathTypeFileStream(string $package_id, string $path, strin if (isset($assets[$path_type])) { $full_path = $assets[$path_type] . '/' . ltrim($path, '/\\'); if (!\is_readable($full_path) || !($res = \fopen($full_path, 'r'))) { - throw new ResourceException('File is not exists or not readable!', $full_path); + throw new ResourceException('File is not exists or not readable!'.$full_path, $full_path); } return $this->factory->createStreamFromResource($res); } diff --git a/src/Settings/SettingsBootstrap.php b/src/Settings/SettingsBootstrap.php index 0cc2b10..0f48e4a 100644 --- a/src/Settings/SettingsBootstrap.php +++ b/src/Settings/SettingsBootstrap.php @@ -6,6 +6,7 @@ use Symbiotic\Core\BootstrapInterface; use Symbiotic\Core\Config; use Symbiotic\Core\CoreInterface; +use Symbiotic\Filesystem\FilesystemManagerInterface; use function _S\collect; use function _S\settings; @@ -68,9 +69,29 @@ public function bootstrap(CoreInterface $core): void } } + $core->instance(SettingsInterface::class, $core[SettingsRepositoryInterface::class]->get('core')); + } + + // append filesystems + if ($core[SettingsRepositoryInterface::class]->has('filesystems')) { + /** + * @var SettingsInterface $core_settings + * @var Config $config + */ + $filesystems_settings = $core[SettingsRepositoryInterface::class]->get('filesystems'); + if(!empty($filesystems_settings) && is_array($filesystems_settings)) { + $core->afterResolving(FilesystemManagerInterface::class, function (FilesystemManagerInterface $manager) use ($filesystems_settings, $config) { + $filesystems = $config->get('filesystems', []); + if(!isset($filesystems['disks'])) { + $filesystems['disks'] = []; + } + $filesystems['disks'] = array_merge($filesystems['disks'], $filesystems_settings); + $config->set('filesystems', $filesystems); + + }); + } - $core->instance(SettingsInterface::class, $core[SettingsRepositoryInterface::class]->get('core')); } } } \ No newline at end of file diff --git a/src/core_helpers.php b/src/core_helpers.php index 7ed7a89..d151e9e 100644 --- a/src/core_helpers.php +++ b/src/core_helpers.php @@ -14,7 +14,7 @@ function core($abstract = null, array $parameters = null) { - $core = Core::getInstance(); + $core = Core::getInstance(); if (is_null($abstract)) { return $core; } @@ -98,7 +98,17 @@ function route($name, $parameters = [], $absolute = true) */ function lang(string $message, array $vars = null, $lang = null): string { + //todo: translate + if(is_array($vars)) { + $replaces = []; + foreach ($vars as $k => $v) { + $replaces[':'.(string)$k] = $v; + } + return str_replace(array_keys($replaces), $replaces, $message); + } + return $message; + } if (!function_exists('_S\\view')) {