db->exec( 'SELECT t.id, t.title, t.created_at, tp.name AS priority_name, ts.name AS status_name, u.display_name FROM tickets t LEFT JOIN ticket_priorities tp ON t.priority_id = tp.id LEFT JOIN ticket_statuses ts ON t.status_id = ts.id LEFT JOIN users u ON t.created_by = u.id WHERE t.recycled = 0 ORDER BY t.created_at DESC' ); $result = $this->getTagsForTickets($tickets); return $result; } public function findFiltered(string $filter): array { $sql = ' SELECT t.*, tp.name AS priority_name, ts.name AS status_name, u.display_name FROM tickets t LEFT JOIN ticket_priorities tp ON t.priority_id = tp.id LEFT JOIN ticket_statuses ts ON t.status_id = ts.id LEFT JOIN users u ON t.created_by = u.id WHERE t.recycled = 0 '; $params = []; switch($filter){ case 'open': $sql .= ' AND status_id = ?'; $params[] = 1; break; case 'in_progress': $sql .= ' AND status_id = ?'; $params[] = 2; break; case 'on_hold': $sql .= ' AND status_id = ?'; $params[] = 3; break; case 'completed': $sql .= ' AND status_id = ?'; $params[] = 4; break; } $sql .= ' ORDER BY t.created_at DESC'; $tickets = $this->db->exec($sql, $params); $result = $this->getTagsForTickets($tickets); return $result; } public function getTagsForTickets(array $tickets) { if(empty($tickets)) return []; $tag_mapper = new Tag($this->db, 'ticket'); $tickets = $tag_mapper->getTagsFor($tickets); return $tickets; } public function findById($id): ?Ticket { $this->status_name = 'SELECT name FROM ticket_statuses WHERE tickets.status_id = ticket_statuses.id'; $this->priority_name = 'SELECT name FROM ticket_priorities WHERE tickets.priority_id = ticket_priorities.id'; $this->load(['id = ?', $id]); if($this->dry()){ return null; } $tag_model = new Tag($this->db, 'ticket'); $this->tags = $tag_model->getTagsForID($this->id, 'ticket_id'); return $this; } public function setTags(array $tags_ids):void { if($this->dry() || !$this->id){ // can't set tags for a ticket that hasn't been saved or loaded return; } // remove existing tags - TODO: shouldn't this be in the tag model? $this->db->exec('DELETE FROM ticket_tags WHERE ticket_id = ?', [$this->id]); if(!empty($tags_ids)){ $sql_insert_tag = 'INSERT INTO ticket_tags (ticket_id, tag_id) VALUES (?,?)'; foreach($tags_ids as $tag_id){ if(filter_var($tag_id, FILTER_VALIDATE_INT)){ $this->db->exec($sql_insert_tag, [$this->id, (int)$tag_id]); } } } // refresh tags $tag_model = new Tag($this->db, 'ticket'); $this->tags = $tag_model->getTagsForID($this->id, 'ticket_id'); } public function createTicket(array $data): int { $this->reset(); $this->title = $data['title'] ?? ''; $this->description = $data['description'] ?? ''; // $this->priority_id = $data['priority_id'] ?? null; $this->status_id = $data['status_id'] ?? null; // $this->created_by = $data['created_by'] ?? null; $this->created_at = ($data['created_at'] == '' ? date('Y-m-d H:i:s') : $data['created_at']) ?? date('Y-m-d H:i:s'); $this->updated_at = date('Y-m-d H:i:s'); $this->save(); $this->logCreate(); return (int)$this->id; } public function updateTicket(array $data): void { $this->logDiff($data); if(isset($data['title'])){ $this->title = $data['title']; } if(isset($data['description'])) { $this->description = $data['description']; } if(isset($data['priority_id'])) { $this->priority_id = $data['priority_id']; } if(isset($data['status_id'])) { $this->status_id = $data['status_id']; } if(isset($data['updated_by'])) { $this->updated_by = $data['updated_by']; } if(isset($data['assigned_to'])) { if($data['assigned_to'] == '-1'){ $this->assigned_to = null; } else { $this->assigned_to = $data['assigned_to']; } } $this->created_at = ($data['created_at'] == '' ? date('Y-m-d H:i:s') : $data['created_at']) ?? date('Y-m-d H:i:s'); $this->updated_at = date('Y-m-d H:i:s'); $this->save(); } public function softDelete():void { $this->recycled = 1; $this->save(); } public function attachments(){ $attachment = new Attachment($this->db); return $attachment->findWithUserByTicketId($this->id); } public function comments(){ $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] ); } } public function getAssignedUser() { if(!$this->assigned_to){ return null; } $sql = ' SELECT id, username, display_name FROM users WHERE id =? '; $user = $this->db->exec($sql, [$this->assigned_to]); return $user[0]; } /** * Logs a change to the ticket history. * @param int $user_id - the ID of the user making the change. * @param string $field_changed - the name of the field that was changed. * @param string|null $old_value - the old value * @param string|null $new_value - the new value */ public function logHistory(int $user_id, string $field_changed, $old_value = null, $new_value = null, $changed_at = null): void { if($this->dry() || !$this->id) return; $history = new \DB\SQL\Mapper($this->db, 'ticket_history'); $history->ticket_id = $this->id; $history->user_id = $user_id; $history->field_changed = $field_changed; $history->old_value = $old_value === null ? null : (string)$old_value; $history->new_value = $new_value === null ? null : (string)$new_value; if($changed_at == null){ $history->changed_at = date('Y-m-d H:i:s'); } else { $history->changed_at = $changed_at; } $history->save(); } public function getHistory(): array { if($this->dry() || !$this->id) return[]; $sql = 'SELECT th.*, u.display_name as user_display_name FROM ticket_history th JOIN users u ON th.user_id = u.id WHERE th.ticket_id = ? ORDER BY th.changed_at DESC'; return $this->db->exec($sql, [$this->id]); } /** * called from create */ public function logCreate(){ $changed_at = date('Y-m-d H:i:s'); $user_making_change = \Base::instance()->get('SESSION.user.id'); $this->logHistory($user_making_change, 'ticket_created', null, $this->title, $changed_at); if($this->status_id) $this->logHistory($user_making_change, 'status_id', null, $this->status_id, $changed_at); if($this->priority_id) $this->logHistory($user_making_change, 'priority_id', null, $this->priority_id, $changed_at); if($this->assigned_to) $this->logHistory($user_making_change, 'assigned_to', null, $this->assigned_to, $changed_at); } /** * called from update */ public function logDiff($data){ $user_making_change = \Base::instance()->get('SESSION.user.id'); $changed_at = date('Y-m-d H:i:s'); $checks = ['title', 'description', 'priority_id', 'status_id', 'assigned_to', 'updated_by']; // loop instead foreach($checks as $check){ if(isset($data[$check]) && $this->$check != $data[$check]){ if($check == 'description'){ $old_hash = hash('sha256', $this->$check); $new_hash = hash('sha256', $data[$check]); $this->logHistory($user_making_change, $check, $old_hash, $new_hash, $changed_at); } else { $this->logHistory($user_making_change, $check, $this->$check, $data[$check], $changed_at); } } } } }