conditions, true) ?: []; $actions = json_decode($rule->actions, true) ?: []; if (self::evaluateConditions($conditions, $context)) { self::executeActions($actions, $rule, $context); } } } catch (\Throwable $e) { Log::add('Automation engine error: ' . $e->getMessage(), Log::ERROR, 'mokosuiteclient'); } } /** * Get active automation rules for a trigger event. */ private static function getActiveRules(string $event): array { $db = Factory::getDbo(); $db->setQuery( $db->getQuery(true) ->select('*') ->from('#__mokosuiteclient_ticket_automation') ->where($db->quoteName('trigger_event') . ' = ' . $db->quote($event)) ->where($db->quoteName('enabled') . ' = 1') ->order('ordering ASC') ); return $db->loadObjectList() ?: []; } /** * Evaluate all conditions (AND logic). */ private static function evaluateConditions(array $conditions, array $context): bool { foreach ($conditions as $c) { $field = $c['field'] ?? ''; $op = $c['op'] ?? 'eq'; $expected = $c['value'] ?? ''; $actual = $context[$field] ?? ''; switch ($op) { case 'eq': if ((string) $actual !== (string) $expected) return false; break; case 'neq': if ((string) $actual === (string) $expected) return false; break; case 'gt': if ((float) $actual <= (float) $expected) return false; break; case 'lt': if ((float) $actual >= (float) $expected) return false; break; case 'in': $values = array_map('trim', explode(',', $expected)); if (!in_array((string) $actual, $values, true)) return false; break; case 'not_in': $values = array_map('trim', explode(',', $expected)); if (in_array((string) $actual, $values, true)) return false; break; } } return true; } /** * Execute actions for a matched rule. */ private static function executeActions(array $actions, object $rule, array $context): void { $db = Factory::getDbo(); $ticketId = (int) ($context['ticket_id'] ?? $context['id'] ?? 0); foreach ($actions as $action) { $type = $action['type'] ?? ''; $value = $action['value'] ?? ''; try { switch ($type) { case 'set_status': if ($ticketId) { $db->setQuery("UPDATE {$db->quoteName('#__mokosuiteclient_tickets')} SET status = {$db->quote($value)}, modified = {$db->quote(Factory::getDate()->toSql())} WHERE id = {$ticketId}")->execute(); } break; case 'set_priority': if ($ticketId) { $db->setQuery("UPDATE {$db->quoteName('#__mokosuiteclient_tickets')} SET priority = {$db->quote($value)}, modified = {$db->quote(Factory::getDate()->toSql())} WHERE id = {$ticketId}")->execute(); } break; case 'assign': if ($ticketId) { $db->setQuery("UPDATE {$db->quoteName('#__mokosuiteclient_tickets')} SET assigned_to = {$db->quote($value)}, modified = {$db->quote(Factory::getDate()->toSql())} WHERE id = {$ticketId}")->execute(); } break; case 'add_note': if ($ticketId) { $note = (object) [ 'ticket_id' => $ticketId, 'user_id' => 0, 'body' => $value ?: '[Automation: ' . ($rule->title ?? '') . ']', 'is_internal' => 1, 'created' => Factory::getDate()->toSql(), ]; $db->insertObject('#__mokosuiteclient_ticket_replies', $note); } break; case 'send_email': NotificationService::securityAlert( 'automation', 'Automation: ' . ($rule->title ?? ''), $value ?: 'Rule triggered for ticket #' . $ticketId ); break; case 'send_ntfy': NotificationService::pushNtfySecurity( 'automation', 'Automation: ' . ($rule->title ?? ''), $value ?: 'Rule triggered for ticket #' . $ticketId ); break; case 'close': if ($ticketId) { $db->setQuery("UPDATE {$db->quoteName('#__mokosuiteclient_tickets')} SET status = 'closed', closed = {$db->quote(Factory::getDate()->toSql())}, modified = {$db->quote(Factory::getDate()->toSql())} WHERE id = {$ticketId}")->execute(); } break; case 'create_ticket': self::createTicketFromAutomation($rule, $context, $value); break; } } catch (\Throwable $e) { Log::add("Automation action {$type} failed: " . $e->getMessage(), Log::WARNING, 'mokosuiteclient'); } } } /** * Create a ticket from automation (with behavior: append/always_new/skip_if_open). */ private static function createTicketFromAutomation(object $rule, array $context, string $subject): void { $db = Factory::getDbo(); $behavior = $rule->behavior ?? 'append'; $userId = (int) ($context['user_id'] ?? 0); $catId = (int) ($context['category_id'] ?? 0); if ($behavior !== 'always_new' && $userId > 0) { // Check for existing open ticket $query = $db->getQuery(true) ->select('id') ->from('#__mokosuiteclient_tickets') ->where('created_by = ' . $userId) ->where("status NOT IN ('closed', 'resolved')"); if ($catId > 0) { $query->where('category_id = ' . $catId); } $db->setQuery($query, 0, 1); $existingId = (int) $db->loadResult(); if ($existingId > 0) { if ($behavior === 'skip_if_open') return; // append — add reply to existing ticket $reply = (object) [ 'ticket_id' => $existingId, 'user_id' => 0, 'body' => $subject ?: '[Automation: ' . ($rule->title ?? '') . ']', 'is_internal' => 1, 'created' => Factory::getDate()->toSql(), ]; $db->insertObject('#__mokosuiteclient_ticket_replies', $reply); return; } } // Create new ticket $ticket = (object) [ 'subject' => $subject ?: 'Automation: ' . ($rule->title ?? ''), 'body' => $context['body'] ?? '', 'status' => 'open', 'priority' => $context['priority'] ?? 'normal', 'category_id' => $catId ?: null, 'created_by' => $userId, 'created' => Factory::getDate()->toSql(), ]; $db->insertObject('#__mokosuiteclient_tickets', $ticket, 'id'); } }