feat: initial scaffold — package manifest with dlid/updateservers + first helper
Universal: Auto Version Bump / Version Bump (push) Successful in 10s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 12s

This commit is contained in:
Jonathan Miller
2026-06-21 17:27:06 -05:00
parent fd55da06df
commit 49385fdef0
2 changed files with 175 additions and 0 deletions
@@ -0,0 +1,149 @@
<?php
namespace Moko\Plugin\System\MokoSuiteSupport\Helper;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\Database\DatabaseInterface;
/**
* Conversation management — create, message, assign, close conversations across all channels.
*/
class ConversationHelper
{
/**
* Create a new conversation.
*/
public static function create(string $channel, ?int $contactId = null, ?string $visitorName = null): object
{
$allowedChannels = ['website', 'facebook', 'instagram', 'whatsapp', 'email'];
if (!in_array($channel, $allowedChannels, true)) {
$channel = 'website';
}
$db = Factory::getContainer()->get(DatabaseInterface::class);
$conversation = (object) [
'channel' => $channel,
'contact_id' => $contactId,
'visitor_name' => $visitorName,
'status' => 'open',
'started_at' => Factory::getDate()->toSql(),
];
$db->insertObject('#__mokosuitesupport_conversations', $conversation, 'id');
return (object) ['success' => true, 'conversation_id' => (int) $conversation->id];
}
/**
* Send a message in a conversation.
*/
public static function sendMessage(int $conversationId, string $body, string $senderType = 'visitor', ?int $senderId = null): object
{
$allowedSenders = ['visitor', 'agent', 'system'];
if (!in_array($senderType, $allowedSenders, true)) {
$senderType = 'visitor';
}
$db = Factory::getContainer()->get(DatabaseInterface::class);
$filter = \Joomla\Filter\InputFilter::getInstance();
$message = (object) [
'conversation_id' => $conversationId,
'sender_type' => $senderType,
'sender_id' => $senderId,
'body' => $filter->clean($body, 'STRING'),
'sent_at' => Factory::getDate()->toSql(),
];
$db->insertObject('#__mokosuitesupport_messages', $message, 'id');
// Update conversation last_message_at
$db->setQuery($db->getQuery(true)
->update('#__mokosuitesupport_conversations')
->set('last_message_at = ' . $db->quote(Factory::getDate()->toSql()))
->where('id = ' . (int) $conversationId));
$db->execute();
return (object) ['success' => true, 'message_id' => (int) $message->id];
}
/**
* Get unified inbox — all open conversations across channels.
*/
public static function getInbox(array $filters = [], int $limit = 50): array
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$query = $db->getQuery(true)
->select('c.id, c.channel, c.status, c.visitor_name, c.started_at, c.last_message_at')
->select('cd.name AS contact_name, cd.email_to')
->select('ag.display_name AS agent_name')
->select('(SELECT COUNT(*) FROM #__mokosuitesupport_messages m WHERE m.conversation_id = c.id) AS message_count')
->select('(SELECT body FROM #__mokosuitesupport_messages m2 WHERE m2.conversation_id = c.id ORDER BY m2.sent_at DESC LIMIT 1) AS last_message')
->from($db->quoteName('#__mokosuitesupport_conversations', 'c'))
->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = c.contact_id')
->join('LEFT', $db->quoteName('#__mokosuitesupport_agents', 'ag') . ' ON ag.id = c.agent_id')
->order('c.last_message_at DESC');
if (!empty($filters['status'])) {
$query->where($db->quoteName('c.status') . ' = ' . $db->quote($filters['status']));
} else {
$query->where($db->quoteName('c.status') . ' IN (' . $db->quote('open') . ',' . $db->quote('assigned') . ')');
}
if (!empty($filters['channel'])) {
$query->where($db->quoteName('c.channel') . ' = ' . $db->quote($filters['channel']));
}
if (!empty($filters['agent_id'])) {
$query->where('c.agent_id = ' . (int) $filters['agent_id']);
}
$db->setQuery($query, 0, min(max(1, $limit), 200));
return $db->loadObjectList() ?: [];
}
/**
* Assign a conversation to an agent.
*/
public static function assign(int $conversationId, int $agentId): bool
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$db->setQuery($db->getQuery(true)
->update('#__mokosuitesupport_conversations')
->set('agent_id = ' . (int) $agentId)
->set($db->quoteName('status') . ' = ' . $db->quote('assigned'))
->set('assigned_at = ' . $db->quote(Factory::getDate()->toSql()))
->where('id = ' . (int) $conversationId));
$db->execute();
return $db->getAffectedRows() > 0;
}
/**
* Close a conversation with optional CSAT rating.
*/
public static function close(int $conversationId, ?int $csatRating = null): bool
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$query = $db->getQuery(true)
->update('#__mokosuitesupport_conversations')
->set($db->quoteName('status') . ' = ' . $db->quote('closed'))
->set('ended_at = ' . $db->quote(Factory::getDate()->toSql()))
->where('id = ' . (int) $conversationId);
if ($csatRating !== null && $csatRating >= 1 && $csatRating <= 5) {
$query->set('csat_rating = ' . (int) $csatRating);
}
$db->setQuery($query);
$db->execute();
return $db->getAffectedRows() > 0;
}
}
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<extension type="package" method="upgrade">
<name>Package - MokoSuite Support</name>
<packagename>mokosuitesupport</packagename>
<version>01.00.00</version>
<creationDate>2026-06-21</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GNU General Public License version 3 or later; see LICENSE</license>
<description>MokoSuite Support - live chat, multi-channel customer support (website, Facebook, Instagram, WhatsApp, email). Interfaces with CRM contacts and ticket system.</description>
<php_minimum>8.3</php_minimum>
<dlid prefix="dlid=" suffix=""/>
<blockChildUninstall>true</blockChildUninstall>
<scriptfile>script.php</scriptfile>
<files folder="packages">
<file type="plugin" id="plg_system_mokosuitesupport" group="system">plg_system_mokosuitesupport.zip</file>
<file type="component" id="com_mokosuitesupport">com_mokosuitesupport.zip</file>
</files>
<updateservers>
<server type="extension" priority="1" name="Package - MokoSuite Support">https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteSupport/updates.xml</server>
</updateservers>
</extension>