feat: expanded automation — Joomla event triggers, create_ticket action, behavior options (#151)
New trigger events hooked into core plugin: - user_login — fires on successful Joomla login - user_register — fires on new user creation - user_login_failed — fires on failed login attempt New action type: create_ticket with behavior options: - append: add reply to existing open ticket (same user+category) - always_new: always create a new ticket - skip_if_open: do nothing if open ticket exists New method: runSystemEventAutomation() for non-ticket events that builds a virtual context object from event data. Automation rules can now create tickets from any system event, with intelligent deduplication to avoid ticket spam. Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -608,10 +608,116 @@ class TicketsModel extends BaseDatabaseModel
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'create_ticket':
|
||||
// value = JSON: {"subject":"...","body":"...","category_id":1,"priority":"normal","behavior":"append"}
|
||||
$ticketData = json_decode($value, true) ?: [];
|
||||
$behavior = $ticketData['behavior'] ?? 'append';
|
||||
$userId = (int) ($ticket->created_by ?? 0);
|
||||
$catId = (int) ($ticketData['category_id'] ?? 0);
|
||||
|
||||
if ($behavior === 'append' && $userId > 0)
|
||||
{
|
||||
// Check for existing open ticket from this user in this category
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select($db->quoteName('id'))
|
||||
->from($db->quoteName('#__mokowaas_tickets'))
|
||||
->where($db->quoteName('created_by') . ' = ' . $userId)
|
||||
->where($db->quoteName('status') . ' NOT IN (' . $db->quote('resolved') . ',' . $db->quote('closed') . ')')
|
||||
->where($catId ? $db->quoteName('category_id') . ' = ' . $catId : '1=1')
|
||||
->order($db->quoteName('created') . ' DESC')
|
||||
->setLimit(1)
|
||||
);
|
||||
$existingId = (int) $db->loadResult();
|
||||
|
||||
if ($existingId)
|
||||
{
|
||||
$this->addReply($existingId, $ticketData['body'] ?? 'Automation event', true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
elseif ($behavior === 'skip_if_open' && $userId > 0)
|
||||
{
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__mokowaas_tickets'))
|
||||
->where($db->quoteName('created_by') . ' = ' . $userId)
|
||||
->where($db->quoteName('status') . ' NOT IN (' . $db->quote('resolved') . ',' . $db->quote('closed') . ')')
|
||||
);
|
||||
|
||||
if ((int) $db->loadResult() > 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create new ticket
|
||||
$this->createTicket([
|
||||
'subject' => $ticketData['subject'] ?? 'Automation: ' . ($ticket->subject ?? 'System event'),
|
||||
'body' => $ticketData['body'] ?? '',
|
||||
'priority' => $ticketData['priority'] ?? 'normal',
|
||||
'category_id' => $catId,
|
||||
]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run automation for a system event (not tied to a specific ticket).
|
||||
* Creates a virtual ticket context from event data.
|
||||
*/
|
||||
public function runSystemEventAutomation(string $event, array $eventData = []): void
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
// Build a virtual ticket-like object from event data
|
||||
$context = (object) array_merge([
|
||||
'id' => 0,
|
||||
'subject' => $eventData['subject'] ?? $event,
|
||||
'body' => $eventData['body'] ?? '',
|
||||
'status' => 'open',
|
||||
'priority' => $eventData['priority'] ?? 'normal',
|
||||
'created_by' => $eventData['user_id'] ?? 0,
|
||||
'created' => gmdate('Y-m-d H:i:s'),
|
||||
'age_hours' => 0,
|
||||
], $eventData);
|
||||
|
||||
foreach ($rules as $rule)
|
||||
{
|
||||
$conditions = json_decode($rule->conditions, true) ?: [];
|
||||
$actions = json_decode($rule->actions, true) ?: [];
|
||||
|
||||
if (empty($conditions) || $this->evaluateConditions($conditions, $context))
|
||||
{
|
||||
$this->executeActions($actions, 0, $context);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
\Joomla\CMS\Log\Log::add('System event automation error: ' . $e->getMessage(), \Joomla\CMS\Log\Log::WARNING, 'mokowaas');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all automation rules.
|
||||
*/
|
||||
|
||||
@@ -938,11 +938,57 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: warnMissingLicenseKey and enforceAdminRestrictions
|
||||
// are now handled by feature plugins (deferred / tenant)
|
||||
$this->protectPlugin();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Automation event hooks (#151) — delegate to ticket automation engine
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
public function onUserLogin($user, $options = [])
|
||||
{
|
||||
$this->fireTicketAutomation('user_login', [
|
||||
'user_id' => $user['id'] ?? 0,
|
||||
'username' => $user['username'] ?? '',
|
||||
'subject' => 'User login: ' . ($user['username'] ?? ''),
|
||||
'body' => 'User ' . ($user['username'] ?? '') . ' logged in from ' . ($_SERVER['REMOTE_ADDR'] ?? ''),
|
||||
]);
|
||||
}
|
||||
|
||||
public function onUserAfterSave($user, $isNew, $success, $msg)
|
||||
{
|
||||
if ($isNew && $success)
|
||||
{
|
||||
$this->fireTicketAutomation('user_register', [
|
||||
'user_id' => $user['id'] ?? 0,
|
||||
'username' => $user['username'] ?? '',
|
||||
'subject' => 'New user registered: ' . ($user['username'] ?? ''),
|
||||
'body' => 'New user: ' . ($user['name'] ?? '') . ' (' . ($user['email'] ?? '') . ')',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function onUserLoginFailure($response)
|
||||
{
|
||||
$this->fireTicketAutomation('user_login_failed', [
|
||||
'subject' => 'Failed login attempt',
|
||||
'body' => 'Failed login from ' . ($_SERVER['REMOTE_ADDR'] ?? '') . ': ' . ($response['username'] ?? ''),
|
||||
]);
|
||||
}
|
||||
|
||||
private function fireTicketAutomation(string $event, array $data): void
|
||||
{
|
||||
try
|
||||
{
|
||||
$model = new \Moko\Component\MokoWaaS\Administrator\Model\TicketsModel();
|
||||
$model->runSystemEventAutomation($event, $data);
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
// Silent — automation should never break the main flow
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject visual branding into the document head.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user