From 6091928f2292214322d2f0bc3bd1aaaf8a61b745 Mon Sep 17 00:00:00 2001 From: Dennis Eichhorn Date: Wed, 4 Nov 2020 20:43:15 +0100 Subject: [PATCH] draft knowledgebase incl. l11n with demo --- Admin/Install/db.json | 47 +++++-- Admin/Installer.php | 2 - Controller/ApiController.php | 95 ++++++++++++- Controller/BackendController.php | 24 ++-- Models/WikiCategory.php | 57 +++----- Models/WikiCategoryL11n.php | 185 ++++++++++++++++++++++++++ Models/WikiCategoryL11nMapper.php | 76 +++++++++++ Models/WikiCategoryMapper.php | 66 ++++++++- Models/WikiDocMapper.php | 34 +++++ Theme/Backend/wiki-dashboard.tpl.php | 24 +++- Theme/Backend/wiki-doc-single.tpl.php | 3 +- 11 files changed, 539 insertions(+), 74 deletions(-) create mode 100644 Models/WikiCategoryL11n.php create mode 100644 Models/WikiCategoryL11nMapper.php diff --git a/Admin/Install/db.json b/Admin/Install/db.json index 1e1be41..9a63e07 100755 --- a/Admin/Install/db.json +++ b/Admin/Install/db.json @@ -27,16 +27,6 @@ "primary": true, "autoincrement": true }, - "wiki_category_name": { - "name": "wiki_category_name", - "type": "VARCHAR(255)", - "null": false - }, - "wiki_category_path": { - "name": "wiki_category_path", - "type": "VARCHAR(255)", - "null": false - }, "wiki_category_parent": { "name": "wiki_category_parent", "type": "INT", @@ -55,6 +45,38 @@ } } }, + "wiki_category_l11n": { + "name": "wiki_category_l11n", + "fields": { + "wiki_category_l11n_id": { + "name": "wiki_category_l11n_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "wiki_category_l11n_name": { + "name": "wiki_category_l11n_name", + "type": "VARCHAR(255)", + "null": false + }, + "wiki_category_l11n_category": { + "name": "wiki_category_l11n_category", + "type": "INT", + "null": false, + "foreignTable": "wiki_category", + "foreignKey": "wiki_category_id" + }, + "wiki_category_l11n_language": { + "name": "wiki_category_l11n_language", + "type": "VARCHAR(2)", + "default": null, + "null": true, + "foreignTable": "language", + "foreignKey": "language_639_1" + } + } + }, "wiki_article": { "name": "wiki_article", "fields": { @@ -85,6 +107,11 @@ "type": "TEXT", "null": false }, + "wiki_article_docraw": { + "name": "wiki_article_docraw", + "type": "TEXT", + "null": false + }, "wiki_article_category": { "name": "wiki_article_category", "type": "INT", diff --git a/Admin/Installer.php b/Admin/Installer.php index 510445b..3cfc4fb 100755 --- a/Admin/Installer.php +++ b/Admin/Installer.php @@ -50,7 +50,6 @@ final class Installer extends InstallerAbstract $category = new WikiCategory(); $category->setApp(new NullWikiApp($id)); $category->setName('Default'); - $category->setPath('/'); WikiCategoryMapper::create($category); @@ -65,7 +64,6 @@ final class Installer extends InstallerAbstract $category = new WikiCategory(); $category->setApp(new NullWikiApp($id)); $category->setName('Default'); - $category->setPath('/'); } } } diff --git a/Controller/ApiController.php b/Controller/ApiController.php index 12510f3..4510054 100755 --- a/Controller/ApiController.php +++ b/Controller/ApiController.php @@ -30,6 +30,10 @@ use phpOMS\Message\RequestAbstract; use phpOMS\Message\ResponseAbstract; use phpOMS\Model\Message\FormValidation; use phpOMS\Utils\Parser\Markdown\Markdown; +use Modules\Knowledgebase\Models\WikiCategoryL11n; +use phpOMS\Message\Http\HttpRequest; +use Modules\Knowledgebase\Models\WikiCategoryL11nMapper; +use Modules\Knowledgebase\Models\NullWikiApp; /** * Knowledgebase class. @@ -92,6 +96,7 @@ final class ApiController extends Controller $doc->setCategory(new NullWikiCategory((int) ($request->getData('category') ?? 1))); $doc->setLanguage((string) ($request->getData('language') ?? $request->getHeader()->getL11n()->getLanguage())); $doc->setStatus((int) ($request->getData('status') ?? WikiStatus::INACTIVE)); + $doc->setApp(new NullWikiApp((int) ($request->getData('app') ?? 1))); if (!empty($tags = $request->getDataJson('tags'))) { foreach ($tags as $tag) { @@ -137,6 +142,76 @@ final class ApiController extends Controller return []; } + /** + * Validate tag l11n create request + * + * @param RequestAbstract $request Request + * + * @return array + * + * @since 1.0.0 + */ + private function validateWikiCategoryL11nCreate(RequestAbstract $request) : array + { + $val = []; + if (($val['name'] = empty($request->getData('name'))) + || ($val['tag'] = empty($request->getData('tag'))) + ) { + return $val; + } + + return []; + } + + /** + * Api method to create tag localization + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param mixed $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiWikiCategoryL11nCreate(RequestAbstract $request, ResponseAbstract $response, $data = null) : void + { + if (!empty($val = $this->validateWikiCategoryL11nCreate($request))) { + $response->set('wiki_category_l11n_create', new FormValidation($val)); + $response->getHeader()->setStatusCode(RequestStatusCode::R_400); + + return; + } + + $l11nWikiCategory = $this->createWikiCategoryL11nFromRequest($request); + $this->createModel($request->getHeader()->getAccount(), $l11nWikiCategory, WikiCategoryL11nMapper::class, 'wiki_category_l11n', $request->getOrigin()); + + $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Localization', 'Category localization successfully created', $l11nWikiCategory); + } + + /** + * Method to create tag localization from request. + * + * @param RequestAbstract $request Request + * + * @return WikiCategoryL11n + * + * @since 1.0.0 + */ + private function createWikiCategoryL11nFromRequest(RequestAbstract $request) : WikiCategoryL11n + { + $l11nWikiCategory = new WikiCategoryL11n(); + $l11nWikiCategory->setCategory((int) ($request->getData('category') ?? 0)); + $l11nWikiCategory->setLanguage((string) ( + $request->getData('language') ?? $request->getHeader()->getL11n()->getLanguage() + )); + $l11nWikiCategory->setName((string) ($request->getData('name') ?? '')); + + return $l11nWikiCategory; + } + /** * Api method to get a doc * @@ -238,6 +313,17 @@ final class ApiController extends Controller $category = $this->createWikiCategoryFromRequest($request); $this->createModel($request->getHeader()->getAccount(), $category, WikiCategoryMapper::class, 'category', $request->getOrigin()); + + $l11nRequest = new HttpRequest($request->getUri()); + $l11nRequest->setData('category', $category->getId()); + $l11nRequest->setData('name', $request->getData('name')); + $l11nRequest->setData('language', $request->getData('language')); + + $l11nWikiCategory = $this->createWikiCategoryL11nFromRequest($l11nRequest); + $this->createModel($request->getHeader()->getAccount(), $l11nWikiCategory, WikiCategoryL11nMapper::class, 'tag_l11n', $request->getOrigin()); + + $category->setName($l11nWikiCategory); + $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Category', 'Category successfully created.', $category); } @@ -253,11 +339,7 @@ final class ApiController extends Controller public function createWikiCategoryFromRequest(RequestAbstract $request) : WikiCategory { $category = new WikiCategory(); - $category->setName((string) $request->getData('title')); - - if ($request->getData('path') !== null) { - $category->setPath((string) $request->getData('path')); - } + $category->setApp(new NullWikiApp((int) ($request->getData('app') ?? 1))); if ($request->getData('parent') !== null) { $category->setParent(new NullWikiCategory((int) $request->getData('parent'))); @@ -278,7 +360,7 @@ final class ApiController extends Controller private function validateWikiCategoryCreate(RequestAbstract $request) : array { $val = []; - if (($val['title'] = empty($request->getData('title')))) { + if (($val['name'] = empty($request->getData('name')))) { return $val; } @@ -337,7 +419,6 @@ final class ApiController extends Controller private function updateCategoryFromRequest(RequestAbstract $request) : WikiCategory { $category = WikiCategoryMapper::get((int) $request->getData('id')); - $category->setName((string) ($request->getData('title') ?? $category->getName())); return $category; } diff --git a/Controller/BackendController.php b/Controller/BackendController.php index 845c355..331eaa7 100755 --- a/Controller/BackendController.php +++ b/Controller/BackendController.php @@ -77,15 +77,20 @@ final class BackendController extends Controller { $view = new View($this->app->l11nManager, $request, $response); + $app = (int) ($request->getData('app') ?? $this->app->orgId); + $view->setTemplate('/Modules/Knowledgebase/Theme/Backend/wiki-dashboard'); $view->addData('nav', $this->app->moduleManager->get('Navigation')->createNavigationMid(1005901001, $request, $response)); - $categories = WikiCategoryMapper::getAll(); + $categories = WikiCategoryMapper::withConditional('language', $response->getHeader()->getL11n()->getLanguage())::getByParentAndApp($request->hasData('category') ? (int) $request->getData('category') : null, $app, 2); $view->setData('categories', $categories); - $documents = WikiDocMapper::withConditional('language', $response->getHeader()->getL11n()->getLanguage())::getNewest(50); + $documents = WikiDocMapper::withConditional('language', $response->getHeader()->getL11n()->getLanguage())::getNewestByApp($app, 10); $view->setData('docs', $documents); + $apps = WikiAppMapper::getAll(); + $view->setData('apps', $apps); + return $view; } @@ -178,10 +183,12 @@ final class BackendController extends Controller { $view = new View($this->app->l11nManager, $request, $response); + $app = (int) ($request->getData('app') ?? $this->app->orgId); + $view->setTemplate('/Modules/Knowledgebase/Theme/Backend/wiki-category-list'); $view->addData('nav', $this->app->moduleManager->get('Navigation')->createNavigationMid(1005901001, $request, $response)); - $list = WikiCategoryMapper::getAll(); + $list = WikiCategoryMapper::getByApp($app, 2); $view->setData('categories', $list); return $view; @@ -276,11 +283,14 @@ final class BackendController extends Controller { $view = new View($this->app->l11nManager, $request, $response); - $category = WikiDocMapper::get((int) $request->getData('id')); + $app = (int) ($request->getData('app') ?? $this->app->orgId); + $lang = $response->getHeader()->getL11n()->getLanguage(); + + $document = WikiDocMapper::withConditional('language', $lang)::get((int) $request->getData('id')); $accountId = $request->getHeader()->getAccount(); if (!$this->app->accountManager->get($accountId)->hasPermission( - PermissionType::READ, $this->app->orgId, $this->app->appName, self::MODULE_NAME, PermissionState::WIKI, $category->getId()) + PermissionType::READ, $this->app->orgId, $this->app->appName, self::MODULE_NAME, PermissionState::WIKI, $document->getId()) ) { $view->setTemplate('/Web/Backend/Error/403_inline'); $response->getHeader()->setStatusCode(RequestStatusCode::R_403); @@ -290,10 +300,8 @@ final class BackendController extends Controller $view->setTemplate('/Modules/Knowledgebase/Theme/Backend/wiki-doc-single'); $view->addData('nav', $this->app->moduleManager->get('Navigation')->createNavigationMid(1005901001, $request, $response)); - $categories = WikiCategoryMapper::getAll(); + $categories = WikiCategoryMapper::withConditional('language', $lang)::getByParentAndApp($request->hasData('category') ? (int) $request->getData('category') : null, $app, 2); $view->setData('categories', $categories); - - $document = WikiDocMapper::get((int) $request->getData('id')); $view->setData('document', $document); return $view; diff --git a/Models/WikiCategory.php b/Models/WikiCategory.php index 32be3c0..5c1ac14 100755 --- a/Models/WikiCategory.php +++ b/Models/WikiCategory.php @@ -14,6 +14,8 @@ declare(strict_types=1); namespace Modules\Knowledgebase\Models; +use phpOMS\Localization\ISO639x1Enum; + /** * Wiki category class. * @@ -45,18 +47,10 @@ class WikiCategory implements \JsonSerializable /** * Name. * - * @var string + * @var string|WikiCategoryL11n * @since 1.0.0 */ - private string $name = ''; - - /** - * Path. - * - * @var string - * @since 1.0.0 - */ - private string $path = '/'; + private $name = ''; /** * Parent category. @@ -123,47 +117,29 @@ class WikiCategory implements \JsonSerializable */ public function getName() : string { - return $this->name; + return $this->name instanceof WikiCategoryL11n ? $this->name->getName() : $this->name; } /** * Set name * - * @param string $name Name + * @param string|TagL11n $name Tag article name * * @return void * * @since 1.0.0 */ - public function setName(string $name) : void + public function setName($name, string $lang = ISO639x1Enum::_EN) : void { - $this->name = $name; - } - - /** - * Get path - * - * @return string - * - * @since 1.0.0 - */ - public function getPath() : string - { - return $this->path; - } - - /** - * Set path - * - * @param string $path Path - * - * @return void - * - * @since 1.0.0 - */ - public function setPath(string $path) : void - { - $this->path = $path; + if ($name instanceof WikiCategoryL11n) { + $this->name = $name; + } elseif ($this->name instanceof WikiCategoryL11n && \is_string($name)) { + $this->name->setName($name); + } elseif (\is_string($name)) { + $this->name = new WikiCategoryL11n(); + $this->name->setName($name); + $this->name->setLanguage($lang); + } } /** @@ -201,7 +177,6 @@ class WikiCategory implements \JsonSerializable 'id' => $this->id, 'app' => $this->app, 'name' => $this->name, - 'path' => $this->path, ]; } diff --git a/Models/WikiCategoryL11n.php b/Models/WikiCategoryL11n.php new file mode 100644 index 0000000..afc3d7d --- /dev/null +++ b/Models/WikiCategoryL11n.php @@ -0,0 +1,185 @@ +name = $name; + $this->language = $language; + } + + /** + * Get id + * + * @return int + * + * @since 1.0.0 + */ + public function getId() : int + { + return $this->id; + } + + /** + * Set category. + * + * @param int $category Category id + * + * @return void + * + * @since 1.0.0 + */ + public function setCategory(int $category) : void + { + $this->category = $category; + } + + /** + * Get category + * + * @return int + * + * @since 1.0.0 + */ + public function getCategory() : int + { + return $this->category; + } + + /** + * 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; + } + + /** + * Get category name. + * + * @return string + * + * @since 1.0.0 + */ + public function getName() : string + { + return $this->name; + } + + /** + * Set name + * + * @param string $name Name + * + * @return void + * + * @since 1.0.0 + */ + public function setName(string $name) : void + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function toArray() : array + { + return [ + 'id' => $this->id, + 'name' => $this->name, + 'category' => $this->category, + 'language' => $this->language, + ]; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() + { + return $this->toArray(); + } +} diff --git a/Models/WikiCategoryL11nMapper.php b/Models/WikiCategoryL11nMapper.php new file mode 100644 index 0000000..f1c7b08 --- /dev/null +++ b/Models/WikiCategoryL11nMapper.php @@ -0,0 +1,76 @@ + + * @since 1.0.0 + */ + protected static array $columns = [ + 'wiki_category_l11n_id' => ['name' => 'wiki_category_l11n_id', 'type' => 'int', 'internal' => 'id'], + 'wiki_category_l11n_name' => ['name' => 'wiki_category_l11n_name', 'type' => 'string', 'internal' => 'name', 'autocomplete' => true], + 'wiki_category_l11n_category' => ['name' => 'wiki_category_l11n_category', 'type' => 'int', 'internal' => 'category'], + 'wiki_category_l11n_language' => ['name' => 'wiki_category_l11n_language', 'type' => 'string', 'internal' => 'language'], + ]; + + /** + * Has one relation. + * + * @var array + * @since 1.0.0 + */ + protected static array $ownsOne = [ + 'language' => [ + 'mapper' => LanguageMapper::class, + 'external' => 'wiki_category_l11n_language', + 'by' => 'code2', + 'column' => 'code2', + 'conditional' => true, + ], + ]; + + /** + * Primary table. + * + * @var string + * @since 1.0.0 + */ + protected static string $table = 'wiki_category_l11n'; + + /** + * Primary field name. + * + * @var string + * @since 1.0.0 + */ + protected static string $primaryField = 'wiki_category_l11n_id'; +} diff --git a/Models/WikiCategoryMapper.php b/Models/WikiCategoryMapper.php index 0f745ce..99c131e 100755 --- a/Models/WikiCategoryMapper.php +++ b/Models/WikiCategoryMapper.php @@ -15,6 +15,7 @@ declare(strict_types=1); namespace Modules\Knowledgebase\Models; use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\RelationType; /** * Mapper class. @@ -35,11 +36,26 @@ final class WikiCategoryMapper extends DataMapperAbstract protected static array $columns = [ 'wiki_category_id' => ['name' => 'wiki_category_id', 'type' => 'int', 'internal' => 'id'], 'wiki_category_app' => ['name' => 'wiki_category_app', 'type' => 'int', 'internal' => 'app'], - 'wiki_category_name' => ['name' => 'wiki_category_name', 'type' => 'string', 'internal' => 'name'], - 'wiki_category_path' => ['name' => 'wiki_category_path', 'type' => 'string', 'internal' => 'path'], 'wiki_category_parent' => ['name' => 'wiki_category_parent', 'type' => 'int', 'internal' => 'parent'], ]; + /** + * Has many relation. + * + * @var array + * @since 1.0.0 + */ + protected static array $hasMany = [ + 'name' => [ + 'mapper' => WikiCategoryL11nMapper::class, + 'table' => 'wiki_category_l11n', + 'self' => 'wiki_category_l11n_category', + 'column' => 'name', + 'conditional' => true, + 'external' => null, + ], + ]; + /** * Has owns one relation. * @@ -72,4 +88,50 @@ final class WikiCategoryMapper extends DataMapperAbstract * @since 1.0.0 */ protected static string $primaryField = 'wiki_category_id'; + + /** + * Parent field name. + * + * @var string + * @since 1.0.0 + */ + protected static string $parent = 'wiki_category_parent'; + + /** + * Get by parent. + * + * @param mixed $value Parent value id + * @param int $app App + * @param int $depth Relation depth + * + * @return array + * + * @since 1.0.0 + */ + public static function getByParentAndApp($value, int $app = 1, int $depth = 3) : array + { + $query = self::getQuery(); + $query->where(static::$table . '_' . $depth . '.' . static::$parent, '=', $value) + ->andWhere(static::$table . '_' . $depth . '.wiki_category_app', '=', $app); + + return self::getAllByQuery($query, RelationType::ALL, $depth); + } + + /** + * Get by app. + * + * @param int $app App + * @param int $depth Relation depth + * + * @return array + * + * @since 1.0.0 + */ + public static function getByApp(int $app, int $depth = 3) : array + { + $query = self::getQuery(); + $query->where(static::$table . '_' . $depth . '.wiki_category_app', '=', $app); + + return self::getAllByQuery($query, RelationType::ALL, $depth); + } } diff --git a/Models/WikiDocMapper.php b/Models/WikiDocMapper.php index d78b828..6a105ef 100755 --- a/Models/WikiDocMapper.php +++ b/Models/WikiDocMapper.php @@ -16,6 +16,8 @@ namespace Modules\Knowledgebase\Models; use Modules\Tag\Models\TagMapper; use phpOMS\DataStorage\Database\DataMapperAbstract; +use phpOMS\DataStorage\Database\RelationType; +use phpOMS\DataStorage\Database\Query\Builder; /** * Mapper class. @@ -39,6 +41,7 @@ final class WikiDocMapper extends DataMapperAbstract 'wiki_article_title' => ['name' => 'wiki_article_title', 'type' => 'string', 'internal' => 'name'], 'wiki_article_language' => ['name' => 'wiki_article_language', 'type' => 'string', 'internal' => 'language'], 'wiki_article_doc' => ['name' => 'wiki_article_doc', 'type' => 'string', 'internal' => 'doc'], + 'wiki_article_docraw' => ['name' => 'wiki_article_docraw', 'type' => 'string', 'internal' => 'docRaw'], 'wiki_article_status' => ['name' => 'wiki_article_status', 'type' => 'int', 'internal' => 'status'], 'wiki_article_category' => ['name' => 'wiki_article_category', 'type' => 'int', 'internal' => 'category'], ]; @@ -90,4 +93,35 @@ final class WikiDocMapper extends DataMapperAbstract * @since 1.0.0 */ protected static string $primaryField = 'wiki_article_id'; + + /** + * Get newest. + * + * This will fall back to the insert id if no datetime column is present. + * + * @param int $app App + * @param int $limit Newest limit + * @param Builder $query Pre-defined query + * @param int $relations Load relations + * @param int $depth Relation depth + * + * @return array + * + * @since 1.0.0 + */ + public static function getNewestByApp(int $app, int $limit = 1, Builder $query = null, int $relations = RelationType::ALL, int $depth = 3) : array + { + $query ??= self::getQuery(null, [], $relations, $depth); + + $query->where(static::$table . '_' . $depth . '.' . 'wiki_article_app', '=', $app) + ->limit($limit); + + if (!empty(static::$createdAt)) { + $query->orderBy(static::$table . '_' . $depth . '.' . static::$columns[static::$createdAt]['name'], 'DESC'); + } else { + $query->orderBy(static::$table . '_' . $depth . '.' . static::$columns[static::$primaryField]['name'], 'DESC'); + } + + return self::getAllByQuery($query, $relations, $depth); + } } diff --git a/Theme/Backend/wiki-dashboard.tpl.php b/Theme/Backend/wiki-dashboard.tpl.php index 69ea90a..ae80d0c 100755 --- a/Theme/Backend/wiki-dashboard.tpl.php +++ b/Theme/Backend/wiki-dashboard.tpl.php @@ -13,6 +13,7 @@ declare(strict_types=1); use \phpOMS\Uri\UriFactory; +use phpOMS\Utils\Parser\Markdown\Markdown; /** @var \phpOMS\Views\View $this */ /** @var \Modules\Knowledgebase\Models\WikiCategory[] $categories */ @@ -21,6 +22,9 @@ $categories = $this->getData('categories') ?? []; /** @var \Modules\Knowledgebase\Models\WikiDoc[] $documents */ $documents = $this->getData('docs') ?? []; +/** @var \Modules\Knowledgebase\Models\WikiApp[] $apps */ +$apps = $this->getData('apps') ?? []; + echo $this->getData('nav')->render(); ?>
@@ -31,7 +35,7 @@ echo $this->getData('nav')->render(); ?>
- getDoc(), 0, 300) . (\strlen($doc->getDoc()) > 300 ? '...' : ''); ?> + getDocRaw(), 0, 500)); ?>
@@ -49,8 +53,22 @@ echo $this->getData('nav')->render(); ?>
-
-
+
+
App
+
+
+ +
+
+
+ +
+
Categories
+
  • printHtml($category->getName()); ?> diff --git a/Theme/Backend/wiki-doc-single.tpl.php b/Theme/Backend/wiki-doc-single.tpl.php index 5ac64b2..68229d1 100755 --- a/Theme/Backend/wiki-doc-single.tpl.php +++ b/Theme/Backend/wiki-doc-single.tpl.php @@ -62,7 +62,8 @@ echo $this->getData('nav')->render();