From 8d0b903d34e4365755b8ea7b08fc8f7ea81c1d20 Mon Sep 17 00:00:00 2001 From: tp_dhu Date: Mon, 24 Mar 2025 01:20:41 +0000 Subject: [PATCH] no $db->exec in controller. all related db operations moved into Ticket model. requireLogin is centralised to BaseController --- app/controllers/TicketController.php | 333 +++++++-------------------- app/models/Ticket.php | 140 ++++++++++- 2 files changed, 220 insertions(+), 253 deletions(-) diff --git a/app/controllers/TicketController.php b/app/controllers/TicketController.php index 3374152..1d3b223 100644 --- a/app/controllers/TicketController.php +++ b/app/controllers/TicketController.php @@ -1,24 +1,22 @@ check_access($f3); - $db = $f3->get('DB'); + $this->requireLogin(); // retrieve tickets - $tickets = $db->exec('SELECT * FROM tickets ORDER BY created_at DESC'); - - // pass data to template - $f3->set('tickets', $tickets); + $ticket_mapper = new Ticket($this->getDB()); + $tickets = $ticket_mapper->findAll(); // render - $f3->set('content', '../ui/views/ticket/index.html'); - echo \Template::instance()->render('../ui/templates/layout.html'); + $this->renderView('../ui/views/ticket/index.html', + ['tickets' => $tickets] + ); $f3->clear('SESSION.error'); } @@ -26,296 +24,129 @@ class TicketController implements CRUD { // view a single ticket // TODO_PROJECTS: show a link back to the related project public function view($f3){ - $this->check_access($f3); + $this->requireLogin(); $ticket_id = $f3->get('PARAMS.id'); - $db = $f3->get('DB'); - - $this->get_ticket($f3, $db, $ticket_id); - $this->get_child_tickets($f3, $db, $ticket_id); - $this->get_parent_tickets($f3, $db, $ticket_id); - $this->get_custom_fields($f3, $db, $ticket_id); + $ticket_mapper = new Ticket($this->getDB()); + $ticket = $ticket_mapper->findById($ticket_id); // render - // $f3->set('js', 'ticket_view.js'); - $f3->set('content', '../ui/views/ticket/view.html'); - echo \Template::instance()->render('../ui/templates/layout.html'); + $this->renderView('../ui/views/ticket/view.html', [ + 'ticket' => $ticket, + 'attachments' => $ticket->attachments(), + 'comments' => $ticket->comments(), + 'parent_tickets' => $ticket->getParentTickets(), + 'child_tickets' => $ticket->getChildTickets(), + 'ticket_meta' => $ticket->getMetaAssoc() + ]); } // show create form // TODO_PROJECTS: dropdown to associate ticket with project public function createForm($f3){ - $this->check_access($f3); - $f3->set('content', '../ui/views/ticket/create.html'); - echo \Template::instance()->render('../ui/templates/layout.html'); + + $this->requireLogin(); + $this->renderView('../ui/views/ticket/create.html'); } // handle POST // including custom forms public function create($f3){ - $this->check_access($f3); + + $this->requireLogin(); - $title = $f3->get('POST.title'); - $description = $f3->get('POST.description'); - $priority = $f3->get('POST.priority'); // eg - low, medium, high - $status = $f3->get('POST.status'); // eg - new, in_progress - $created_by = $f3->get('SESSION.user.id'); // current logged in user + $data = [ + 'title' => $this->f3->get('POST.title'), + 'description' => $this->f3->get('POST.description'), + 'priority' => $this->f3->get('POST.priority'), + 'status' => $this->f3->get('POST.status'), + 'created_by' => $this->f3->get('SESSION.user.id') + ]; - $db = $f3->get('DB'); + $ticket_mapper = new Ticket($this->getDB()); + $new_ticket_id = $ticket_mapper->createTicket($data); - $db->exec( - 'INSERT - INTO tickets (title, description, priority, status, created_by, created_at, updated_at) - VALUES (?,?,?,?,?,NOW(), NOW())', - [$title, $description, $priority, $status, $created_by] - ); + // custom field + $meta_keys = $this->f3->get('POST.meta_key'); + $meta_values = $this->f3->get('POST.meta_value'); + $meta_assoc = $ticket_mapper->assocMetaFromKeyValue($meta_keys, $meta_values); + $ticket_mapper->setCustomFields($meta_assoc); - $ticket_id = $db->lastInsertId(); - - // custom fields - $meta_keys = $f3->get('POST.meta_key'); // eg ['department', 'category'] - $meta_values = $f3->get('POST.meta_value'); - - if(is_array($meta_keys) && is_array($meta_values)){ - foreach($meta_keys as $index => $key){ - $val = $meta_values[$index] ?? ''; - if(!empty($key) && $val !== ''){ - $db->exec( - 'INSERT INTO ticket_meta (ticket_id, meta_key, meta_value) - VALUES (?,?,?)', - [$ticket_id, $key, $val] - ); - } - } - } - - // reroute to ticket - $f3->reroute('/ticket/'.$ticket_id); + $this->f3->reroute('/ticket/' . $new_ticket_id); } // show edit form // including custom forms // TODO_PROJECTS: allow reasssigning or removing a project association public function editForm($f3){ - $this->check_access($f3); + $this->requireLogin(); $ticket_id = $f3->get('PARAMS.id'); - $db = $f3->get('DB'); - + $ticket_mapper = new Ticket($this->getDB()); + $ticket = $ticket_mapper->findById($ticket_id); - $ticket = $this->get_ticket_check_edit_permission($f3); - $f3->set('ticket', $ticket); + if(!$ticket){ + $this->f3->set('SESSION.error', 'Ticket not found.'); + $this->f3->reroute('/tickets'); + } - // fetch custom fields - $meta = $db->exec( - 'SELECT id, meta_key, meta_value - FROM ticket_meta - WHERE ticket_id = ?', - [$ticket_id] + $this->renderView('../ui/views/ticket/edit.html',[ + 'ticket' => $ticket, + 'ticket_meta' => $ticket->getMeta() + ] ); - $f3->set('ticket_meta', $meta); - - $f3->set('ticket', $ticket); - $f3->set('content', '../ui/views/ticket/edit.html'); - echo \Template::instance()->render('../ui/templates/layout.html'); + return; } // process edit POST TODO: if assigned or admin public function update($f3){ - $this->check_access($f3); - $ticket = $this->get_ticket_check_edit_permission($f3); - $ticket_id = $ticket['id']; - $db = $f3->get('DB'); - // get updated fields from post - $title = $f3->get('POST.title'); - $description = $f3->get('POST.description'); - $priority = $f3->get('POST.priority'); // eg - low, medium, high - $status = $f3->get('POST.status'); // eg - new, in_progress - $updated_by = $f3->get('SESSION.user.id'); // current logged in user + $this->requireLogin(); + + $ticket_id = $this->f3->get('PARAMS.id'); + $ticket_mapper = new Ticket($this->getDB()); + $ticket = $ticket_mapper->findById($ticket_id); + + if(!$ticket){ + $this->f3->set('SESSION.error', 'Ticket not found.'); + $this->f3->reroute('/tickets'); + } + + $data = [ + 'title' => $this->f3->get('POST.title'), + 'description' => $this->f3->get('POST.description'), + 'priority' => $this->f3->get('POST.priority'), + 'status' => $this->f3->get('POST.status'), + 'updated_by' => $this->f3->get('SESSION.user.id') + ]; + $ticket->updateTicket($data); - // TODO: if you want to update assignment, should be added here. - - $db->exec( - 'UPDATE tickets - SET title=?, description=?, priority=?, status=?, updated_by=?, updated_at=? - WHERE id=?', - [$title, $description, $priority, $status, $updated_by, 'NOW()', $ticket_id] - ); - - // handle custom fields - // 1. update existing custom fields - $meta_ids = $f3->get('POST.meta_id'); - $meta_keys = $f3->get('POST.meta_key'); - $meta_values = $f3->get('POST.meta_value'); - - if(is_array($meta_ids) && is_array($meta_keys) && is_array($meta_values)){ - foreach($meta_ids as $idx => $m_id){ - $m_key = $meta_keys[$idx] ?? ''; - $m_val = $meta_values[$idx] ?? ''; - - if(!empty($m_key) && $m_val !== ''){ - $db->exec( - 'UPDATE ticket_meta - SET meta_key = ?, meta_value=? - WHERE id = ? AND ticket_id = ?', - [$m_key, $m_val, $m_id, $ticket_id] - ); - } else { - // delete if the user cleared it - $db->exec( - 'DELETE FROM ticket_meta - WHERE id =? AND ticket_id = ?', - [$m_id, $ticket_id] - ); - } - } - } - - // 2. insert new fields - $new_keys = $f3->get('POST.new_meta_key'); - $new_values = $f3->get('POST.new_meta_value'); - if(is_array($new_keys) && is_array($new_values)){ - foreach($new_keys as $idx => $n_key){ - $n_val = $new_values[$idx] ?? ''; - if(!empty($n_key) && $n_val !== ''){ - $db->exec( - 'INSERT INTO ticket_meta (ticket_id, meta_key, meta_value) - VALUES (?,?,?)', - [$ticket_id, $n_key, $n_val] - ); - } - } - } + // deal with meta data / custom fields + $meta_keys = $this->f3->get('POST.meta_key'); + $meta_values = $this->f3->get('POST.meta_value'); + $meta_assoc = $ticket->assocMetaFromKeyValue($meta_keys, $meta_values); + $ticket->setCustomFields($meta_assoc); $f3->reroute('/ticket/' . $ticket_id); } // subtask public function addSubtask($f3){ - $this->check_access($f3); + $this->requireLogin(); $parent_id = (int) $f3->get('PARAMS.id'); $child_id = (int) $f3->get('POST.child_ticket_id'); - $db = $f3->get('DB'); - // TODO check that both parent and child tickets exist - // ensure you don't link a ticket to itself, etc. + $ticket_mapper = new Ticket($this->getDB()); + $ticket = $ticket_mapper->findById($parent_id); - // insert or ignore - $db->exec( - 'INSERT IGNORE INTO ticket_relations (parent_ticket_id, child_ticket_id) - VALUES (?, ?)', - [$parent_id, $child_id] - ); - $f3->reroute('/ticket/' . $parent_id); - } - - - - protected function get_ticket_check_edit_permission($f3){ - - - $db = $f3->get('DB'); - - $ticket_id = $f3->get('PARAMS.id'); - $result = $db->exec('SELECT * FROM tickets WHERE id = ? LIMIT 1', [$ticket_id]); - - if(!$result){ - $f3->set('SESSION.error', 'Ticket not found.'); - $f3->reroute('/tickets'); + if(!$ticket){ + $this->f3->set('SESSION.error', 'Parent Ticket not found'); + $this->f3->reroute('/tickets'); } - - $ticket = $result[0]; - - // TODO: refine - $current_user = $f3->get('SESSION.user'); - $is_admin = (isset($current_user['role_name']) && $current_user['role_name'] == 'admin'); - $is_assigned = ($ticket['assigned_to'] == $current_user['id']); - - if(!$is_admin && !$is_assigned){ // should this be || - // if not assigned and not admin, disallow edit - $f3->set('SESSION.error', 'You do not have permission to edit this ticket.'); - $f3->reroute('/tickets'); - } - - return $ticket; - - } - - protected function get_ticket($f3, $db, $ticket_id){ - - // new - $db = $f3->get('DB'); - $ticket_id = $f3->get('PARAMS.id'); - - $ticketModel = new Ticket($db); - $ticket = $ticketModel->findById($ticket_id); - - if(!$ticket->dry()){ - $f3->set('ticket', $ticket); - $f3->set('attachments', $ticket->attachments()); - $f3->set('comments', $ticket->comments()); - } else { - $f3->error(404, "Ticket not found!"); - } - return; - - // old: - - $result = $db->exec( - 'SELECT t.*, u.username as created_by_name - FROM tickets t - LEFT JOIN users u ON t.created_by = u.id - WHERE t.id =? LIMIT 1', - [$ticket_id] - ); - - if(!$result){ - // no record - $f3->set('SESSION.error', 'Ticket not found.'); - $f3->reroute('/tickets'); - } - - $ticket = $result[0]; - $f3->set('ticket', $ticket); - } - - protected function get_child_tickets($f3, $db, $ticket_id){ - $child_tickets = $db->exec( - 'SELECT c.* - FROM ticket_relations r - INNER JOIN tickets c ON r.child_ticket_id = c.id - WHERE r.parent_ticket_id = ?', - [$ticket_id]); - $f3->set('child_tickets', $child_tickets); - } - - protected function get_parent_tickets($f3, $db, $ticket_id){ - $parent_tickets = $db->exec( - 'SELECT p.* - FROM ticket_relations r - INNER JOIN tickets p ON r.parent_ticket_id = p.id - WHERE r.child_ticket_id = ?', - [$ticket_id] - ); - $f3->set('parent_tickets', $parent_tickets); - } - - protected function get_custom_fields($f3, $db, $ticket_id){ - $meta = $db->exec( - 'SELECT meta_key, meta_value - FROM ticket_meta - WHERE ticket_id = ?', - [$ticket_id] - ); - // convert meta rows into assoc array - $meta_array = []; - foreach($meta as $m){ - $meta_array[$m['meta_key']] = $m['meta_value']; - } - $f3->set('ticket_meta', $meta_array); + $ticket->addChildTicket($child_id); + $this->f3->reroute('/ticket/' . $parent_id); } } \ No newline at end of file diff --git a/app/models/Ticket.php b/app/models/Ticket.php index 40ea567..7e707c7 100644 --- a/app/models/Ticket.php +++ b/app/models/Ticket.php @@ -5,9 +5,47 @@ class Ticket extends \DB\SQL\Mapper { parent::__construct($db, 'tickets'); } - public function findById($id){ + /** + * Return all tickets in descending order of creation + */ + public function findAll(): array + { + return $this->db->exec( + 'SELECT * FROM tickets ORDER BY created_at DESC' + ); + } + + public function findById($id): ?Ticket + { $this->load(['id = ?', $id]); - return $this; + return $this->dry() ? null : $this; + } + + public function createTicket(array $data): int + { + $this->reset(); + + $this->title = $data['title'] ?? ''; + $this->description = $data['description'] ?? ''; + $this->priority = $data['priority'] ?? 'Low'; + $this->status = $data['status'] ?? 'New'; + $this->created_by = $data['created_by'] ?? null; + $this->created_at = date('Y-m-d H:i:s'); + $this->updated_at = date('Y-m-d H:i:s'); + + $this->save(); + return (int)$this->id; + } + + public function updateTicket(array $data): void + { + if(isset($data['title'])){ $this->title = $data['title']; } + if(isset($data['description'])) { $this->descrtipion = $data['description']; } + if(isset($data['priority'])) { $this->priority = $data['priority']; } + if(isset($data['status'])) { $this->status = $data['status']; } + if(isset($data['updated_by'])) { $this->status = $data['updated_by']; } + $this->updated_at = date('Y-m-d H:i:s'); + $this->save(); } public function attachments(){ @@ -19,4 +57,102 @@ class Ticket extends \DB\SQL\Mapper { $comment = new Comment($this->db); return $comment->findWithUserByTicketId($this->id); } + + public function getParentTickets() + { + return $this->db->exec( + 'SELECT p.* + FROM ticket_relations r + INNER JOIN tickets p ON r.parent_ticket_id = p.id + WHERE r.child_ticket_id = ?', + [$this->id] + ); + } + + public function getChildTickets() + { + return $this->db->exec( + 'SELECT c.* + FROM ticket_relations r + INNER JOIN tickets c ON r.child_ticket_id = c.id + WHERE r.parent_ticket_id = ?', + [$this->id] + ); + } + + public function addChildTicket(int $childId) + { + $this->db->exec( + 'INSERT IGNORE INTO ticket_relations (parent_ticket_id, child_ticket_id) + VALUES (?, ?)', + [$this->id, $childId] + ); + } + + // meta data + public function getMeta() + { + return $this->db->exec( + 'SELECT id, meta_key, meta_value + FROM ticket_meta + WHERE ticket_id = ?', + [$this->id] + ); + } + + public function getMetaAssoc() + { + $rows = $this->getMeta(); + $assoc = []; + foreach($rows as $row){ + $assoc[$row['meta_key']] = $row['meta_value']; + } + return $assoc; + } + + public function assocExistingMeta($meta_ids, $meta_keys, $meta_values){ + if(is_array($meta_ids) && is_array($meta_keys) && is_array($meta_values)){ + $field_assoc = []; + foreach($meta_ids as $i => $m_id){ + $key = $meta_keys[$i] ?? ''; + $value = $meta_values[$i] ?? ''; + if(!empty($key) && $value !== ''){ + $field_assoc[$key] = $value; + } + } + return $field_assoc; + } + return []; + } + + public function assocMetaFromKeyValue($meta_keys, $meta_values) + { + if(is_array($meta_keys) && is_array($meta_values)){ + $field_assoc = []; + foreach($meta_keys as $i => $key){ + $val = $meta_values[$i] ?? ''; + if(!empty($key) && $val != ''){ + $field_assoc[$key] = $val; + } + } + return $field_assoc; + } + return []; + } + + public function setCustomFields(array $fields) + { + $this->db->exec( + 'DELETE FROM ticket_meta WHERE ticket_id = ?', [$this->id] + ); + + foreach($fields as $key => $value){ + $this->db->exec( + 'INSERT INTO ticket_meta (ticket_id, meta_key, meta_value) + VALUES (?, ?, ?)', + [$this->id, $key, $value] + ); + } + } + } \ No newline at end of file