2025-05-10 15:26:21 +01:00

351 lines
11 KiB
PHP

<?php
class Ticket extends \DB\SQL\Mapper {
function __construct($db){
parent::__construct($db, 'tickets');
}
/**
* Return all tickets in descending order of creation
*/
public function findAll(): array
{
$tickets = $this->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);
}
}
}
}
}