implemented csrf
This commit is contained in:
parent
2d6cd5e48d
commit
4a421564c2
@ -2,8 +2,13 @@
|
||||
|
||||
namespace Admin;
|
||||
|
||||
use CheckCSRF;
|
||||
|
||||
class TicketOptionsController extends \BaseController
|
||||
{
|
||||
|
||||
use CheckCSRF;
|
||||
|
||||
public function listPriorities()
|
||||
{
|
||||
$this->requireLogin();
|
||||
@ -24,10 +29,12 @@ class TicketOptionsController extends \BaseController
|
||||
$this->renderView('views/admin/priorities/create.html');
|
||||
}
|
||||
|
||||
public function createPriority()
|
||||
public function createPriority($f3)
|
||||
{
|
||||
$this->requireLogin();
|
||||
$this->requireAdmin(); // Added admin check
|
||||
$this->checkCSRF($f3, '/admin/priority/create');
|
||||
|
||||
$p = new \TicketPriority($this->getDB());
|
||||
$p->name = $this->f3->get('POST.name');
|
||||
$p->sort_order = $this->f3->get('POST.sort_order');
|
||||
@ -60,6 +67,7 @@ class TicketOptionsController extends \BaseController
|
||||
{
|
||||
$this->requireLogin();
|
||||
$this->requireAdmin();
|
||||
$this->checkCSRF($f3, '/admin/priority/', $params['id'] . '/edit');
|
||||
$priorityId = $params['id'];
|
||||
|
||||
$model = new \TicketPriority($this->getDB());
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
class AttachmentController {
|
||||
|
||||
use RequiresAuth;
|
||||
use RequiresAuth, CheckCSRF;
|
||||
|
||||
// list attachments
|
||||
public function index($f3){
|
||||
@ -33,6 +33,7 @@ class AttachmentController {
|
||||
// handle file upload
|
||||
public function upload($f3){
|
||||
$this->check_access($f3);
|
||||
$this->checkCSRF($f3, '/ticket/'.$f3->get('PARAMS.id')); // not ideal for AJAX
|
||||
|
||||
$ticket_id = (int) $f3->get('PARAMS.id');
|
||||
$uploaded_by = $f3->get('SESSION.user.id');
|
||||
@ -80,6 +81,9 @@ class AttachmentController {
|
||||
);
|
||||
|
||||
$f3->reroute('/ticket/'.$ticket_id.'');
|
||||
|
||||
// ideal ajax response:
|
||||
// $f3->json(['success' => true, 'message' => 'file upload success.', 'filename' => $original_name, 'version' => $new_version]);
|
||||
}
|
||||
|
||||
// download attachment
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
class AuthController {
|
||||
|
||||
use CheckCSRF;
|
||||
|
||||
public function showLoginForm($f3){
|
||||
|
||||
@ -16,6 +17,9 @@ class AuthController {
|
||||
}
|
||||
|
||||
public function login($f3){
|
||||
// CSRF
|
||||
$this->checkCSRF($f3, '/login');
|
||||
|
||||
$username = $f3->get('POST.username');
|
||||
$password = $f3->get('POST.password');
|
||||
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
class CommentController {
|
||||
|
||||
use CheckCSRF;
|
||||
|
||||
/**
|
||||
* Add a new comment to a ticket.
|
||||
* Expects POST data: comment (text)
|
||||
@ -13,6 +15,8 @@ class CommentController {
|
||||
$f3->reroute('/login');
|
||||
}
|
||||
|
||||
$this->checkCSRF($f3, '/ticket/' . $f3->get('PARAMS.id'));
|
||||
|
||||
$ticket_id = (int) $f3->get('PARAMS.id');
|
||||
$comment_text = $f3->get('POST.comment');
|
||||
$current_user_id = $f3->get('SESSION.user.id');
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
class KBController implements CRUD {
|
||||
|
||||
use RequiresAuth;
|
||||
use RequiresAuth, CheckCSRF;
|
||||
|
||||
public function index($f3){
|
||||
|
||||
@ -66,6 +66,8 @@ class KBController implements CRUD {
|
||||
public function create($f3){
|
||||
$this->check_access($f3);
|
||||
|
||||
$this->checkCSRF($f3, '/kb/create');
|
||||
|
||||
$title = $f3->get('POST.title');
|
||||
$content = $f3->get('POST.content');
|
||||
$created_by = $f3->get('SESSION.user.id');
|
||||
@ -160,7 +162,10 @@ class KBController implements CRUD {
|
||||
* Handle POST to edit existing article
|
||||
*/
|
||||
public function update($f3){
|
||||
|
||||
$this->check_access($f3);
|
||||
$this->checkCSRF($f3, '/kb/' . $f3->get('PARAMS.id') . '/edit');
|
||||
|
||||
$article_id = $f3->get('PARAMS.id');
|
||||
$db = $f3->get('DB');
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
class TagController implements CRUD {
|
||||
|
||||
use RequiresAuth;
|
||||
use RequiresAuth, CheckCSRF;
|
||||
|
||||
/**
|
||||
* List all tags
|
||||
@ -27,6 +27,7 @@ class TagController implements CRUD {
|
||||
|
||||
public function create($f3){
|
||||
$this->check_access($f3);
|
||||
$this->checkCSRF($f3, '/tag/create');
|
||||
|
||||
$name = $f3->get('POST.name');
|
||||
$color = $f3->get('POST.color');
|
||||
|
||||
@ -2,8 +2,12 @@
|
||||
|
||||
class ThemeController
|
||||
{
|
||||
use CheckCSRF;
|
||||
|
||||
function toggle($f3)
|
||||
{
|
||||
$this->checkCSRF($f3, $f3->get('HEADERS.Referer') ?: '/');
|
||||
|
||||
$current = $f3->get('SESSION.theme') ?: 'light';
|
||||
$new_theme = ($current === 'light') ? 'dark' : 'light';
|
||||
$f3->set('SESSION.theme', $new_theme);
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
class TicketController extends BaseController implements CRUD {
|
||||
|
||||
use RequiresAuth;
|
||||
use RequiresAuth, CheckCSRF;
|
||||
|
||||
// list all tickts
|
||||
public function index($f3){
|
||||
@ -58,10 +58,15 @@ class TicketController extends BaseController implements CRUD {
|
||||
$priorities = (new TicketPriority($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->renderView('views/ticket/create.html',[
|
||||
'priorities' => $priorities,
|
||||
'statuses' => $statuses
|
||||
'statuses' => $statuses,
|
||||
'users' => $users
|
||||
]);
|
||||
}
|
||||
|
||||
@ -70,6 +75,7 @@ class TicketController extends BaseController implements CRUD {
|
||||
public function create($f3){
|
||||
|
||||
$this->requireLogin();
|
||||
$this->checkCSRF($f3, '/ticket/create');
|
||||
|
||||
$data = [
|
||||
'title' => $this->f3->get('POST.title'),
|
||||
@ -114,12 +120,16 @@ class TicketController extends BaseController implements CRUD {
|
||||
// dropdowns
|
||||
$priorities = (new TicketPriority($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',[
|
||||
'ticket' => $ticket,
|
||||
'ticket_meta' => $ticket->getMeta(),
|
||||
'priorities' => $priorities,
|
||||
'statuses' => $statuses
|
||||
'statuses' => $statuses,
|
||||
'users' => $users
|
||||
]
|
||||
);
|
||||
return;
|
||||
@ -130,6 +140,7 @@ class TicketController extends BaseController implements CRUD {
|
||||
{
|
||||
|
||||
$this->requireLogin();
|
||||
$this->checkCSRF($f3, '/ticket/create');
|
||||
|
||||
$ticket_id = $this->f3->get('PARAMS.id');
|
||||
$ticket_mapper = new Ticket($this->getDB());
|
||||
@ -146,7 +157,8 @@ class TicketController extends BaseController implements CRUD {
|
||||
'description' => $this->f3->get('POST.description'),
|
||||
'priority_id' => $this->f3->get('POST.priority_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);
|
||||
|
||||
@ -162,6 +174,7 @@ class TicketController extends BaseController implements CRUD {
|
||||
// subtask
|
||||
public function addSubtask($f3){
|
||||
$this->requireLogin();
|
||||
$this->checkCSRF($f3, '/ticket/create');
|
||||
|
||||
$parent_id = (int) $f3->get('PARAMS.id');
|
||||
$child_id = (int) $f3->get('POST.child_ticket_id');
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
class UserController implements CRUD {
|
||||
|
||||
use RequiresAuth;
|
||||
use RequiresAuth, CheckCSRF;
|
||||
|
||||
// list all users (admin only)
|
||||
|
||||
@ -44,6 +44,7 @@ class UserController implements CRUD {
|
||||
public function update($f3){
|
||||
|
||||
$this->check_access($f3);
|
||||
$this->checkCSRF($f3, '/user/' . $f3->get('PARAMS.id') . '/edit');
|
||||
|
||||
$user_id = (int) $f3->get('PARAMS.id');
|
||||
$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">
|
||||
<style>
|
||||
#upload-area {
|
||||
height: 250px;
|
||||
border: 2px dashed #aaa;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
color: #555;
|
||||
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.'
|
||||
<input type="hidden" id="csrf-token-clipboard" value="{{ \CSRFHelper::token() }}">
|
||||
<style>
|
||||
#upload-area {
|
||||
height: 250px;
|
||||
border: 2px dashed #aaa;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
color: #555;
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
// paste
|
||||
area.addEventListener('paste', (e) => {
|
||||
for(let item of e.clipboardData.items){
|
||||
if(item.type.startsWith('image/')){
|
||||
uploadImage(item.getAsFile());
|
||||
#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');
|
||||
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
|
||||
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);
|
||||
// paste
|
||||
area.addEventListener('paste', (e) => {
|
||||
for (let item of e.clipboardData.items) {
|
||||
if (item.type.startsWith('image/')) {
|
||||
uploadImage(item.getAsFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</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>
|
||||
@ -55,7 +55,8 @@
|
||||
<div class="navbar-end">
|
||||
<div class="navbar-item">
|
||||
<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">
|
||||
<span class="icon">
|
||||
<i class="fas fa-circle-half-stroke" id="theme-icon"></i>
|
||||
|
||||
@ -1,2 +1,6 @@
|
||||
<h1 class="title">Create Ticket Priority</h1>
|
||||
<p>TODO:</p>
|
||||
|
||||
<form method="POST">
|
||||
{{ \CSRFHelper::field() | raw }}
|
||||
</form>
|
||||
@ -37,6 +37,7 @@
|
||||
</check>
|
||||
<div class="block">
|
||||
<form action="/ticket/{{@PARAMS.id}}/attachments/upload" method="POST" enctype="multipart/form-data">
|
||||
{{ \CSRFHelper::field() | raw }}
|
||||
<div class="field has-addons">
|
||||
<div class="control has-icons-left"><!-- is-expanded -->
|
||||
<input class="input" type="file" name="attachment" required>
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
</check>
|
||||
<div class="block">
|
||||
<form action="/ticket/{{@PARAMS.id}}/comment" method="POST">
|
||||
{{ \CSRFHelper::field() | raw }}
|
||||
<div class="field">
|
||||
<label class="label">Add comment:</label>
|
||||
<div class="control">
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
<div class="content">
|
||||
<form action="/kb/create" method="POST">
|
||||
{{ \CSRFHelper::field() | raw }}
|
||||
|
||||
<bulma type="H_FIELD_INPUT" label="Title:" name="title" value=""></bulma>
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
<form action="/kb/{{@article.id}}/update" method="POST">
|
||||
{{ \CSRFHelper::field() | raw }}
|
||||
|
||||
<bulma type="H_FIELD_INPUT" label="Title:" name="title" value="{{@article.title}}"></bulma>
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
</check>
|
||||
|
||||
<form action="/login" method="POST">
|
||||
|
||||
{{ \CSRFHelper::field() | raw }}
|
||||
<div class="field">
|
||||
<p class="control has-icons-left has-icons-right">
|
||||
<input name="username" class="input" type="text" placeholder="Username">
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
<div class="content">
|
||||
<form action="/tag/create" method="POST">
|
||||
{{ \CSRFHelper::field() | raw }}
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Name</label>
|
||||
|
||||
@ -1,32 +1,10 @@
|
||||
<h1 class="title">Create Ticket Form</h1>
|
||||
|
||||
<form action="/ticket/create" method="POST">
|
||||
|
||||
<bulma type="H_FIELD_INPUT" label="Title:" name="title" value=""></bulma>
|
||||
<bulma type="H_FIELD_INPUT" label="Created At:" name="created_at" value=""></bulma>
|
||||
<bulma type="H_FIELD_TEXTAREA" label="Description:" name="description" value=""></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>
|
||||
{{ \CSRFHelper::field() | raw }}
|
||||
<div class="is-flex">
|
||||
<div class="is-flex-grow-1">
|
||||
<bulma type="FIELD_INPUT" name="title" value="" class="mr-3"></bulma>
|
||||
</div>
|
||||
<div class="field is-grouped is-grouped-right">
|
||||
<div class="control">
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
<!-- Ticket - Edit -->
|
||||
<!-- made to look more in line with view-->
|
||||
<form action="/ticket/{{ @PARAMS.id }}/update" method="POST">
|
||||
{{ \CSRFHelper::field() | raw }}
|
||||
<div class="is-flex">
|
||||
<div class="is-flex-grow-1">
|
||||
<bulma type="FIELD_INPUT" name="title" value="{{@ticket.title}}" class="mr-3"></bulma>
|
||||
</div>
|
||||
<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">
|
||||
<a class="button is-secondary" href="/ticket/{{ @ticket.id }}">Cancel</a>
|
||||
</div>
|
||||
@ -57,9 +56,15 @@
|
||||
options="{{@priorities}}" option_value="id" option_name="name"
|
||||
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"
|
||||
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>
|
||||
<!-- additional data -->
|
||||
<div class="block">
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
|
||||
|
||||
<form method="POST" action="/user/{{@edit_user.id}}/update">
|
||||
{{ \CSRFHelper::field() | raw }}
|
||||
<div class="field">
|
||||
<label class="label">Username</label>
|
||||
<div class="control">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user