draft knowledgebase incl. l11n with demo

This commit is contained in:
Dennis Eichhorn 2020-11-04 20:43:15 +01:00
parent cf8b79f287
commit 6091928f22
11 changed files with 539 additions and 74 deletions

View File

@ -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",

View File

@ -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('/');
}
}
}

View File

@ -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<string, bool>
*
* @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;
}

View File

@ -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;

View File

@ -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,
];
}

185
Models/WikiCategoryL11n.php Normal file
View File

@ -0,0 +1,185 @@
<?php
/**
* Orange Management
*
* PHP Version 7.4
*
* @package Modules\Knowledgebase\Models
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://orange-management.org
*/
declare(strict_types=1);
namespace Modules\Knowledgebase\Models;
use phpOMS\Contract\ArrayableInterface;
use phpOMS\Localization\ISO639x1Enum;
/**
* Category class.
*
* @package Modules\Knowledgebase\Models
* @license OMS License 1.0
* @link https://orange-management.org
* @since 1.0.0
*/
class WikiCategoryL11n implements \JsonSerializable, ArrayableInterface
{
/**
* Article ID.
*
* @var int
* @since 1.0.0
*/
protected int $id = 0;
/**
* Category ID.
*
* @var int
* @since 1.0.0
*/
protected int $category = 0;
/**
* Language.
*
* @var string
* @since 1.0.0
*/
protected string $language = ISO639x1Enum::_EN;
/**
* Name.
*
* @var string
* @since 1.0.0
*/
private string $name = '';
/**
* Constructor.
*
* @param string $name Name
*
* @since 1.0.0
*/
public function __construct(string $name = '', string $language = ISO639x1Enum::_EN)
{
$this->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();
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* Orange Management
*
* PHP Version 7.4
*
* @package Modules\Knowledgebase\Models
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://orange-management.org
*/
declare(strict_types=1);
namespace Modules\Knowledgebase\Models;
use phpOMS\DataStorage\Database\DataMapperAbstract;
use phpOMS\Localization\Defaults\LanguageMapper;
/**
* Category mapper class.
*
* @package Modules\Knowledgebase\Models
* @license OMS License 1.0
* @link https://orange-management.org
* @since 1.0.0
*
* @todo Do I really want to create a relation to the language mapper? It's not really needed right?
*/
final class WikiCategoryL11nMapper extends DataMapperAbstract
{
/**
* Columns.
*
* @var array<string, array{name:string, type:string, internal:string, autocomplete?:bool, readonly?:bool, writeonly?:bool, annotations?:array}>
* @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<string, array{mapper:string, external:string, by?:string, column?:string, conditional?:bool}>
* @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';
}

View File

@ -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<string, array{mapper:string, table:string, self?:?string, external?:?string, column?:string}>
* @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);
}
}

View File

@ -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);
}
}

View File

@ -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(); ?>
<div class="row">
<div class="col-xs-12 col-md-8 col-lg-9">
@ -31,7 +35,7 @@ echo $this->getData('nav')->render(); ?>
<div class="portlet-head"><a href="<?= $url; ?>"><?= $this->printHtml($doc->getName()); ?></a></div>
<div class="portlet-body">
<article>
<?= \substr($doc->getDoc(), 0, 300) . (\strlen($doc->getDoc()) > 300 ? '...' : ''); ?>
<?= Markdown::parse(\substr($doc->getDocRaw(), 0, 500)); ?>
</article>
</div>
<div class="portlet-foot">
@ -49,8 +53,22 @@ echo $this->getData('nav')->render(); ?>
</div>
<div class="col-xs-12 col-md-4 col-lg-3">
<section class="box wf-100">
<div class="inner">
<section class="portlet">
<div class="portlet-head">App</div>
<div class="portlet-body">
<form>
<select>
<?php foreach ($apps as $app) : ?>
<option><?= $this->printHtml($app->getName()); ?>
<?php endforeach; ?>
</select>
</form>
</div>
</section>
<section class="portlet">
<div class="portlet-head">Categories</div>
<div class="portlet-body">
<ul>
<?php foreach ($categories as $category) : ?>
<li><a href="<?= UriFactory::build('{/prefix}wiki/doc/list?{?}&id=' . $category->getId()); ?>"><?= $this->printHtml($category->getName()); ?></a>

View File

@ -62,7 +62,8 @@ echo $this->getData('nav')->render();
<div class="col-xs-12 col-md-4 col-lg-3">
<section class="portlet">
<div class="inner">
<div class="portlet-head">Categories</div>
<div class="portlet-body">
<ul>
<?php foreach ($categories as $category) : ?>
<li><a href="<?= UriFactory::build('{/prefix}wiki/doc/list?{?}&id=' . $category->getId()); ?>"><?= $this->printHtml($category->getName()); ?></a>