diff --git a/Admin/Install/attributes.json b/Admin/Install/attributes.json new file mode 100644 index 0000000..f5242d1 --- /dev/null +++ b/Admin/Install/attributes.json @@ -0,0 +1,23 @@ +[ + { + "name": "category", + "l11n": { + "en": "Category", + "de": "Kategorie" + }, + "value_type": "int", + "is_custom_allowed": false, + "validation_pattern": "", + "is_required": true, + "default_value": "", + "values": [ + { + "value": 1, + "l11n": { + "en": "Todo", + "de": "Todo" + } + } + ] + } +] \ No newline at end of file diff --git a/Admin/Install/db.json b/Admin/Install/db.json index 9159586..0d53712 100755 --- a/Admin/Install/db.json +++ b/Admin/Install/db.json @@ -104,6 +104,218 @@ } } }, + "task_attr_type": { + "name": "task_attr_type", + "fields": { + "task_attr_type_id": { + "name": "task_attr_type_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "task_attr_type_name": { + "name": "task_attr_type_name", + "type": "VARCHAR(255)", + "null": false + }, + "task_attr_type_fields": { + "name": "task_attr_type_fields", + "type": "INT(11)", + "null": false + }, + "task_attr_type_custom": { + "name": "task_attr_type_custom", + "type": "TINYINT(1)", + "null": false + }, + "task_attr_type_required": { + "description": "Every task must have this attribute type if set to true.", + "name": "task_attr_type_required", + "type": "TINYINT(1)", + "null": false + }, + "task_attr_type_pattern": { + "description": "This is a regex validation pattern.", + "name": "task_attr_type_pattern", + "type": "VARCHAR(255)", + "null": false + } + } + }, + "task_attr_type_l11n": { + "name": "task_attr_type_l11n", + "fields": { + "task_attr_type_l11n_id": { + "name": "task_attr_type_l11n_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "task_attr_type_l11n_title": { + "name": "task_attr_type_l11n_title", + "type": "VARCHAR(255)", + "null": false + }, + "task_attr_type_l11n_type": { + "name": "task_attr_type_l11n_type", + "type": "INT(11)", + "null": false, + "foreignTable": "task_attr_type", + "foreignKey": "task_attr_type_id" + }, + "task_attr_type_l11n_lang": { + "name": "task_attr_type_l11n_lang", + "type": "VARCHAR(2)", + "null": false, + "foreignTable": "language", + "foreignKey": "language_639_1" + } + } + }, + "task_attr_value": { + "name": "task_attr_value", + "fields": { + "task_attr_value_id": { + "name": "task_attr_value_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "task_attr_value_default": { + "name": "task_attr_value_default", + "type": "TINYINT(1)", + "null": false + }, + "task_attr_value_valuetype": { + "name": "task_attr_value_valuetype", + "type": "INT(11)", + "null": false + }, + "task_attr_value_valueStr": { + "name": "task_attr_value_valueStr", + "type": "VARCHAR(255)", + "null": true, + "default": null + }, + "task_attr_value_valueInt": { + "name": "task_attr_value_valueInt", + "type": "INT(11)", + "null": true, + "default": null + }, + "task_attr_value_valueDec": { + "name": "task_attr_value_valueDec", + "type": "DECIMAL(19,5)", + "null": true, + "default": null + }, + "task_attr_value_valueDat": { + "name": "task_attr_value_valueDat", + "type": "DATETIME", + "null": true, + "default": null + }, + "task_attr_value_unit": { + "name": "task_attr_value_unit", + "type": "VARCHAR(255)", + "null": false + } + } + }, + "task_attr_value_l11n": { + "name": "task_attr_value_l11n", + "fields": { + "task_attr_value_l11n_id": { + "name": "task_attr_value_l11n_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "task_attr_value_l11n_title": { + "name": "task_attr_value_l11n_title", + "type": "VARCHAR(255)", + "null": false + }, + "task_attr_value_l11n_value": { + "name": "task_attr_value_l11n_value", + "type": "INT(11)", + "null": false, + "foreignTable": "task_attr_value", + "foreignKey": "task_attr_value_id" + }, + "task_attr_value_l11n_lang": { + "name": "task_attr_value_l11n_lang", + "type": "VARCHAR(2)", + "null": false, + "foreignTable": "language", + "foreignKey": "language_639_1" + } + } + }, + "task_attr_default": { + "name": "task_attr_default", + "fields": { + "task_attr_default_id": { + "name": "task_attr_default_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "task_attr_default_type": { + "name": "task_attr_default_type", + "type": "INT(11)", + "null": false, + "foreignTable": "task_attr_type", + "foreignKey": "task_attr_type_id" + }, + "task_attr_default_value": { + "name": "task_attr_default_value", + "type": "INT(11)", + "null": false, + "foreignTable": "task_attr_value", + "foreignKey": "task_attr_value_id" + } + } + }, + "task_attr": { + "name": "task_attr", + "fields": { + "task_attr_id": { + "name": "task_attr_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "task_attr_task": { + "name": "task_attr_task", + "type": "INT(11)", + "null": false, + "foreignTable": "task", + "foreignKey": "task_id" + }, + "task_attr_type": { + "name": "task_attr_type", + "type": "INT(11)", + "null": false, + "foreignTable": "task_attr_type", + "foreignKey": "task_attr_type_id" + }, + "task_attr_value": { + "name": "task_attr_value", + "type": "INT(11)", + "null": true, + "default": null, + "foreignTable": "task_attr_value", + "foreignKey": "task_attr_value_id" + } + } + }, "task_media": { "name": "task_media", "fields": { diff --git a/Admin/Installer.php b/Admin/Installer.php index 4510c2f..603fbb8 100755 --- a/Admin/Installer.php +++ b/Admin/Installer.php @@ -14,7 +14,13 @@ declare(strict_types=1); namespace Modules\Tasks\Admin; +use phpOMS\Application\ApplicationAbstract; +use phpOMS\Config\SettingsInterface; +use phpOMS\Message\Http\HttpRequest; +use phpOMS\Message\Http\HttpResponse; use phpOMS\Module\InstallerAbstract; +use phpOMS\Module\ModuleInfo; +use phpOMS\Uri\HttpUri; /** * Installer class. @@ -33,4 +39,161 @@ final class Installer extends InstallerAbstract * @since 1.0.0 */ public const PATH = __DIR__; + + /** + * {@inheritdoc} + */ + public static function install(ApplicationAbstract $app, ModuleInfo $info, SettingsInterface $cfgHandler) : void + { + parent::install($app, $info, $cfgHandler); + + /* Attributes */ + $fileContent = \file_get_contents(__DIR__ . '/Install/attributes.json'); + if ($fileContent === false) { + return; + } + + $attributes = \json_decode($fileContent, true); + $attrTypes = self::createTaskAttributeTypes($app, $attributes); + $attrValues = self::createTaskAttributeValues($app, $attrTypes, $attributes); + } + + /** + * Install default attribute types + * + * @param ApplicationAbstract $app Application + * @param array, is_required?:bool, is_custom_allowed?:bool, validation_pattern?:string, value_type?:string, values?:array}> $attributes Attribute definition + * + * @return array + * + * @since 1.0.0 + */ + private static function createTaskAttributeTypes(ApplicationAbstract $app, array $attributes) : array + { + /** @var array $taskAttrType */ + $taskAttrType = []; + + /** @var \Modules\Tasks\Controller\ApiController $module */ + $module = $app->moduleManager->getModuleInstance('Tasks'); + + /** @var array $attribute */ + foreach ($attributes as $attribute) { + $response = new HttpResponse(); + $request = new HttpRequest(new HttpUri('')); + + $request->header->account = 1; + $request->setData('name', $attribute['name'] ?? ''); + $request->setData('title', \reset($attribute['l11n'])); + $request->setData('language', \array_keys($attribute['l11n'])[0] ?? 'en'); + $request->setData('is_required', $attribute['is_required'] ?? false); + $request->setData('is_custom_allowed', $attribute['is_custom_allowed'] ?? false); + $request->setData('validation_pattern', $attribute['validation_pattern'] ?? ''); + + $module->apiTaskAttributeTypeCreate($request, $response); + + $responseData = $response->get(''); + if (!\is_array($responseData)) { + continue; + } + + $taskAttrType[$attribute['name']] = !\is_array($responseData['response']) + ? $responseData['response']->toArray() + : $responseData['response']; + + $isFirst = true; + foreach ($attribute['l11n'] as $language => $l11n) { + if ($isFirst) { + $isFirst = false; + continue; + } + + $response = new HttpResponse(); + $request = new HttpRequest(new HttpUri('')); + + $request->header->account = 1; + $request->setData('title', $l11n); + $request->setData('language', $language); + $request->setData('type', $taskAttrType[$attribute['name']]['id']); + + $module->apiTaskAttributeTypeL11nCreate($request, $response); + } + } + + return $taskAttrType; + } + + /** + * Create default attribute values for types + * + * @param ApplicationAbstract $app Application + * @param array $taskAttrType Attribute types + * @param array, is_required?:bool, is_custom_allowed?:bool, validation_pattern?:string, value_type?:string, values?:array}> $attributes Attribute definition + * + * @return array + * + * @since 1.0.0 + */ + private static function createTaskAttributeValues(ApplicationAbstract $app, array $taskAttrType, array $attributes) : array + { + /** @var array $taskAttrValue */ + $taskAttrValue = []; + + /** @var \Modules\Tasks\Controller\ApiController $module */ + $module = $app->moduleManager->getModuleInstance('Tasks'); + + foreach ($attributes as $attribute) { + $taskAttrValue[$attribute['name']] = []; + + /** @var array $value */ + foreach ($attribute['values'] as $value) { + $response = new HttpResponse(); + $request = new HttpRequest(new HttpUri('')); + + $request->header->account = 1; + $request->setData('value', $value['value'] ?? ''); + $request->setData('value_type', $attribute['value_type'] ?? 0); + $request->setData('unit', $value['unit'] ?? ''); + $request->setData('default', isset($attribute['values']) && !empty($attribute['values'])); + $request->setData('attributetype', $taskAttrType[$attribute['name']]['id']); + + if (isset($value['l11n']) && !empty($value['l11n'])) { + $request->setData('title', \reset($value['l11n'])); + $request->setData('language', \array_keys($value['l11n'])[0] ?? 'en'); + } + + $module->apiTaskAttributeValueCreate($request, $response); + + $responseData = $response->get(''); + if (!\is_array($responseData)) { + continue; + } + + $attrValue = !\is_array($responseData['response']) + ? $responseData['response']->toArray() + : $responseData['response']; + + $taskAttrValue[$attribute['name']][] = $attrValue; + + $isFirst = true; + foreach (($value['l11n'] ?? []) as $language => $l11n) { + if ($isFirst) { + $isFirst = false; + continue; + } + + $response = new HttpResponse(); + $request = new HttpRequest(new HttpUri('')); + + $request->header->account = 1; + $request->setData('title', $l11n); + $request->setData('language', $language); + $request->setData('value', $attrValue['id']); + + $module->apiTaskAttributeValueL11nCreate($request, $response); + } + } + } + + return $taskAttrValue; + } } diff --git a/Controller/ApiController.php b/Controller/ApiController.php index a963273..6aa0e55 100755 --- a/Controller/ApiController.php +++ b/Controller/ApiController.php @@ -29,6 +29,18 @@ use Modules\Tasks\Models\TaskElementMapper; use Modules\Tasks\Models\TaskMapper; use Modules\Tasks\Models\TaskStatus; use Modules\Tasks\Models\TaskType; +use Modules\Tasks\Models\TaskAttribute; +use Modules\Tasks\Models\TaskAttributeMapper; +use Modules\Tasks\Models\TaskAttributeType; +use Modules\Tasks\Models\TaskAttributeTypeL11n; +use Modules\Tasks\Models\TaskAttributeTypeL11nMapper; +use Modules\Tasks\Models\TaskAttributeTypeMapper; +use Modules\Tasks\Models\TaskAttributeValue; +use Modules\Tasks\Models\TaskAttributeValueL11n; +use Modules\Tasks\Models\TaskAttributeValueL11nMapper; +use Modules\Tasks\Models\TaskAttributeValueMapper; +use Modules\Tasks\Models\NullTaskAttributeType; +use Modules\Tasks\Models\NullTaskAttributeValue; use phpOMS\Message\Http\HttpResponse; use phpOMS\Message\Http\RequestStatusCode; use phpOMS\Message\NotificationLevel; @@ -665,4 +677,371 @@ final class ApiController extends Controller return $element; } + + /** + * Api method to create task attribute + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param mixed $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiTaskAttributeCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void + { + if (!empty($val = $this->validateTaskAttributeCreate($request))) { + $response->set('attribute_create', new FormValidation($val)); + $response->header->status = RequestStatusCode::R_400; + + return; + } + + $attribute = $this->createTaskAttributeFromRequest($request); + $this->createModel($request->header->account, $attribute, TaskAttributeMapper::class, 'attribute', $request->getOrigin()); + $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Attribute', 'Attribute successfully created', $attribute); + } + + /** + * Method to create task attribute from request. + * + * @param RequestAbstract $request Request + * + * @return TaskAttribute + * + * @since 1.0.0 + */ + private function createTaskAttributeFromRequest(RequestAbstract $request) : TaskAttribute + { + $attribute = new TaskAttribute(); + $attribute->task = (int) $request->getData('task'); + $attribute->type = new NullTaskAttributeType((int) $request->getData('type')); + + if ($request->getData('value') !== null) { + $attribute->value = new NullTaskAttributeValue((int) $request->getData('value')); + } else { + $newRequest = clone $request; + $newRequest->setData('value', $request->getData('custom'), true); + + $value = $this->createTaskAttributeValueFromRequest($request); + + $attribute->value = $value; + } + + return $attribute; + } + + /** + * Validate task attribute create request + * + * @param RequestAbstract $request Request + * + * @return array + * + * @since 1.0.0 + */ + private function validateTaskAttributeCreate(RequestAbstract $request) : array + { + $val = []; + if (($val['type'] = empty($request->getData('type'))) + || ($val['value'] = (empty($request->getData('value')) && empty($request->getData('custom')))) + || ($val['task'] = empty($request->getData('task'))) + ) { + return $val; + } + + return []; + } + + /** + * Api method to create task attribute l11n + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param mixed $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiTaskAttributeTypeL11nCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void + { + if (!empty($val = $this->validateTaskAttributeTypeL11nCreate($request))) { + $response->set('attr_type_l11n_create', new FormValidation($val)); + $response->header->status = RequestStatusCode::R_400; + + return; + } + + $attrL11n = $this->createTaskAttributeTypeL11nFromRequest($request); + $this->createModel($request->header->account, $attrL11n, TaskAttributeTypeL11nMapper::class, 'attr_type_l11n', $request->getOrigin()); + $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Attribute type localization', 'Attribute type localization successfully created', $attrL11n); + } + + /** + * Method to create task attribute l11n from request. + * + * @param RequestAbstract $request Request + * + * @return TaskAttributeTypeL11n + * + * @since 1.0.0 + */ + private function createTaskAttributeTypeL11nFromRequest(RequestAbstract $request) : TaskAttributeTypeL11n + { + $attrL11n = new TaskAttributeTypeL11n(); + $attrL11n->type = (int) ($request->getData('type') ?? 0); + $attrL11n->setLanguage((string) ( + $request->getData('language') ?? $request->getLanguage() + )); + $attrL11n->title = (string) ($request->getData('title') ?? ''); + + return $attrL11n; + } + + /** + * Validate task attribute l11n create request + * + * @param RequestAbstract $request Request + * + * @return array + * + * @since 1.0.0 + */ + private function validateTaskAttributeTypeL11nCreate(RequestAbstract $request) : array + { + $val = []; + if (($val['title'] = empty($request->getData('title'))) + || ($val['type'] = empty($request->getData('type'))) + ) { + return $val; + } + + return []; + } + + /** + * Api method to create task attribute type + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param mixed $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiTaskAttributeTypeCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void + { + if (!empty($val = $this->validateTaskAttributeTypeCreate($request))) { + $response->set('attr_type_create', new FormValidation($val)); + $response->header->status = RequestStatusCode::R_400; + + return; + } + + $attrType = $this->createTaskAttributeTypeFromRequest($request); + $this->createModel($request->header->account, $attrType, TaskAttributeTypeMapper::class, 'attr_type', $request->getOrigin()); + + $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Attribute type', 'Attribute type successfully created', $attrType); + } + + /** + * Method to create task attribute from request. + * + * @param RequestAbstract $request Request + * + * @return TaskAttributeType + * + * @since 1.0.0 + */ + private function createTaskAttributeTypeFromRequest(RequestAbstract $request) : TaskAttributeType + { + $attrType = new TaskAttributeType($request->getData('name') ?? ''); + $attrType->setL11n((string) ($request->getData('title') ?? ''), $request->getData('language') ?? ISO639x1Enum::_EN); + $attrType->setFields((int) ($request->getData('fields') ?? 0)); + $attrType->custom = (bool) ($request->getData('custom') ?? false); + $attrType->isRequired = (bool) ($request->getData('is_required') ?? false); + $attrType->validationPattern = (string) ($request->getData('validation_pattern') ?? ''); + + return $attrType; + } + + /** + * Validate task attribute create request + * + * @param RequestAbstract $request Request + * + * @return array + * + * @since 1.0.0 + */ + private function validateTaskAttributeTypeCreate(RequestAbstract $request) : array + { + $val = []; + if (($val['title'] = empty($request->getData('title'))) + || ($val['name'] = empty($request->getData('name'))) + ) { + return $val; + } + + return []; + } + + /** + * Api method to create task attribute value + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param mixed $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiTaskAttributeValueCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void + { + if (!empty($val = $this->validateTaskAttributeValueCreate($request))) { + $response->set('attr_value_create', new FormValidation($val)); + $response->header->status = RequestStatusCode::R_400; + + return; + } + + $attrValue = $this->createTaskAttributeValueFromRequest($request); + $this->createModel($request->header->account, $attrValue, TaskAttributeValueMapper::class, 'attr_value', $request->getOrigin()); + + if ($attrValue->isDefault) { + $this->createModelRelation( + $request->header->account, + (int) $request->getData('attributetype'), + $attrValue->getId(), + TaskAttributeTypeMapper::class, 'defaults', '', $request->getOrigin() + ); + } + + $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Attribute value', 'Attribute value successfully created', $attrValue); + } + + /** + * Method to create task attribute value from request. + * + * @param RequestAbstract $request Request + * + * @return TaskAttributeValue + * + * @since 1.0.0 + */ + private function createTaskAttributeValueFromRequest(RequestAbstract $request) : TaskAttributeValue + { + $type = (int) ($request->getData('attributetype') ?? 0); + + $attrValue = new TaskAttributeValue($type, $request->getData('value')); + $attrValue->isDefault = (bool) ($request->getData('default') ?? false); + + if ($request->getData('title') !== null) { + $attrValue->setL11n($request->getData('title'), $request->getData('language') ?? ISO639x1Enum::_EN); + } + + return $attrValue; + } + + /** + * Validate task attribute value create request + * + * @param RequestAbstract $request Request + * + * @return array + * + * @since 1.0.0 + */ + private function validateTaskAttributeValueCreate(RequestAbstract $request) : array + { + $val = []; + if (($val['attributetype'] = empty($request->getData('attributetype'))) + || ($val['value'] = empty($request->getData('value'))) + ) { + return $val; + } + + return []; + } + + /** + * Api method to create task attribute l11n + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param mixed $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiTaskAttributeValueL11nCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void + { + if (!empty($val = $this->validateTaskAttributeValueL11nCreate($request))) { + $response->set('attr_value_l11n_create', new FormValidation($val)); + $response->header->status = RequestStatusCode::R_400; + + return; + } + + $attrL11n = $this->createTaskAttributeValueL11nFromRequest($request); + $this->createModel($request->header->account, $attrL11n, TaskAttributeValueL11nMapper::class, 'attr_value_l11n', $request->getOrigin()); + $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Attribute type localization', 'Attribute type localization successfully created', $attrL11n); + } + + /** + * Method to create task attribute l11n from request. + * + * @param RequestAbstract $request Request + * + * @return TaskAttributeValueL11n + * + * @since 1.0.0 + */ + private function createTaskAttributeValueL11nFromRequest(RequestAbstract $request) : TaskAttributeValueL11n + { + $attrL11n = new TaskAttributeValueL11n(); + $attrL11n->value = (int) ($request->getData('value') ?? 0); + $attrL11n->setLanguage((string) ( + $request->getData('language') ?? $request->getLanguage() + )); + $attrL11n->title = (string) ($request->getData('title') ?? ''); + + return $attrL11n; + } + + /** + * Validate task attribute l11n create request + * + * @param RequestAbstract $request Request + * + * @return array + * + * @since 1.0.0 + */ + private function validateTaskAttributeValueL11nCreate(RequestAbstract $request) : array + { + $val = []; + if (($val['title'] = empty($request->getData('title'))) + || ($val['value'] = empty($request->getData('value'))) + ) { + return $val; + } + + return []; + } } diff --git a/Controller/BackendController.php b/Controller/BackendController.php index e747aa8..68a96d4 100755 --- a/Controller/BackendController.php +++ b/Controller/BackendController.php @@ -106,7 +106,6 @@ final class BackendController extends Controller implements DashboardElementInte /** @var \Modules\Tasks\Models\Task[] $open */ $open = TaskMapper::getAll() ->with('createdBy') - ->with('taskElements') ->with('tags') ->with('tags/title') ->where('tags/title/language', $response->getLanguage()) diff --git a/Models/AttributeValueType.php b/Models/AttributeValueType.php new file mode 100755 index 0000000..ba5eaa9 --- /dev/null +++ b/Models/AttributeValueType.php @@ -0,0 +1,36 @@ +id = $id; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() : mixed + { + return ['id' => $this->id]; + } +} diff --git a/Models/NullTaskAttributeType.php b/Models/NullTaskAttributeType.php new file mode 100755 index 0000000..f74b717 --- /dev/null +++ b/Models/NullTaskAttributeType.php @@ -0,0 +1,46 @@ +id = $id; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() : mixed + { + return ['id' => $this->id]; + } +} diff --git a/Models/NullTaskAttributeTypeL11n.php b/Models/NullTaskAttributeTypeL11n.php new file mode 100755 index 0000000..e00e168 --- /dev/null +++ b/Models/NullTaskAttributeTypeL11n.php @@ -0,0 +1,47 @@ +id = $id; + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() : mixed + { + return ['id' => $this->id]; + } +} diff --git a/Models/NullTaskAttributeValue.php b/Models/NullTaskAttributeValue.php new file mode 100755 index 0000000..7477c69 --- /dev/null +++ b/Models/NullTaskAttributeValue.php @@ -0,0 +1,46 @@ +id = $id; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() : mixed + { + return ['id' => $this->id]; + } +} diff --git a/Models/Task.php b/Models/Task.php index fe0edd4..3922671 100755 --- a/Models/Task.php +++ b/Models/Task.php @@ -124,6 +124,14 @@ class Task implements \JsonSerializable */ public int $completion = -1; + /** + * Attributes. + * + * @var ItemAttribute[] + * @since 1.0.0 + */ + private array $attributes = []; + /** * Task can be closed by user. * @@ -602,6 +610,73 @@ class Task implements \JsonSerializable $this->type = $type; } + /** + * Add attribute to item + * + * @param ItemAttribute $attribute Note + * + * @return void + * + * @since 1.0.0 + */ + public function addAttribute(ItemAttribute $attribute) : void + { + $this->attributes[] = $attribute; + } + + /** + * Get attributes + * + * @return ItemAttribute[] + * + * @since 1.0.0 + */ + public function getAttributes() : array + { + return $this->attributes; + } + + /** + * Has attribute value + * + * @param string $attrName Attribute name + * @param mixed $attrValue Attribute value + * + * @return bool + * + * @since 1.0.0 + */ + public function hasAttributeValue(string $attrName, mixed $attrValue) : bool + { + foreach ($this->attributes as $attribute) { + if ($attribute->type->name === $attrName && $attribute->value->getValue() === $attrValue) { + return true; + } + } + + return false; + } + + /** + * Get attribute + * + * @param string $attrName Attribute name + * + * @return null|AttributeValue + * + * @since 1.0.0 + */ + public function getAttribute(string $attrName) : ?AttributeValue + { + foreach ($this->attributes as $attribute) { + if ($attribute->type->name === $attrName) { + return $attribute->value; + } + } + + return null; + } + /** * {@inheritdoc} */ diff --git a/Models/TaskAttribute.php b/Models/TaskAttribute.php new file mode 100755 index 0000000..70fb81a --- /dev/null +++ b/Models/TaskAttribute.php @@ -0,0 +1,102 @@ +type = new NullTaskAttributeType(); + $this->value = new NullTaskAttributeValue(); + } + + /** + * Get id + * + * @return int + * + * @since 1.0.0 + */ + public function getId() : int + { + return $this->id; + } + + /** + * {@inheritdoc} + */ + public function toArray() : array + { + return [ + 'id' => $this->id, + 'task' => $this->task, + 'type' => $this->type, + 'value' => $this->value, + ]; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() : mixed + { + return $this->toArray(); + } +} diff --git a/Models/TaskAttributeMapper.php b/Models/TaskAttributeMapper.php new file mode 100755 index 0000000..f00db04 --- /dev/null +++ b/Models/TaskAttributeMapper.php @@ -0,0 +1,74 @@ + + * @since 1.0.0 + */ + public const COLUMNS = [ + 'task_attr_id' => ['name' => 'task_attr_id', 'type' => 'int', 'internal' => 'id'], + 'task_attr_task' => ['name' => 'task_attr_task', 'type' => 'int', 'internal' => 'task'], + 'task_attr_type' => ['name' => 'task_attr_type', 'type' => 'int', 'internal' => 'type'], + 'task_attr_value' => ['name' => 'task_attr_value', 'type' => 'int', 'internal' => 'value'], + ]; + + /** + * Has one relation. + * + * @var array + * @since 1.0.0 + */ + public const OWNS_ONE = [ + 'type' => [ + 'mapper' => TaskAttributeTypeMapper::class, + 'external' => 'task_attr_type', + ], + 'value' => [ + 'mapper' => TaskAttributeValueMapper::class, + 'external' => 'task_attr_value', + ], + ]; + + /** + * Primary table. + * + * @var string + * @since 1.0.0 + */ + public const TABLE = 'task_attr'; + + /** + * Primary field name. + * + * @var string + * @since 1.0.0 + */ + public const PRIMARYFIELD ='task_attr_id'; +} diff --git a/Models/TaskAttributeType.php b/Models/TaskAttributeType.php new file mode 100755 index 0000000..e944257 --- /dev/null +++ b/Models/TaskAttributeType.php @@ -0,0 +1,191 @@ +name = $name; + } + + /** + * Get id + * + * @return int + * + * @since 1.0.0 + */ + public function getId() : int + { + return $this->id; + } + + /** + * Set l11n + * + * @param string|TaskAttributeTypeL11n $l11n Tag article l11n + * @param string $lang Language + * + * @return void + * + * @since 1.0.0 + */ + public function setL11n(string | TaskAttributeTypeL11n $l11n, string $lang = ISO639x1Enum::_EN) : void + { + if ($l11n instanceof TaskAttributeTypeL11n) { + $this->l11n = $l11n; + } elseif (isset($this->l11n) && $this->l11n instanceof TaskAttributeTypeL11n) { + $this->l11n->title = $l11n; + } else { + $this->l11n = new TaskAttributeTypeL11n(); + $this->l11n->title = $l11n; + $this->l11n->setLanguage($lang); + } + } + + /** + * @return string + * + * @since 1.0.0 + */ + public function getL11n() : string + { + return $this->l11n instanceof TaskAttributeTypeL11n ? $this->l11n->title : $this->l11n; + } + + /** + * Set fields + * + * @param int $fields Fields + * + * @return void + * + * @since 1.0.0 + */ + public function setFields(int $fields) : void + { + $this->fields = $fields; + } + + /** + * Get default values + * + * @return array + * + * @sicne 1.0.0 + */ + public function getDefaults() : array + { + return $this->defaults; + } + + /** + * {@inheritdoc} + */ + public function toArray() : array + { + return [ + 'id' => $this->id, + 'name' => $this->name, + 'validationPattern' => $this->validationPattern, + 'custom' => $this->custom, + 'isRequired' => $this->isRequired, + ]; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() : mixed + { + return $this->toArray(); + } +} diff --git a/Models/TaskAttributeTypeL11n.php b/Models/TaskAttributeTypeL11n.php new file mode 100755 index 0000000..7040cc9 --- /dev/null +++ b/Models/TaskAttributeTypeL11n.php @@ -0,0 +1,135 @@ +type = $type; + $this->title = $title; + $this->language = $language; + } + + /** + * Get id + * + * @return int + * + * @since 1.0.0 + */ + public function getId() : int + { + return $this->id; + } + + /** + * Get language + * + * @return string + * + * @since 1.0.0 + */ + public function getLanguage() : string + { + return $this->language; + } + + /** + * Set language + * + * @param string $language Language + * + * @return void + * + * @since 1.0.0 + */ + public function setLanguage(string $language) : void + { + $this->language = $language; + } + + /** + * {@inheritdoc} + */ + public function toArray() : array + { + return [ + 'id' => $this->id, + 'title' => $this->title, + 'type' => $this->type, + 'language' => $this->language, + ]; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() : mixed + { + return $this->toArray(); + } +} diff --git a/Models/TaskAttributeTypeL11nMapper.php b/Models/TaskAttributeTypeL11nMapper.php new file mode 100755 index 0000000..9778bc1 --- /dev/null +++ b/Models/TaskAttributeTypeL11nMapper.php @@ -0,0 +1,57 @@ + + * @since 1.0.0 + */ + public const COLUMNS = [ + 'task_attr_type_l11n_id' => ['name' => 'task_attr_type_l11n_id', 'type' => 'int', 'internal' => 'id'], + 'task_attr_type_l11n_title' => ['name' => 'task_attr_type_l11n_title', 'type' => 'string', 'internal' => 'title', 'autocomplete' => true], + 'task_attr_type_l11n_type' => ['name' => 'task_attr_type_l11n_type', 'type' => 'int', 'internal' => 'type'], + 'task_attr_type_l11n_lang' => ['name' => 'task_attr_type_l11n_lang', 'type' => 'string', 'internal' => 'language'], + ]; + + /** + * Primary table. + * + * @var string + * @since 1.0.0 + */ + public const TABLE = 'task_attr_type_l11n'; + + /** + * Primary field name. + * + * @var string + * @since 1.0.0 + */ + public const PRIMARYFIELD ='task_attr_type_l11n_id'; +} diff --git a/Models/TaskAttributeTypeMapper.php b/Models/TaskAttributeTypeMapper.php new file mode 100755 index 0000000..26be77d --- /dev/null +++ b/Models/TaskAttributeTypeMapper.php @@ -0,0 +1,81 @@ + + * @since 1.0.0 + */ + public const COLUMNS = [ + 'task_attr_type_id' => ['name' => 'task_attr_type_id', 'type' => 'int', 'internal' => 'id'], + 'task_attr_type_name' => ['name' => 'task_attr_type_name', 'type' => 'string', 'internal' => 'name', 'autocomplete' => true], + 'task_attr_type_fields' => ['name' => 'task_attr_type_fields', 'type' => 'int', 'internal' => 'fields'], + 'task_attr_type_custom' => ['name' => 'task_attr_type_custom', 'type' => 'bool', 'internal' => 'custom'], + 'task_attr_type_pattern' => ['name' => 'task_attr_type_pattern', 'type' => 'string', 'internal' => 'validationPattern'], + 'task_attr_type_required' => ['name' => 'task_attr_type_required', 'type' => 'bool', 'internal' => 'isRequired'], + ]; + + /** + * Has many relation. + * + * @var array + * @since 1.0.0 + */ + public const HAS_MANY = [ + 'l11n' => [ + 'mapper' => TaskAttributeTypeL11nMapper::class, + 'table' => 'task_attr_type_l11n', + 'self' => 'task_attr_type_l11n_type', + 'column' => 'title', + 'external' => null, + ], + 'defaults' => [ + 'mapper' => TaskAttributeValueMapper::class, + 'table' => 'task_attr_default', + 'self' => 'task_attr_default_type', + 'external' => 'task_attr_default_value', + ], + ]; + + /** + * Primary table. + * + * @var string + * @since 1.0.0 + */ + public const TABLE = 'task_attr_type'; + + /** + * Primary field name. + * + * @var string + * @since 1.0.0 + */ + public const PRIMARYFIELD ='task_attr_type_id'; +} diff --git a/Models/TaskAttributeValue.php b/Models/TaskAttributeValue.php new file mode 100755 index 0000000..b3d4386 --- /dev/null +++ b/Models/TaskAttributeValue.php @@ -0,0 +1,231 @@ +type = $type; + + $this->setValue($value); + } + + /** + * Get id + * + * @return int + * + * @since 1.0.0 + */ + public function getId() : int + { + return $this->id; + } + + /** + * Set l11n + * + * @param string|TaskAttributeValueL11n $l11n Tag article l11n + * @param string $lang Language + * + * @return void + * + * @since 1.0.0 + */ + public function setL11n(string | TaskAttributeValueL11n $l11n, string $lang = ISO639x1Enum::_EN) : void + { + if ($l11n instanceof TaskAttributeValueL11n) { + $this->l11n = $l11n; + } elseif (isset($this->l11n) && $this->l11n instanceof TaskAttributeValueL11n) { + $this->l11n->title = $l11n; + } else { + $this->l11n = new TaskAttributeValueL11n(); + $this->l11n->title = $l11n; + $this->l11n->setLanguage($lang); + } + } + + /** + * Get localization + * + * @return null|string + * + * @since 1.0.0 + */ + public function getL11n() : ?string + { + return $this->l11n instanceof TaskAttributeValueL11n ? $this->l11n->title : $this->l11n; + } + + /** + * Set value + * + * @param int|string|float|\DateTimeInterface $value Value + * + * @return void + * + * @since 1.0.0 + */ + public function setValue(mixed $value) : void + { + if (\is_string($value)) { + $this->valueStr = $value; + } elseif (\is_int($value)) { + $this->valueInt = $value; + } elseif (\is_float($value)) { + $this->valueDec = $value; + } elseif ($value instanceof \DateTimeInterface) { + $this->valueDat = $value; + } + } + + /** + * Get value + * + * @return null|int|string|float|\DateTimeInterface + * + * @since 1.0.0 + */ + public function getValue() : mixed + { + if (!empty($this->valueStr)) { + return $this->valueStr; + } elseif (!empty($this->valueInt)) { + return $this->valueInt; + } elseif (!empty($this->valueDec)) { + return $this->valueDec; + } elseif ($this->valueDat instanceof \DateTimeInterface) { + return $this->valueDat; + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function toArray() : array + { + return [ + 'id' => $this->id, + 'type' => $this->type, + 'valueInt' => $this->valueInt, + 'valueStr' => $this->valueStr, + 'valueDec' => $this->valueDec, + 'valueDat' => $this->valueDat, + 'isDefault' => $this->isDefault, + ]; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() : mixed + { + return $this->toArray(); + } +} diff --git a/Models/TaskAttributeValueL11n.php b/Models/TaskAttributeValueL11n.php new file mode 100755 index 0000000..b9180cd --- /dev/null +++ b/Models/TaskAttributeValueL11n.php @@ -0,0 +1,135 @@ +value = $value; + $this->title = $title; + $this->language = $language; + } + + /** + * Get id + * + * @return int + * + * @since 1.0.0 + */ + public function getId() : int + { + return $this->id; + } + + /** + * Get language + * + * @return string + * + * @since 1.0.0 + */ + public function getLanguage() : string + { + return $this->language; + } + + /** + * Set language + * + * @param string $language Language + * + * @return void + * + * @since 1.0.0 + */ + public function setLanguage(string $language) : void + { + $this->language = $language; + } + + /** + * {@inheritdoc} + */ + public function toArray() : array + { + return [ + 'id' => $this->id, + 'title' => $this->title, + 'value' => $this->value, + 'language' => $this->language, + ]; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() : mixed + { + return $this->toArray(); + } +} diff --git a/Models/TaskAttributeValueL11nMapper.php b/Models/TaskAttributeValueL11nMapper.php new file mode 100755 index 0000000..50e3f71 --- /dev/null +++ b/Models/TaskAttributeValueL11nMapper.php @@ -0,0 +1,57 @@ + + * @since 1.0.0 + */ + public const COLUMNS = [ + 'task_attr_value_l11n_id' => ['name' => 'task_attr_value_l11n_id', 'type' => 'int', 'internal' => 'id'], + 'task_attr_value_l11n_title' => ['name' => 'task_attr_value_l11n_title', 'type' => 'string', 'internal' => 'title', 'autocomplete' => true], + 'task_attr_value_l11n_value' => ['name' => 'task_attr_value_l11n_value', 'type' => 'int', 'internal' => 'value'], + 'task_attr_value_l11n_lang' => ['name' => 'task_attr_value_l11n_lang', 'type' => 'string', 'internal' => 'language'], + ]; + + /** + * Primary table. + * + * @var string + * @since 1.0.0 + */ + public const TABLE = 'task_attr_value_l11n'; + + /** + * Primary field name. + * + * @var string + * @since 1.0.0 + */ + public const PRIMARYFIELD ='task_attr_value_l11n_id'; +} diff --git a/Models/TaskAttributeValueMapper.php b/Models/TaskAttributeValueMapper.php new file mode 100755 index 0000000..e759185 --- /dev/null +++ b/Models/TaskAttributeValueMapper.php @@ -0,0 +1,76 @@ + + * @since 1.0.0 + */ + public const COLUMNS = [ + 'task_attr_value_id' => ['name' => 'task_attr_value_id', 'type' => 'int', 'internal' => 'id'], + 'task_attr_value_default' => ['name' => 'task_attr_value_default', 'type' => 'bool', 'internal' => 'isDefault'], + 'task_attr_value_valuetype' => ['name' => 'task_attr_value_valuetype', 'type' => 'int', 'internal' => 'type'], + 'task_attr_value_valueStr' => ['name' => 'task_attr_value_valueStr', 'type' => 'string', 'internal' => 'valueStr'], + 'task_attr_value_valueInt' => ['name' => 'task_attr_value_valueInt', 'type' => 'int', 'internal' => 'valueInt'], + 'task_attr_value_valueDec' => ['name' => 'task_attr_value_valueDec', 'type' => 'float', 'internal' => 'valueDec'], + 'task_attr_value_valueDat' => ['name' => 'task_attr_value_valueDat', 'type' => 'DateTime', 'internal' => 'valueDat'], + 'task_attr_value_unit' => ['name' => 'task_attr_value_unit', 'type' => 'string', 'internal' => 'unit'], + ]; + + /** + * Has many relation. + * + * @var array + * @since 1.0.0 + */ + public const HAS_MANY = [ + 'l11n' => [ + 'mapper' => TaskAttributeValueL11nMapper::class, + 'table' => 'task_attr_value_l11n', + 'self' => 'task_attr_value_l11n_value', + 'external' => null, + ], + ]; + + /** + * Primary table. + * + * @var string + * @since 1.0.0 + */ + public const TABLE = 'task_attr_value'; + + /** + * Primary field name. + * + * @var string + * @since 1.0.0 + */ + public const PRIMARYFIELD ='task_attr_value_id'; +} diff --git a/Models/TaskMapper.php b/Models/TaskMapper.php index 1eba871..3209499 100755 --- a/Models/TaskMapper.php +++ b/Models/TaskMapper.php @@ -85,6 +85,12 @@ final class TaskMapper extends DataMapperFactory 'external' => 'task_tag_dst', 'self' => 'task_tag_src', ], + 'attributes' => [ + 'mapper' => TaskAttributeMapper::class, + 'table' => 'task_attr', + 'self' => 'task_attr_item', + 'external' => null, + ], ]; /** diff --git a/Theme/Backend/task-single.tpl.php b/Theme/Backend/task-single.tpl.php index e5900ff..9adc98f 100755 --- a/Theme/Backend/task-single.tpl.php +++ b/Theme/Backend/task-single.tpl.php @@ -320,7 +320,7 @@ echo $this->getData('nav')->render(); ?>
-
+