implemented csrf
This commit is contained in:
parent
2d6cd5e48d
commit
4a421564c2
@ -2,8 +2,13 @@
|
|||||||
|
|
||||||
namespace Admin;
|
namespace Admin;
|
||||||
|
|
||||||
|
use CheckCSRF;
|
||||||
|
|
||||||
class TicketOptionsController extends \BaseController
|
class TicketOptionsController extends \BaseController
|
||||||
{
|
{
|
||||||
|
|
||||||
|
use CheckCSRF;
|
||||||
|
|
||||||
public function listPriorities()
|
public function listPriorities()
|
||||||
{
|
{
|
||||||
$this->requireLogin();
|
$this->requireLogin();
|
||||||
@ -24,10 +29,12 @@ class TicketOptionsController extends \BaseController
|
|||||||
$this->renderView('views/admin/priorities/create.html');
|
$this->renderView('views/admin/priorities/create.html');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createPriority()
|
public function createPriority($f3)
|
||||||
{
|
{
|
||||||
$this->requireLogin();
|
$this->requireLogin();
|
||||||
$this->requireAdmin(); // Added admin check
|
$this->requireAdmin(); // Added admin check
|
||||||
|
$this->checkCSRF($f3, '/admin/priority/create');
|
||||||
|
|
||||||
$p = new \TicketPriority($this->getDB());
|
$p = new \TicketPriority($this->getDB());
|
||||||
$p->name = $this->f3->get('POST.name');
|
$p->name = $this->f3->get('POST.name');
|
||||||
$p->sort_order = $this->f3->get('POST.sort_order');
|
$p->sort_order = $this->f3->get('POST.sort_order');
|
||||||
@ -60,6 +67,7 @@ class TicketOptionsController extends \BaseController
|
|||||||
{
|
{
|
||||||
$this->requireLogin();
|
$this->requireLogin();
|
||||||
$this->requireAdmin();
|
$this->requireAdmin();
|
||||||
|
$this->checkCSRF($f3, '/admin/priority/', $params['id'] . '/edit');
|
||||||
$priorityId = $params['id'];
|
$priorityId = $params['id'];
|
||||||
|
|
||||||
$model = new \TicketPriority($this->getDB());
|
$model = new \TicketPriority($this->getDB());
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class AttachmentController {
|
class AttachmentController {
|
||||||
|
|
||||||
use RequiresAuth;
|
use RequiresAuth, CheckCSRF;
|
||||||
|
|
||||||
// list attachments
|
// list attachments
|
||||||
public function index($f3){
|
public function index($f3){
|
||||||
@ -33,6 +33,7 @@ class AttachmentController {
|
|||||||
// handle file upload
|
// handle file upload
|
||||||
public function upload($f3){
|
public function upload($f3){
|
||||||
$this->check_access($f3);
|
$this->check_access($f3);
|
||||||
|
$this->checkCSRF($f3, '/ticket/'.$f3->get('PARAMS.id')); // not ideal for AJAX
|
||||||
|
|
||||||
$ticket_id = (int) $f3->get('PARAMS.id');
|
$ticket_id = (int) $f3->get('PARAMS.id');
|
||||||
$uploaded_by = $f3->get('SESSION.user.id');
|
$uploaded_by = $f3->get('SESSION.user.id');
|
||||||
@ -80,6 +81,9 @@ class AttachmentController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
$f3->reroute('/ticket/'.$ticket_id.'');
|
$f3->reroute('/ticket/'.$ticket_id.'');
|
||||||
|
|
||||||
|
// ideal ajax response:
|
||||||
|
// $f3->json(['success' => true, 'message' => 'file upload success.', 'filename' => $original_name, 'version' => $new_version]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// download attachment
|
// download attachment
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
class AuthController {
|
class AuthController {
|
||||||
|
|
||||||
|
use CheckCSRF;
|
||||||
|
|
||||||
public function showLoginForm($f3){
|
public function showLoginForm($f3){
|
||||||
|
|
||||||
@ -16,6 +17,9 @@ class AuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function login($f3){
|
public function login($f3){
|
||||||
|
// CSRF
|
||||||
|
$this->checkCSRF($f3, '/login');
|
||||||
|
|
||||||
$username = $f3->get('POST.username');
|
$username = $f3->get('POST.username');
|
||||||
$password = $f3->get('POST.password');
|
$password = $f3->get('POST.password');
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
class CommentController {
|
class CommentController {
|
||||||
|
|
||||||
|
use CheckCSRF;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new comment to a ticket.
|
* Add a new comment to a ticket.
|
||||||
* Expects POST data: comment (text)
|
* Expects POST data: comment (text)
|
||||||
@ -13,6 +15,8 @@ class CommentController {
|
|||||||
$f3->reroute('/login');
|
$f3->reroute('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->checkCSRF($f3, '/ticket/' . $f3->get('PARAMS.id'));
|
||||||
|
|
||||||
$ticket_id = (int) $f3->get('PARAMS.id');
|
$ticket_id = (int) $f3->get('PARAMS.id');
|
||||||
$comment_text = $f3->get('POST.comment');
|
$comment_text = $f3->get('POST.comment');
|
||||||
$current_user_id = $f3->get('SESSION.user.id');
|
$current_user_id = $f3->get('SESSION.user.id');
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class KBController implements CRUD {
|
class KBController implements CRUD {
|
||||||
|
|
||||||
use RequiresAuth;
|
use RequiresAuth, CheckCSRF;
|
||||||
|
|
||||||
public function index($f3){
|
public function index($f3){
|
||||||
|
|
||||||
@ -66,6 +66,8 @@ class KBController implements CRUD {
|
|||||||
public function create($f3){
|
public function create($f3){
|
||||||
$this->check_access($f3);
|
$this->check_access($f3);
|
||||||
|
|
||||||
|
$this->checkCSRF($f3, '/kb/create');
|
||||||
|
|
||||||
$title = $f3->get('POST.title');
|
$title = $f3->get('POST.title');
|
||||||
$content = $f3->get('POST.content');
|
$content = $f3->get('POST.content');
|
||||||
$created_by = $f3->get('SESSION.user.id');
|
$created_by = $f3->get('SESSION.user.id');
|
||||||
@ -160,7 +162,10 @@ class KBController implements CRUD {
|
|||||||
* Handle POST to edit existing article
|
* Handle POST to edit existing article
|
||||||
*/
|
*/
|
||||||
public function update($f3){
|
public function update($f3){
|
||||||
|
|
||||||
$this->check_access($f3);
|
$this->check_access($f3);
|
||||||
|
$this->checkCSRF($f3, '/kb/' . $f3->get('PARAMS.id') . '/edit');
|
||||||
|
|
||||||
$article_id = $f3->get('PARAMS.id');
|
$article_id = $f3->get('PARAMS.id');
|
||||||
$db = $f3->get('DB');
|
$db = $f3->get('DB');
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class TagController implements CRUD {
|
class TagController implements CRUD {
|
||||||
|
|
||||||
use RequiresAuth;
|
use RequiresAuth, CheckCSRF;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all tags
|
* List all tags
|
||||||
@ -27,6 +27,7 @@ class TagController implements CRUD {
|
|||||||
|
|
||||||
public function create($f3){
|
public function create($f3){
|
||||||
$this->check_access($f3);
|
$this->check_access($f3);
|
||||||
|
$this->checkCSRF($f3, '/tag/create');
|
||||||
|
|
||||||
$name = $f3->get('POST.name');
|
$name = $f3->get('POST.name');
|
||||||
$color = $f3->get('POST.color');
|
$color = $f3->get('POST.color');
|
||||||
|
|||||||
@ -2,8 +2,12 @@
|
|||||||
|
|
||||||
class ThemeController
|
class ThemeController
|
||||||
{
|
{
|
||||||
|
use CheckCSRF;
|
||||||
|
|
||||||
function toggle($f3)
|
function toggle($f3)
|
||||||
{
|
{
|
||||||
|
$this->checkCSRF($f3, $f3->get('HEADERS.Referer') ?: '/');
|
||||||
|
|
||||||
$current = $f3->get('SESSION.theme') ?: 'light';
|
$current = $f3->get('SESSION.theme') ?: 'light';
|
||||||
$new_theme = ($current === 'light') ? 'dark' : 'light';
|
$new_theme = ($current === 'light') ? 'dark' : 'light';
|
||||||
$f3->set('SESSION.theme', $new_theme);
|
$f3->set('SESSION.theme', $new_theme);
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class TicketController extends BaseController implements CRUD {
|
class TicketController extends BaseController implements CRUD {
|
||||||
|
|
||||||
use RequiresAuth;
|
use RequiresAuth, CheckCSRF;
|
||||||
|
|
||||||
// list all tickts
|
// list all tickts
|
||||||
public function index($f3){
|
public function index($f3){
|
||||||
@ -58,10 +58,15 @@ class TicketController extends BaseController implements CRUD {
|
|||||||
$priorities = (new TicketPriority($db))->findAll();
|
$priorities = (new TicketPriority($db))->findAll();
|
||||||
$statuses = (new TicketStatus($db))->findAll();
|
$statuses = (new TicketStatus($db))->findAll();
|
||||||
|
|
||||||
|
// TODO: this needs moving into a model?
|
||||||
|
$users = $this->getDB()->exec('SELECT id, username, display_name FROM users ORDER BY display_name ASC');
|
||||||
|
$users = array_merge([['id'=>'-1', 'display_name'=>'--']], $users);
|
||||||
|
|
||||||
$this->requireLogin();
|
$this->requireLogin();
|
||||||
$this->renderView('views/ticket/create.html',[
|
$this->renderView('views/ticket/create.html',[
|
||||||
'priorities' => $priorities,
|
'priorities' => $priorities,
|
||||||
'statuses' => $statuses
|
'statuses' => $statuses,
|
||||||
|
'users' => $users
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +75,7 @@ class TicketController extends BaseController implements CRUD {
|
|||||||
public function create($f3){
|
public function create($f3){
|
||||||
|
|
||||||
$this->requireLogin();
|
$this->requireLogin();
|
||||||
|
$this->checkCSRF($f3, '/ticket/create');
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'title' => $this->f3->get('POST.title'),
|
'title' => $this->f3->get('POST.title'),
|
||||||
@ -114,12 +120,16 @@ class TicketController extends BaseController implements CRUD {
|
|||||||
// dropdowns
|
// dropdowns
|
||||||
$priorities = (new TicketPriority($this->getDB()))->findAll();
|
$priorities = (new TicketPriority($this->getDB()))->findAll();
|
||||||
$statuses = (new TicketStatus($this->getDB()))->findAll();
|
$statuses = (new TicketStatus($this->getDB()))->findAll();
|
||||||
|
// TODO: this needs moving into a model?
|
||||||
|
$users = $this->getDB()->exec('SELECT id, username, display_name FROM users ORDER BY display_name ASC');
|
||||||
|
$users = array_merge([['id'=>'-1', 'display_name'=>'--']], $users);
|
||||||
|
|
||||||
$this->renderView('views/ticket/edit.html',[
|
$this->renderView('views/ticket/edit.html',[
|
||||||
'ticket' => $ticket,
|
'ticket' => $ticket,
|
||||||
'ticket_meta' => $ticket->getMeta(),
|
'ticket_meta' => $ticket->getMeta(),
|
||||||
'priorities' => $priorities,
|
'priorities' => $priorities,
|
||||||
'statuses' => $statuses
|
'statuses' => $statuses,
|
||||||
|
'users' => $users
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -130,6 +140,7 @@ class TicketController extends BaseController implements CRUD {
|
|||||||
{
|
{
|
||||||
|
|
||||||
$this->requireLogin();
|
$this->requireLogin();
|
||||||
|
$this->checkCSRF($f3, '/ticket/create');
|
||||||
|
|
||||||
$ticket_id = $this->f3->get('PARAMS.id');
|
$ticket_id = $this->f3->get('PARAMS.id');
|
||||||
$ticket_mapper = new Ticket($this->getDB());
|
$ticket_mapper = new Ticket($this->getDB());
|
||||||
@ -146,7 +157,8 @@ class TicketController extends BaseController implements CRUD {
|
|||||||
'description' => $this->f3->get('POST.description'),
|
'description' => $this->f3->get('POST.description'),
|
||||||
'priority_id' => $this->f3->get('POST.priority_id'),
|
'priority_id' => $this->f3->get('POST.priority_id'),
|
||||||
'status_id' => $this->f3->get('POST.status_id'),
|
'status_id' => $this->f3->get('POST.status_id'),
|
||||||
'updated_by' => $this->f3->get('SESSION.user.id')
|
'updated_by' => $this->f3->get('SESSION.user.id') ,
|
||||||
|
'assigned_to' => $this->f3->get('POST.assigned_to') ?: null
|
||||||
];
|
];
|
||||||
$ticket->updateTicket($data);
|
$ticket->updateTicket($data);
|
||||||
|
|
||||||
@ -162,6 +174,7 @@ class TicketController extends BaseController implements CRUD {
|
|||||||
// subtask
|
// subtask
|
||||||
public function addSubtask($f3){
|
public function addSubtask($f3){
|
||||||
$this->requireLogin();
|
$this->requireLogin();
|
||||||
|
$this->checkCSRF($f3, '/ticket/create');
|
||||||
|
|
||||||
$parent_id = (int) $f3->get('PARAMS.id');
|
$parent_id = (int) $f3->get('PARAMS.id');
|
||||||
$child_id = (int) $f3->get('POST.child_ticket_id');
|
$child_id = (int) $f3->get('POST.child_ticket_id');
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class UserController implements CRUD {
|
class UserController implements CRUD {
|
||||||
|
|
||||||
use RequiresAuth;
|
use RequiresAuth, CheckCSRF;
|
||||||
|
|
||||||
// list all users (admin only)
|
// list all users (admin only)
|
||||||
|
|
||||||
@ -44,6 +44,7 @@ class UserController implements CRUD {
|
|||||||
public function update($f3){
|
public function update($f3){
|
||||||
|
|
||||||
$this->check_access($f3);
|
$this->check_access($f3);
|
||||||
|
$this->checkCSRF($f3, '/user/' . $f3->get('PARAMS.id') . '/edit');
|
||||||
|
|
||||||
$user_id = (int) $f3->get('PARAMS.id');
|
$user_id = (int) $f3->get('PARAMS.id');
|
||||||
$new_username = $f3->get('POST.username');
|
$new_username = $f3->get('POST.username');
|
||||||
|
|||||||
36
app/extensions/CSRFHelper.php
Normal file
36
app/extensions/CSRFHelper.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class CSRFHelper {
|
||||||
|
|
||||||
|
const TOKEN_NAME = 'csrf_token';
|
||||||
|
|
||||||
|
public static function token():string {
|
||||||
|
$f3 = \Base::instance();
|
||||||
|
if(!$f3->exists('SESSION.' . self::TOKEN_NAME)) {
|
||||||
|
$token = bin2hex(random_bytes(32));
|
||||||
|
$f3->set('SESSION.' . self::TOKEN_NAME, $token);
|
||||||
|
}
|
||||||
|
return $f3->get('SESSION.' . self::TOKEN_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function verify(?string $submitted_token): bool {
|
||||||
|
$f3 = \Base::instance();
|
||||||
|
$session_token = $f3->get('SESSION.' . self::TOKEN_NAME);
|
||||||
|
|
||||||
|
if(empty($submitted_token) || empty($session_token)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hash_equals($session_token, $submitted_token)){
|
||||||
|
$f3->clear('SESSION.' . self::TOKEN_NAME);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function field(): string {
|
||||||
|
return '<input type="hidden" name="'.self::TOKEN_NAME.'" value="'.self::token().'">';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
13
app/traits/CheckCSRF.php
Normal file
13
app/traits/CheckCSRF.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
trait CheckCSRF {
|
||||||
|
|
||||||
|
public function checkCSRF($f3, $reroute){
|
||||||
|
if(!\CSRFHelper::verify($f3->get('POST.' . \CSRFHelper::TOKEN_NAME))){
|
||||||
|
$f3->set('SESSION.error', 'CSRF token validation failed.');
|
||||||
|
$f3->reroute($reroute);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,89 +1,98 @@
|
|||||||
<div class="block">
|
<div class="block">
|
||||||
<style>
|
<input type="hidden" id="csrf-token-clipboard" value="{{ \CSRFHelper::token() }}">
|
||||||
#upload-area {
|
<style>
|
||||||
height: 250px;
|
#upload-area {
|
||||||
border: 2px dashed #aaa;
|
height: 250px;
|
||||||
display: flex;
|
border: 2px dashed #aaa;
|
||||||
align-items: center;
|
display: flex;
|
||||||
margin-top: 10px;
|
align-items: center;
|
||||||
color: #555;
|
margin-top: 10px;
|
||||||
font-family: sans-serif;
|
color: #555;
|
||||||
text-align: center;
|
font-family: sans-serif;
|
||||||
}
|
text-align: center;
|
||||||
|
|
||||||
#upload-area.hover {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
border-color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
#preview {
|
|
||||||
max-width:100%;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div id="upload-area" contenteditable="true">
|
|
||||||
Paste or drag an image here
|
|
||||||
</div>
|
|
||||||
<img id="preview" alt="image preview" hidden>
|
|
||||||
<p id="status"></p>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const area = document.getElementById('upload-area');
|
|
||||||
const preview = document.getElementById('preview');
|
|
||||||
const status = document.getElementById('status');
|
|
||||||
|
|
||||||
async function uploadImage(file){
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = () => {
|
|
||||||
preview.src = reader.result;
|
|
||||||
preview.hidden = false;
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('attachment', file);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch('/ticket/{{@ticket->id}}/attachments/upload', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
const text = await res.text();
|
|
||||||
status.textContent = text;
|
|
||||||
} catch (e) {
|
|
||||||
status.textContent = 'upload failed.'
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// paste
|
#upload-area.hover {
|
||||||
area.addEventListener('paste', (e) => {
|
background-color: #f0f0f0;
|
||||||
for(let item of e.clipboardData.items){
|
border-color: #444;
|
||||||
if(item.type.startsWith('image/')){
|
}
|
||||||
uploadImage(item.getAsFile());
|
|
||||||
|
#preview {
|
||||||
|
max-width: 100%;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div id="upload-area" contenteditable="true">
|
||||||
|
Paste or drag an image here
|
||||||
|
</div>
|
||||||
|
<img id="preview" alt="image preview" hidden>
|
||||||
|
<p id="status"></p>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const area = document.getElementById('upload-area');
|
||||||
|
const preview = document.getElementById('preview');
|
||||||
|
const status = document.getElementById('status');
|
||||||
|
const csrfTokenClipboard = document.getElementById('csrf-token-clipboard').value;
|
||||||
|
|
||||||
|
async function uploadImage(file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => {
|
||||||
|
preview.src = reader.result;
|
||||||
|
preview.hidden = false;
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('attachment', file);
|
||||||
|
formData.append('csrf_token', csrfTokenClipboard);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/ticket/{{@ticket->id}}/attachments/upload', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
if(res.ok){
|
||||||
|
const responseData = await res.json(); // assuming json returned
|
||||||
|
statusMsg.textContent = responseData || 'Upload success, no message';
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
const errorData = await res.json();
|
||||||
|
statusMsg.textContent = 'Upload failed: ' + (errorData.error || res.statusText);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
status.textContent = 'upload failed.'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// drag and drop
|
// paste
|
||||||
area.addEventListener('dragover', (e) => {
|
area.addEventListener('paste', (e) => {
|
||||||
e.preventDefault();
|
for (let item of e.clipboardData.items) {
|
||||||
area.classList.add('hover');
|
if (item.type.startsWith('image/')) {
|
||||||
});
|
uploadImage(item.getAsFile());
|
||||||
|
}
|
||||||
area.addEventListener('dragLeave', ()=> {
|
|
||||||
area.classList.remove('hover');
|
|
||||||
});
|
|
||||||
|
|
||||||
area.addEventListener('drop', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
area.classList.remove('hover');
|
|
||||||
const files = e.dataTransfer.files;
|
|
||||||
for(let file of files){
|
|
||||||
if(file.type.startsWith('image/')){
|
|
||||||
uploadImage(file);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
</script>
|
// drag and drop
|
||||||
|
area.addEventListener('dragover', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
area.classList.add('hover');
|
||||||
|
});
|
||||||
|
|
||||||
|
area.addEventListener('dragLeave', () => {
|
||||||
|
area.classList.remove('hover');
|
||||||
|
});
|
||||||
|
|
||||||
|
area.addEventListener('drop', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
area.classList.remove('hover');
|
||||||
|
const files = e.dataTransfer.files;
|
||||||
|
for (let file of files) {
|
||||||
|
if (file.type.startsWith('image/')) {
|
||||||
|
uploadImage(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</div>
|
</div>
|
||||||
@ -55,7 +55,8 @@
|
|||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
<div class="navbar-item">
|
<div class="navbar-item">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<form id="theme-toggle-form" method="post" action="/toggle-theme" style="display:inline">
|
<form id="theme-toggle-form" method="POST" action="/toggle-theme" style="display:inline">
|
||||||
|
{{ \CSRFHelper::field() | raw }}
|
||||||
<button type="submit" id="theme-toggle-button" class="button is-small" aria-label="Toggle Theme">
|
<button type="submit" id="theme-toggle-button" class="button is-small" aria-label="Toggle Theme">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="fas fa-circle-half-stroke" id="theme-icon"></i>
|
<i class="fas fa-circle-half-stroke" id="theme-icon"></i>
|
||||||
|
|||||||
@ -1,2 +1,6 @@
|
|||||||
<h1 class="title">Create Ticket Priority</h1>
|
<h1 class="title">Create Ticket Priority</h1>
|
||||||
<p>TODO:</p>
|
<p>TODO:</p>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
{{ \CSRFHelper::field() | raw }}
|
||||||
|
</form>
|
||||||
@ -37,6 +37,7 @@
|
|||||||
</check>
|
</check>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<form action="/ticket/{{@PARAMS.id}}/attachments/upload" method="POST" enctype="multipart/form-data">
|
<form action="/ticket/{{@PARAMS.id}}/attachments/upload" method="POST" enctype="multipart/form-data">
|
||||||
|
{{ \CSRFHelper::field() | raw }}
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
<div class="control has-icons-left"><!-- is-expanded -->
|
<div class="control has-icons-left"><!-- is-expanded -->
|
||||||
<input class="input" type="file" name="attachment" required>
|
<input class="input" type="file" name="attachment" required>
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
</check>
|
</check>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<form action="/ticket/{{@PARAMS.id}}/comment" method="POST">
|
<form action="/ticket/{{@PARAMS.id}}/comment" method="POST">
|
||||||
|
{{ \CSRFHelper::field() | raw }}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Add comment:</label>
|
<label class="label">Add comment:</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<form action="/kb/create" method="POST">
|
<form action="/kb/create" method="POST">
|
||||||
|
{{ \CSRFHelper::field() | raw }}
|
||||||
|
|
||||||
<bulma type="H_FIELD_INPUT" label="Title:" name="title" value=""></bulma>
|
<bulma type="H_FIELD_INPUT" label="Title:" name="title" value=""></bulma>
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<form action="/kb/{{@article.id}}/update" method="POST">
|
<form action="/kb/{{@article.id}}/update" method="POST">
|
||||||
|
{{ \CSRFHelper::field() | raw }}
|
||||||
|
|
||||||
<bulma type="H_FIELD_INPUT" label="Title:" name="title" value="{{@article.title}}"></bulma>
|
<bulma type="H_FIELD_INPUT" label="Title:" name="title" value="{{@article.title}}"></bulma>
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
</check>
|
</check>
|
||||||
|
|
||||||
<form action="/login" method="POST">
|
<form action="/login" method="POST">
|
||||||
|
{{ \CSRFHelper::field() | raw }}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<p class="control has-icons-left has-icons-right">
|
<p class="control has-icons-left has-icons-right">
|
||||||
<input name="username" class="input" type="text" placeholder="Username">
|
<input name="username" class="input" type="text" placeholder="Username">
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<form action="/tag/create" method="POST">
|
<form action="/tag/create" method="POST">
|
||||||
|
{{ \CSRFHelper::field() | raw }}
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Name</label>
|
<label class="label">Name</label>
|
||||||
|
|||||||
@ -1,32 +1,10 @@
|
|||||||
<h1 class="title">Create Ticket Form</h1>
|
<h1 class="title">Create Ticket Form</h1>
|
||||||
|
|
||||||
<form action="/ticket/create" method="POST">
|
<form action="/ticket/create" method="POST">
|
||||||
|
{{ \CSRFHelper::field() | raw }}
|
||||||
<bulma type="H_FIELD_INPUT" label="Title:" name="title" value=""></bulma>
|
<div class="is-flex">
|
||||||
<bulma type="H_FIELD_INPUT" label="Created At:" name="created_at" value=""></bulma>
|
<div class="is-flex-grow-1">
|
||||||
<bulma type="H_FIELD_TEXTAREA" label="Description:" name="description" value=""></bulma>
|
<bulma type="FIELD_INPUT" name="title" value="" class="mr-3"></bulma>
|
||||||
|
|
||||||
<!-- priority and status -->
|
|
||||||
<bulma type="H_FIELD_SELECT_NEW" label="Priority:" name="priority_id"
|
|
||||||
options="priorities" option_value="id" option_name="name"
|
|
||||||
selected="2"></bulma>
|
|
||||||
|
|
||||||
<bulma type="H_FIELD_SELECT_NEW" label="Status:" name="status_id"
|
|
||||||
options="statuses" option_value="id" option_name="name"
|
|
||||||
selected="1"></bulma>
|
|
||||||
|
|
||||||
<!-- custom fields -->
|
|
||||||
<hr>
|
|
||||||
<div class="block">
|
|
||||||
<div class="field is-grouped is-grouped-right">
|
|
||||||
<div class="control">
|
|
||||||
<label class="label">Key:</label>
|
|
||||||
<input class="input" type="text" name="meta_key[]" placeholder="eg. Department">
|
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
<label class="label">Value:</label>
|
|
||||||
<input class="input" type="text" name="meta_value[]" placeholder="eg. Finance">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field is-grouped is-grouped-right">
|
<div class="field is-grouped is-grouped-right">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
<!-- Ticket - Edit -->
|
<!-- Ticket - Edit -->
|
||||||
<!-- made to look more in line with view-->
|
<!-- made to look more in line with view-->
|
||||||
<form action="/ticket/{{ @PARAMS.id }}/update" method="POST">
|
<form action="/ticket/{{ @PARAMS.id }}/update" method="POST">
|
||||||
|
{{ \CSRFHelper::field() | raw }}
|
||||||
<div class="is-flex">
|
<div class="is-flex">
|
||||||
<div class="is-flex-grow-1">
|
<div class="is-flex-grow-1">
|
||||||
<bulma type="FIELD_INPUT" name="title" value="{{@ticket.title}}" class="mr-3"></bulma>
|
<bulma type="FIELD_INPUT" name="title" value="{{@ticket.title}}" class="mr-3"></bulma>
|
||||||
</div>
|
</div>
|
||||||
<div class="field is-grouped">
|
<div class="field is-grouped">
|
||||||
<!-- <p class="control"><a class="button" href="/ticket/{{ @ticket.id}}/edit">edit ticket</a></p>
|
|
||||||
<p class="control"><a class="button is-primary" href="/ticket/create">new ticket</a></p>-->
|
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<a class="button is-secondary" href="/ticket/{{ @ticket.id }}">Cancel</a>
|
<a class="button is-secondary" href="/ticket/{{ @ticket.id }}">Cancel</a>
|
||||||
</div>
|
</div>
|
||||||
@ -57,9 +56,15 @@
|
|||||||
options="{{@priorities}}" option_value="id" option_name="name"
|
options="{{@priorities}}" option_value="id" option_name="name"
|
||||||
selected="{{@ticket.priority_id}}"></bulma>
|
selected="{{@ticket.priority_id}}"></bulma>
|
||||||
|
|
||||||
<bulma type="FIELD_SELECT" label="Status:" name="status_id"
|
<bulma type="FIELD_SELECT" label="Status:" name="status_id"
|
||||||
options="{{@statuses}}" option_value="id" option_name="name"
|
options="{{@statuses}}" option_value="id" option_name="name"
|
||||||
selected="{{@ticket.status_id}}"></bulma>
|
selected="{{@ticket.status_id}}"></bulma>
|
||||||
|
|
||||||
|
<bulma type="FIELD_SELECT" label="Assigned User:" name="assigned_to"
|
||||||
|
options="{{@users}}" option_value="id" option_name="display_name"
|
||||||
|
selected="{{@ticket.assigned_to}}"></bulma>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- additional data -->
|
<!-- additional data -->
|
||||||
<div class="block">
|
<div class="block">
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
<form method="POST" action="/user/{{@edit_user.id}}/update">
|
<form method="POST" action="/user/{{@edit_user.id}}/update">
|
||||||
|
{{ \CSRFHelper::field() | raw }}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Username</label>
|
<label class="label">Username</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user