diff --git a/src/packages/com_mokowaas/admin/sql/install.mysql.sql b/src/packages/com_mokowaas/admin/sql/install.mysql.sql
index 12dbca53..208a25bd 100644
--- a/src/packages/com_mokowaas/admin/sql/install.mysql.sql
+++ b/src/packages/com_mokowaas/admin/sql/install.mysql.sql
@@ -61,6 +61,23 @@ CREATE TABLE IF NOT EXISTS `#__mokowaas_ticket_canned` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+CREATE TABLE IF NOT EXISTS `#__mokowaas_ticket_automation` (
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `title` VARCHAR(255) NOT NULL,
+ `trigger_event` VARCHAR(50) NOT NULL DEFAULT 'ticket_created',
+ `conditions` TEXT NOT NULL DEFAULT '[]',
+ `actions` TEXT NOT NULL DEFAULT '[]',
+ `enabled` TINYINT NOT NULL DEFAULT 1,
+ `ordering` INT NOT NULL DEFAULT 0,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- Default automation rules
+INSERT IGNORE INTO `#__mokowaas_ticket_automation` (`id`, `title`, `trigger_event`, `conditions`, `actions`, `enabled`, `ordering`) VALUES
+(1, 'Auto-close resolved tickets after 7 days', 'scheduled', '[{"field":"status","op":"eq","value":"resolved"},{"field":"age_hours","op":"gt","value":"168"}]', '[{"type":"set_status","value":"closed"},{"type":"add_note","value":"Auto-closed after 7 days with no response."}]', 1, 1),
+(2, 'Escalate urgent tickets with no response in 1 hour', 'scheduled', '[{"field":"priority","op":"eq","value":"urgent"},{"field":"sla_responded","op":"eq","value":"0"},{"field":"age_hours","op":"gt","value":"1"}]', '[{"type":"add_note","value":"SLA BREACH: Urgent ticket has no staff response after 1 hour."}]', 1, 2),
+(3, 'Notify on high priority ticket creation', 'ticket_created', '[{"field":"priority","op":"in","value":"high,urgent"}]', '[{"type":"add_note","value":"High/urgent ticket created — requires immediate attention."}]', 1, 3);
+
-- Default categories
INSERT IGNORE INTO `#__mokowaas_ticket_categories` (`id`, `title`, `alias`, `description`, `sla_response_minutes`, `sla_resolution_minutes`, `ordering`) VALUES
(1, 'General Support', 'general-support', 'General questions and assistance', 480, 2880, 1),
diff --git a/src/packages/com_mokowaas/admin/src/Model/TicketsModel.php b/src/packages/com_mokowaas/admin/src/Model/TicketsModel.php
index 8bad035d..657b7d71 100644
--- a/src/packages/com_mokowaas/admin/src/Model/TicketsModel.php
+++ b/src/packages/com_mokowaas/admin/src/Model/TicketsModel.php
@@ -173,6 +173,9 @@ class TicketsModel extends BaseDatabaseModel
$db->insertObject('#__mokowaas_tickets', $ticket, 'id');
+ // Run automation
+ $this->runAutomation('ticket_created', (int) $ticket->id);
+
return ['success' => true, 'message' => 'Ticket #' . $ticket->id . ' created.', 'id' => (int) $ticket->id];
}
catch (\Throwable $e)
@@ -212,6 +215,9 @@ class TicketsModel extends BaseDatabaseModel
->where($db->quoteName('sla_responded') . ' = 0')
)->execute();
+ // Run automation
+ $this->runAutomation('ticket_replied', $ticketId);
+
return ['success' => true, 'message' => 'Reply added.'];
}
catch (\Throwable $e)
@@ -259,6 +265,9 @@ class TicketsModel extends BaseDatabaseModel
->where($db->quoteName('id') . ' = ' . $ticketId)
)->execute();
+ // Run automation
+ $this->runAutomation('status_changed', $ticketId);
+
return ['success' => true, 'message' => 'Status updated to ' . $status . '.'];
}
catch (\Throwable $e)
@@ -351,6 +360,235 @@ class TicketsModel extends BaseDatabaseModel
return $db->loadObjectList() ?: [];
}
+ // ==================================================================
+ // Automation Engine
+ // ==================================================================
+
+ /**
+ * Run automation rules for a specific trigger event against a ticket.
+ *
+ * @param string $event trigger_event: ticket_created, ticket_replied, status_changed, scheduled
+ * @param int $ticketId The ticket to evaluate
+ */
+ public function runAutomation(string $event, int $ticketId): void
+ {
+ try
+ {
+ $db = $this->getDatabase();
+
+ // Load enabled rules for this event
+ $query = $db->getQuery(true)
+ ->select('*')
+ ->from($db->quoteName('#__mokowaas_ticket_automation'))
+ ->where($db->quoteName('trigger_event') . ' = ' . $db->quote($event))
+ ->where($db->quoteName('enabled') . ' = 1')
+ ->order($db->quoteName('ordering') . ' ASC');
+ $db->setQuery($query);
+ $rules = $db->loadObjectList() ?: [];
+
+ if (empty($rules))
+ {
+ return;
+ }
+
+ // Load the ticket
+ $ticket = $this->getTicket($ticketId);
+
+ if (!$ticket)
+ {
+ return;
+ }
+
+ // Calculate age in hours
+ $ticket->age_hours = (time() - strtotime($ticket->created)) / 3600;
+
+ foreach ($rules as $rule)
+ {
+ $conditions = json_decode($rule->conditions, true) ?: [];
+ $actions = json_decode($rule->actions, true) ?: [];
+
+ if ($this->evaluateConditions($conditions, $ticket))
+ {
+ $this->executeActions($actions, $ticketId, $ticket);
+ }
+ }
+ }
+ catch (\Throwable $e)
+ {
+ \Joomla\CMS\Log\Log::add('Automation error: ' . $e->getMessage(), \Joomla\CMS\Log\Log::WARNING, 'mokowaas');
+ }
+ }
+
+ /**
+ * Run all scheduled automation rules against all open tickets.
+ */
+ public function runScheduledAutomation(): array
+ {
+ $db = $this->getDatabase();
+ $results = ['evaluated' => 0, 'acted' => 0];
+
+ // Load scheduled rules
+ $query = $db->getQuery(true)
+ ->select('*')
+ ->from($db->quoteName('#__mokowaas_ticket_automation'))
+ ->where($db->quoteName('trigger_event') . ' = ' . $db->quote('scheduled'))
+ ->where($db->quoteName('enabled') . ' = 1')
+ ->order($db->quoteName('ordering') . ' ASC');
+ $db->setQuery($query);
+ $rules = $db->loadObjectList() ?: [];
+
+ if (empty($rules))
+ {
+ return $results;
+ }
+
+ // Load all non-closed tickets
+ $query = $db->getQuery(true)
+ ->select('*')
+ ->from($db->quoteName('#__mokowaas_tickets'))
+ ->where($db->quoteName('status') . ' != ' . $db->quote('closed'));
+ $db->setQuery($query);
+ $tickets = $db->loadObjectList() ?: [];
+
+ foreach ($tickets as $ticket)
+ {
+ $ticket->age_hours = (time() - strtotime($ticket->created)) / 3600;
+ $ticket->replies = [];
+ $results['evaluated']++;
+
+ foreach ($rules as $rule)
+ {
+ $conditions = json_decode($rule->conditions, true) ?: [];
+ $actions = json_decode($rule->actions, true) ?: [];
+
+ if ($this->evaluateConditions($conditions, $ticket))
+ {
+ $this->executeActions($actions, (int) $ticket->id, $ticket);
+ $results['acted']++;
+ }
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Evaluate a set of conditions against a ticket (all must match).
+ */
+ private function evaluateConditions(array $conditions, object $ticket): bool
+ {
+ foreach ($conditions as $cond)
+ {
+ $field = $cond['field'] ?? '';
+ $op = $cond['op'] ?? 'eq';
+ $value = $cond['value'] ?? '';
+
+ $ticketValue = $ticket->{$field} ?? null;
+
+ if ($ticketValue === null)
+ {
+ return false;
+ }
+
+ switch ($op)
+ {
+ case 'eq':
+ if ((string) $ticketValue !== (string) $value) return false;
+ break;
+ case 'neq':
+ if ((string) $ticketValue === (string) $value) return false;
+ break;
+ case 'gt':
+ if ((float) $ticketValue <= (float) $value) return false;
+ break;
+ case 'lt':
+ if ((float) $ticketValue >= (float) $value) return false;
+ break;
+ case 'in':
+ $list = array_map('trim', explode(',', $value));
+ if (!\in_array((string) $ticketValue, $list, true)) return false;
+ break;
+ case 'not_in':
+ $list = array_map('trim', explode(',', $value));
+ if (\in_array((string) $ticketValue, $list, true)) return false;
+ break;
+ default:
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Execute a set of actions on a ticket.
+ */
+ private function executeActions(array $actions, int $ticketId, object $ticket): void
+ {
+ $db = $this->getDatabase();
+ $now = Factory::getDate()->toSql();
+
+ foreach ($actions as $action)
+ {
+ $type = $action['type'] ?? '';
+ $value = $action['value'] ?? '';
+
+ switch ($type)
+ {
+ case 'set_status':
+ $this->updateStatus($ticketId, $value);
+ break;
+
+ case 'set_priority':
+ $db->setQuery(
+ $db->getQuery(true)
+ ->update($db->quoteName('#__mokowaas_tickets'))
+ ->set($db->quoteName('priority') . ' = ' . $db->quote($value))
+ ->set($db->quoteName('modified') . ' = ' . $db->quote($now))
+ ->where($db->quoteName('id') . ' = ' . $ticketId)
+ )->execute();
+ break;
+
+ case 'assign':
+ $db->setQuery(
+ $db->getQuery(true)
+ ->update($db->quoteName('#__mokowaas_tickets'))
+ ->set($db->quoteName('assigned_to') . ' = ' . (int) $value)
+ ->set($db->quoteName('modified') . ' = ' . $db->quote($now))
+ ->where($db->quoteName('id') . ' = ' . $ticketId)
+ )->execute();
+ break;
+
+ case 'add_note':
+ $reply = (object) [
+ 'ticket_id' => $ticketId,
+ 'user_id' => 0,
+ 'body' => $value,
+ 'is_internal' => 1,
+ 'created' => $now,
+ ];
+ $db->insertObject('#__mokowaas_ticket_replies', $reply, 'id');
+ break;
+ }
+ }
+ }
+
+ /**
+ * Get all automation rules.
+ */
+ public function getAutomationRules(): array
+ {
+ $db = $this->getDatabase();
+ $db->setQuery(
+ $db->getQuery(true)
+ ->select('*')
+ ->from($db->quoteName('#__mokowaas_ticket_automation'))
+ ->order($db->quoteName('ordering') . ' ASC')
+ );
+
+ return $db->loadObjectList() ?: [];
+ }
+
// ==================================================================
// Akeeba Ticket System Importer
// ==================================================================
diff --git a/src/packages/com_mokowaas/admin/src/View/Dashboard/HtmlView.php b/src/packages/com_mokowaas/admin/src/View/Dashboard/HtmlView.php
index 8638da95..b8b2f0db 100644
--- a/src/packages/com_mokowaas/admin/src/View/Dashboard/HtmlView.php
+++ b/src/packages/com_mokowaas/admin/src/View/Dashboard/HtmlView.php
@@ -36,9 +36,17 @@ class HtmlView extends BaseHtmlView
$this->wafBlocks = $model->getRecentWafBlocks(5);
// Check for importable Akeeba data
- $importModel = $this->getModel('Import');
- $this->adminToolsAvailable = $importModel->checkAdminToolsAvailable();
- $this->atsAvailable = $importModel->checkAtsAvailable();
+ try
+ {
+ $importModel = new \Moko\Component\MokoWaaS\Administrator\Model\ImportModel();
+ $this->adminToolsAvailable = $importModel->checkAdminToolsAvailable();
+ $this->atsAvailable = $importModel->checkAtsAvailable();
+ }
+ catch (\Throwable $e)
+ {
+ $this->adminToolsAvailable = null;
+ $this->atsAvailable = null;
+ }
$this->addToolbar();
diff --git a/src/packages/plg_task_mokowaas_tickets/language/en-GB/plg_task_mokowaas_tickets.ini b/src/packages/plg_task_mokowaas_tickets/language/en-GB/plg_task_mokowaas_tickets.ini
new file mode 100644
index 00000000..5b695de4
--- /dev/null
+++ b/src/packages/plg_task_mokowaas_tickets/language/en-GB/plg_task_mokowaas_tickets.ini
@@ -0,0 +1,4 @@
+PLG_TASK_MOKOWAAS_TICKETS="Task - MokoWaaS Ticket Automation"
+PLG_TASK_MOKOWAAS_TICKETS_DESC="Runs scheduled helpdesk automation rules."
+PLG_TASK_MOKOWAAS_TICKETS_AUTOMATION_TITLE="MokoWaaS: Ticket Automation"
+PLG_TASK_MOKOWAAS_TICKETS_AUTOMATION_DESC="Runs time-based automation rules against open tickets (auto-close, SLA escalation, etc.)."
diff --git a/src/packages/plg_task_mokowaas_tickets/language/en-GB/plg_task_mokowaas_tickets.sys.ini b/src/packages/plg_task_mokowaas_tickets/language/en-GB/plg_task_mokowaas_tickets.sys.ini
new file mode 100644
index 00000000..c0dc6562
--- /dev/null
+++ b/src/packages/plg_task_mokowaas_tickets/language/en-GB/plg_task_mokowaas_tickets.sys.ini
@@ -0,0 +1,2 @@
+PLG_TASK_MOKOWAAS_TICKETS="Task - MokoWaaS Ticket Automation"
+PLG_TASK_MOKOWAAS_TICKETS_DESC="Runs scheduled helpdesk automation rules — auto-close, SLA escalation, and time-based actions."
diff --git a/src/packages/plg_task_mokowaas_tickets/mokowaas_tickets.xml b/src/packages/plg_task_mokowaas_tickets/mokowaas_tickets.xml
new file mode 100644
index 00000000..c8e4801f
--- /dev/null
+++ b/src/packages/plg_task_mokowaas_tickets/mokowaas_tickets.xml
@@ -0,0 +1,25 @@
+
+
+ Task - MokoWaaS Ticket Automation
+ mokowaas_tickets
+ Moko Consulting
+ 2026-06-02
+ Copyright (C) 2026 Moko Consulting. All rights reserved.
+ GPL-3.0-or-later
+ hello@mokoconsulting.tech
+ https://mokoconsulting.tech
+ 02.32.00
+ Runs scheduled helpdesk automation rules — auto-close resolved tickets, SLA breach escalation, and time-based actions.
+ Moko\Plugin\Task\MokoWaaSTickets
+
+
+ src
+ services
+ language
+
+
+
+ en-GB/plg_task_mokowaas_tickets.ini
+ en-GB/plg_task_mokowaas_tickets.sys.ini
+
+
diff --git a/src/packages/plg_task_mokowaas_tickets/services/provider.php b/src/packages/plg_task_mokowaas_tickets/services/provider.php
new file mode 100644
index 00000000..e97c8c8e
--- /dev/null
+++ b/src/packages/plg_task_mokowaas_tickets/services/provider.php
@@ -0,0 +1,27 @@
+set(
+ PluginInterface::class,
+ function (Container $container) {
+ $dispatcher = $container->get(DispatcherInterface::class);
+ $plugin = new TicketAutomation($dispatcher, (array) PluginHelper::getPlugin('task', 'mokowaas_tickets'));
+ $plugin->setApplication(Factory::getApplication());
+
+ return $plugin;
+ }
+ );
+ }
+};
diff --git a/src/packages/plg_task_mokowaas_tickets/src/Extension/TicketAutomation.php b/src/packages/plg_task_mokowaas_tickets/src/Extension/TicketAutomation.php
new file mode 100644
index 00000000..3daa7aec
--- /dev/null
+++ b/src/packages/plg_task_mokowaas_tickets/src/Extension/TicketAutomation.php
@@ -0,0 +1,65 @@
+ [
+ 'langConstPrefix' => 'PLG_TASK_MOKOWAAS_TICKETS_AUTOMATION',
+ 'method' => 'runAutomation',
+ ],
+ ];
+
+ protected $autoloadLanguage = true;
+
+ public static function getSubscribedEvents(): array
+ {
+ return [
+ 'onTaskOptionsList' => 'advertiseRoutines',
+ 'onExecuteTask' => 'standardRoutineHandler',
+ 'onContentPrepareForm' => 'enhanceTaskItemForm',
+ ];
+ }
+
+ /**
+ * Run all scheduled automation rules against open tickets.
+ */
+ private function runAutomation(ExecuteTaskEvent $event): int
+ {
+ try
+ {
+ $model = new TicketsModel();
+ $results = $model->runScheduledAutomation();
+
+ $this->logTask(
+ \sprintf('Ticket automation: evaluated %d tickets, acted on %d', $results['evaluated'], $results['acted'])
+ );
+
+ return Status::OK;
+ }
+ catch (\Throwable $e)
+ {
+ $this->logTask('Ticket automation failed: ' . $e->getMessage(), 'error');
+
+ return Status::KNOCKOUT;
+ }
+ }
+}
diff --git a/src/pkg_mokowaas.xml b/src/pkg_mokowaas.xml
index a5eb97a8..67ea2c6f 100644
--- a/src/pkg_mokowaas.xml
+++ b/src/pkg_mokowaas.xml
@@ -24,6 +24,7 @@
plg_webservices_perfectpublisher.zip
plg_task_mokowaasdemo.zip
plg_task_mokowaassync.zip
+ plg_task_mokowaas_tickets.zip
tpl_mokoonyx.zip
diff --git a/src/script.php b/src/script.php
index 7afd3b8c..882bcba5 100644
--- a/src/script.php
+++ b/src/script.php
@@ -47,6 +47,7 @@ class Pkg_MokowaasInstallerScript
$this->enablePlugin('webservices', 'mokowaas');
$this->enablePlugin('task', 'mokowaasdemo');
$this->enablePlugin('task', 'mokowaassync');
+ $this->enablePlugin('task', 'mokowaas_tickets');
// Migrate params from core plugin to feature plugins (one-time)
$this->migrateFeatureParams();
@@ -411,6 +412,7 @@ class Pkg_MokowaasInstallerScript
$db->quote('mod_mokowaas_cpanel'),
$db->quote('mokowaasdemo'),
$db->quote('mokowaassync'),
+ $db->quote('mokowaas_tickets'),
$db->quote('perfectpublisher'),
$db->quote('mokoonyx'),
];