Compare commits

..

No commits in common. "1f10cc7f89c165fb3ee4c4ae620a59b20bda3b50" and "b2ff261e74a9998f905d9b3d3d614fe38e96d277" have entirely different histories.

14 changed files with 76 additions and 369 deletions

1
.gitignore vendored
View File

@ -2,4 +2,3 @@
/downloads/ /downloads/
/app/.env.cfg /app/.env.cfg
/public/tmp/ /public/tmp/
composer.lock

View File

@ -30,11 +30,11 @@ class KBController {
$args[] = $tag_param; $args[] = $tag_param;
if($search_term){ if($search_term){
$sql .= ' AND LOWER(a.title) LIKE LOWER(?)'; $sql .= ' AND a.title LIKE ?';
$args[] = '%' . $search_term . '%'; $args[] = '%' . $search_term . '%';
} }
} else if ($search_term){ } else if ($search_term){
$sql .= ' WHERE LOWER(a.title) LIKE LOWER(?)'; $sql .= ' WHERE a.title LIKE ?';
$args[] = '%' . $search_term . '%'; $args[] = '%' . $search_term . '%';
} }
@ -82,7 +82,7 @@ class KBController {
$db->exec( $db->exec(
'INSERT INTO kb (title, content, created_by, updated_by, created_at, updated_at) 'INSERT INTO kb (title, content, created_by, updated_by, created_at, updated_at)
VALUES (?,?,?,?, NOW(), NOW())', VALUES (?,?,?,?, NOW(), NOW())',
[$title, $content, $created_by, $created_by] [$title, $content, $created_by]
); );
$article_id = $db->lastInsertId(); $article_id = $db->lastInsertId();
@ -92,26 +92,24 @@ class KBController {
$f3->reroute('/kb'); $f3->reroute('/kb');
} }
//
protected function check_kb_exists($article_id, $db, $f3){
$articles = $db->exec(
'SELECT * FROM kb WHERE id = ? LIMIT 1', [$article_id]
);
if(!$articles){
$f3->set('SESSION.error', 'Article not found');
$f3->reroute('/kb');
}
return $articles;
}
// view a single // view a single
public function view($f3){ public function view($f3){
$this->check_access($f3); $this->check_access($f3);
$article_id = $f3->get('PARAMS.id'); $article_id = $f3->get('PARAMS.id');
$db = $f3->get('DB'); $db = $f3->get('DB');
$articles = $this->check_kb_exists($article_id, $db, $f3); $articles = $db->exec(
'SELECT a.*, u.username AS created_by_name
FROM kb AS a
LEFT JOIN users AS u ON a.created_by = u.id
WHERE a.id = ? LIMIT 1',
[$article_id]
);
if(!$articles){
$f3->set('SESSION.error', 'Article not found');
$f3->reroute('/kb');
}
$article = $articles[0]; $article = $articles[0];
$f3->set('article', $article); $f3->set('article', $article);
@ -120,7 +118,7 @@ class KBController {
$tags = $db->exec( $tags = $db->exec(
'SELECT t.* FROM tags AS t 'SELECT t.* FROM tags AS t
JOIN kb_tags AS at ON t.id = at.tag_id JOIN kb_tags AS at ON t.id = at.tag_id
WHERE at.kb_id = ?', WHERE at.article_id = ?',
[$article_id] [$article_id]
); );
@ -130,76 +128,4 @@ class KBController {
$f3->clear('SESSION.error'); $f3->clear('SESSION.error');
} }
/**
* Form to edit existing kb article
*/
public function editForm($f3){
$this->check_access($f3);
$article_id = $f3->get('PARAMS.id');
$db = $f3->get('DB');
$articles = $this->check_kb_exists($article_id, $db, $f3);
$article = $articles[0];
$f3->set('article', $article);
// fetch current tags
$current_tag_ids = $db->exec(
'SELECT tag_id FROM kb_tags WHERE kb_id = ?', [$article_id]
);
$article_tag_ids = array_column($current_tag_ids, 'tag_id');
$f3->set('article_tag_ids', $article_tag_ids);
// render
$f3->set('content', '../ui/views/kb/edit.html');
echo \Template::instance()->render('../ui/templates/layout.html');
$f3->clear('SESSION.error');
}
/**
* Handle POST to edit existing article
*/
public function update($f3){
$this->check_access($f3);
$article_id = $f3->get('PARAMS.id');
$db = $f3->get('DB');
$articles = $this->check_kb_exists($article_id, $db, $f3);
$article = $articles[0];
$title = $f3->get('POST.title');
$content = $f3->get('POST.content');
$updated_by = $f3->get('SESSION.user.id');
$db->exec(
'UPDATE kb
SET title=?, content=?, updated_by =?, updated_at = NOW()
WHERE id = ?',
[$title, $content, $updated_by, $article_id]
);
// update tags - first delete
$db->exec('DELETE FROM kb_tags WHERE kb_id = ?', [$article_id]);
$tags_id = $f3->get('POST.tags');
if(!empty($tags_id) && is_array($tags_id)){
foreach($tags_id as $tag_id){
$db->exec(
'INSERT IGNORE INTO kb_tags (article_id, tag_id) VALUES (?,?)',
[$article_id, $tag_id]
);
}
}
$f3->reroute('/kb/'.$article_id);
}
} }

View File

@ -1,44 +0,0 @@
<?php
class TagController {
protected function check_access($f3){
if(!$f3->exists('SESSION.user')){
$f3->reroute('/login');
}
}
/**
* List all tags
*/
public function index($f3){
$this->check_access($f3);
$db = $f3->get('DB');
$tags = $db->exec('SELECT * FROM tags ORDER BY name ASC');
$f3->set('tags', $tags);
$f3->set('content', '../ui/views/tag/index.html');
echo \Template::instance()->render('../ui/templates/layout.html');
}
public function createForm($f3){
$this->check_access($f3);
$f3->set('content', '../ui/views/tag/create.html');
echo \Template::instance()->render('../ui/templates/layout.html');
}
public function create($f3){
$this->check_access($f3);
$name = $f3->get('POST.name');
$color = $f3->get('POST.color');
$db = $f3->get('DB');
// insert new tag
$db->exec('INSERT IGNORE INTO tags (name, color) VALUES (?, ?)', [$name, $color]);
$f3->reroute('/tags');
}
}

View File

@ -6,7 +6,6 @@
}, },
"require": { "require": {
"bcosca/fatfree-core": "^3.9", "bcosca/fatfree-core": "^3.9",
"erusev/parsedown": "^1.7", "erusev/parsedown": "^1.7"
"ezyang/htmlpurifier": "^4.18"
} }
} }

View File

@ -1,16 +1,8 @@
<?php <?php
require '../lib/autoload.php'; require '../lib/vendor/autoload.php';
$f3 = \Base::instance(); $f3 = \Base::instance();
/**
* Not required yet
*/
// $htmlpurifier = new \HTMLPurifier();
// $htmlpurifier->purify($input);
$md = \Parsedown::instance();
$f3->set('DEBUG', 3); // development debug $f3->set('DEBUG', 3); // development debug
$f3->config('../app/.env.cfg'); $f3->config('../app/.env.cfg');
@ -52,16 +44,16 @@ $f3->route('POST /ticket/@id/update', 'TicketController->update'); //
// knowledgebase // knowledgebase
$f3->route('GET /kb', 'KBController->index'); $f3->route('GET /kb', 'KBController->index');
$f3->route('GET /kb/@id', 'KBController->view');
$f3->route('GET /kb/create', 'KBController->createForm'); $f3->route('GET /kb/create', 'KBController->createForm');
$f3->route('POST /kb/create', 'KBController->create'); $f3->route('POST /kb/create', 'KBController->create');
$f3->route('GET /kb/@id', 'KBController->view');
$f3->route('GET /kb/@id/edit', 'KBController->editForm'); $f3->route('GET /kb/@id/edit', 'KBController->editForm');
$f3->route('POST /kb/@id/update', 'KBController->update'); // should this be update - "crud"? $f3->route('POST /kb/@id/edit', 'KBControllerKB->edit'); // should this be update - "crud"?
// tags // tags
$f3->route('GET /tags', 'TagController->index'); $f3->route('GET /tags', 'TagController->index');
$f3->route('GET /tag/create', 'TagController->createForm'); $f3->route('GET /tag/create', 'Tag->createForm');
$f3->route('POST /tag/create', 'TagController->create'); $f3->route('POST /tag/create', 'Tag->create');
// dashboard // dashboard

View File

@ -11,7 +11,7 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma-helpers/0.4.3/css/bulma-helpers.min.css" <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma-helpers/0.4.3/css/bulma-helpers.min.css"
integrity="sha512-U6ELnUi7oqVEjkLmFw5r5UR5LEtvpImS/jUykBKneVhD0lxZxfJZ3k3pe003ktrtNZYungd9u3Urp2X09wKwXg==" integrity="sha512-U6ELnUi7oqVEjkLmFw5r5UR5LEtvpImS/jUykBKneVhD0lxZxfJZ3k3pe003ktrtNZYungd9u3Urp2X09wKwXg=="
crossorigin="anonymous" referrerpolicy="no-referrer" /> crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="/style.css"> <!-- <link rel="stylesheet" href="style.css"> -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma-checkradio@2.1/dist/css/bulma-checkradio.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma-checkradio@2.1/dist/css/bulma-checkradio.min.css">
<!-- font awesome --> <!-- font awesome -->
<link rel="stylesheet" <link rel="stylesheet"
@ -40,8 +40,8 @@
<div class="navbar-start"> <div class="navbar-start">
<a class="navbar-item" href="/dashboard">Dashboard</a> <a class="navbar-item" href="/dashboard">Dashboard</a>
<a class="navbar-item" href="/tickets">Tickets</a> <a class="navbar-item" href="/tickets">Tickets</a>
<a class="navbar-item" href="/projects">Projects</a>
<a class="navbar-item" href="/kb">Knowledge Base</a> <a class="navbar-item" href="/kb">Knowledge Base</a>
<a class="navbar-item" href="/tags">Tags</a>
</div> </div>
<div class="navbar-end"> <div class="navbar-end">
<div class="navbar-item"> <div class="navbar-item">
@ -60,20 +60,6 @@
</div> </div>
</nav> </nav>
<!-- breadcrumbs TODO: NOT YET
<div class="container">
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul>
<li><a href="#">Bulma</a></li>
<li><a href="#">Documentation</a></li>
<li><a href="#">Components</a></li>
<li class="is-active"><a href="#" aria-current="page">Breadcrumb</a></li>
</ul>
</nav>
</div>
-->
<!-- Main Content Area --> <!-- Main Content Area -->
<main class="section" id="page"> <main class="section" id="page">
<div class="container"> <div class="container">

View File

@ -1,34 +1,16 @@
<h1 class="title">Create Knowledge Base Article</h1> <h1 class="title">Create Knowledge Base Article</h1>
<div class="content">
<form action="/kb/create" method="POST"> <form action="/kb/create" method="POST">
{{ BulmaForm::horizontal_field_input('Title:', 'title') }} {{ BulmaForm::horizontal_field_input('Title:', 'title') }}
<div id="editor" class="block"> {{ BulmaForm::horizontal_field_textarea('Description:', 'description') }}
<div class="tabs">
<ul>
<li class="is-active"><a>Write</a></li>
<li class=""><a>Preivew</a></li>
</ul>
</div>
<div class="block"> {{ BulmaForm::horizontal_field_select('Priority:', 'priority', ['Low', 'Medium', 'High'])}}
{{ BulmaForm::horizontal_field_textarea('Content:', 'content') }}
</div>
</div>
<!-- TODO: tags --> {{ BulmaForm::horizontal_field_select('Status:', 'status', ['New', 'In Progress', 'On Hold', 'Completed'])}}
<!-- buttons --> <button class="button is-primary" type="submit">Create Ticket</button>
<div class="field is-grouped is-grouped-right">
<div class="control">
<a class="button is-secondary" href="/kb">Cancel</a>
</div>
<div class="control">
<button class="button is-primary" type="submit">Create KB Article</button>
</div>
</div> </div>
</form> </form>
</div>

View File

@ -1,25 +0,0 @@
<h1 class="title">Edit Knowledge Base Article</h1>
<form action="/kb/{{@article.id}}/update" method="POST">
{{ BulmaForm::horizontal_field_input('Title:', 'title', @article.title)}}
<div id="editor" class="block">
<div class="tabs">
<ul>
<li class="is-active"><a>Write</a></li>
<li class=""><a>Preivew</a></li>
</ul>
</div>
<div class="block">
{{ BulmaForm::horizontal_field_textarea('Content:', 'content', @article.content) }}
</div>
</div>
<!-- TODO: tags -->
<button class="button is-primary" type="submit">Update Article</button>
</div>
</form>

View File

@ -6,56 +6,32 @@
</div> </div>
</check> </check>
<p><a href="/kb/create">create new kb article</a></p> <p><a href="/kb/create">create kb article</a></p>
<hr> <hr>
<div class="block">
<form method="GET" action="/kb">
<div class="level">
<div class="level-item">
<input class="input" type="text" name="search" placeholder="Search by title...">
</div>
<div class="level-right">
<div class="select">
<select name="tag">
<option value="">--Filter by Tag</option>
<!-- TODO: load list of all tags-->
</select>
</div>
</div>
<div class="level-right">
<button class="button is-primary" type="submit">Search</button>
</div>
</div>
</form>
</div>
<check if="{{@articles}}">
<table class="table is-fullwidth is-bordered"> <table class="table is-fullwidth is-bordered">
<thead> <thead>
<tr> <tr>
<th>id</th><th>title</th><th>created_at</th> <th>id</th><th>title</th><th>description</th>
<th>status</th><th>priority</th><th>created_at</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<repeat group="{{@articles}}" value="{{@article}}"> <repeat group="{{@tickets}}" value="{{@ticket}}">
<tr> <tr>
<td><a href="/kb/{{@article.id}}">{{@article.id}}</a></td> <td>{{@ticket.id}}</td>
<td>{{@article.title}}</td> <td>{{@ticket.title}}</td>
<td>{{@article.created_at}}</td> <td>{{@ticket.description}}</td>
<td>{{@ticket.status}}</td>
<td>{{@ticket.priority}}</td>
<td>{{@ticket.created_at}}</td>
<td> <td>
<a href="/kb/{{@article.id}}/edit"><i class="fa fa-edit"></i></a> <a href="/ticket/{{@ticket.id}}"><i class="fa fa-eye"></i></a>
<a href="/ticket/{{@ticket.id}}/edit"><i class="fa fa-edit"></i></a>
</td> </td>
</tr> </tr>
</repeat> </repeat>
</tbody> </tbody>
</table> </table>
</check>
<check if="!@articles">
<div class="notification is-info is-light">
<p>No articles found.</p>
</div>
</check>

View File

@ -1,13 +1,9 @@
<h1 class="title">{{@article.title}}</h1> <h1 class="title">Knowledge Article</h1>
<div class="content">
{{ Parsedown::instance()->text(@article.content) }}
</div>
<check if="{{ isset(@tags)}}">
<div class="box"> <div class="box">
<h2 class="title">{{@article.title}}</h2>
<table class="table is-bordered is-fullwidth"> <table class="table is-bordered is-fullwidth">
<thead> <thead>
<tr><th class="has-width-200">Property</th><th>Value</th></tr> <tr><th class="has-width-200">Property</th><th>Value</th></tr>
@ -18,5 +14,5 @@
</repeat> </repeat>
</tbody> </tbody>
</table> </table>
</div> </div>
</check>

View File

@ -1,29 +0,0 @@
<h1 class="title">Create Tag</h1>
<div class="content">
<form action="/tag/create" method="POST">
<div class="field">
<label class="label">Name</label>
<div class="control">
<input name="name" class="input" type="text" placeholder="Tag Name">
</div>
</div>
<div class="field">
<label class="label">Color</label>
<div class="control">
<input name="color" class="input" type="text" placeholder="Tag Color">
</div>
</div>
<div class="field is-grouped is-grouped-right">
<div class="control">
<a class="button is-secondary" href="/tags">Cancel</a>
</div>
<div class="control">
<button class="button is-primary" type="submit">Create Tag</button>
</div>
</div>
</form>
</div>

View File

@ -1,45 +0,0 @@
<h1 class="title">Tags</h1>
<check if="{{isset(@SESSION.error)}}">
<div class="notification is-warning">
{{ @SESSION.error }}
</div>
</check>
<p><a href="/tag/create">create new tag</a></p>
<hr>
<check if="{{ count(@tags) > 1 }}">
<true>
<div class="tags are-medium">
<repeat group="{{ @tags }}" value="{{ @tag }}">
<a class="tag {{ (@tag.color ? 'is-' . @tag.color : '') }}">{{ @tag.name }}</a>
</repeat>
</div>
</true>
<false>
<div class="notification is-size-5">
<span><i class="fa fa-2xl fa-face-sad-tear"></i> </span>No tags found
</div>
</false>
</check>
<div class="box has-background-dark">
<div class="block">
<h3 class="subtitle has-text-white"><i class="fa-solid fa-lightbulb has-text-warning"></i> Color Examples</h3>
<p class="has-text-white">The following color names can be used for tags</p>
</div>
<div class="block">
<span class="tag is-black">Black</span>
<span class="tag is-dark">Dark</span>
<span class="tag is-light">Light</span>
<span class="tag is-white">White</span>
<span class="tag is-primary">Primary</span>
<span class="tag is-link">Link</span>
<span class="tag is-info">Info</span>
<span class="tag is-success">Success</span>
<span class="tag is-warning">Warning</span>
<span class="tag is-danger">Danger</span>
</div>
</div>

View File

@ -11,13 +11,6 @@
{{ BulmaForm::horizontal_field_select('Status:', 'status', ['New', 'In Progress', 'On Hold', 'Completed'])}} {{ BulmaForm::horizontal_field_select('Status:', 'status', ['New', 'In Progress', 'On Hold', 'Completed'])}}
<div class="field is-grouped is-grouped-right">
<div class="control">
<a class="button is-secondary" href="/tickets">Cancel</a>
</div>
<div class="control">
<button class="button is-primary" type="submit">Create Ticket</button> <button class="button is-primary" type="submit">Create Ticket</button>
</div> </div>
</div>
</div>
</form> </form>

View File

@ -12,7 +12,7 @@
<table class="table is-fullwidth is-bordered"> <table class="table is-fullwidth is-bordered">
<thead> <thead>
<tr> <tr>
<th>id</th><th>title</th> <th>id</th><th>title</th><th>description</th>
<th>status</th><th>priority</th><th>created_at</th> <th>status</th><th>priority</th><th>created_at</th>
<th></th> <th></th>
</tr> </tr>
@ -21,12 +21,13 @@
<tbody> <tbody>
<repeat group="{{@tickets}}" value="{{@ticket}}"> <repeat group="{{@tickets}}" value="{{@ticket}}">
<tr> <tr>
<td><a href="/ticket/{{@ticket.id}}">{{@ticket.id}}</a></td> <td>{{@ticket.id}}</td>
<td>{{@ticket.title}}</td> <td>{{@ticket.title}}</td>
<td>{{@ticket.status}}</td> <td>{{@ticket.status}}</td>
<td>{{@ticket.priority}}</td> <td>{{@ticket.priority}}</td>
<td>{{@ticket.created_at}}</td> <td>{{@ticket.created_at}}</td>
<td> <td>
<a href="/ticket/{{@ticket.id}}"><i class="fa fa-eye"></i></a>
<a href="/ticket/{{@ticket.id}}/edit"><i class="fa fa-edit"></i></a> <a href="/ticket/{{@ticket.id}}/edit"><i class="fa fa-edit"></i></a>
</td> </td>
</tr> </tr>