diff --git a/source/packages/com_mokosuiteclient/admin/sql/install.mysql.sql b/source/packages/com_mokosuiteclient/admin/sql/install.mysql.sql
index 8b744bcf..badd0183 100644
--- a/source/packages/com_mokosuiteclient/admin/sql/install.mysql.sql
+++ b/source/packages/com_mokosuiteclient/admin/sql/install.mysql.sql
@@ -326,3 +326,27 @@ CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_replacements` (
PRIMARY KEY (`id`),
KEY `idx_published` (`published`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+--
+-- Content Templates
+--
+
+CREATE TABLE IF NOT EXISTS `#__mokosuiteclient_content_templates` (
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `alias` VARCHAR(100) NOT NULL DEFAULT '',
+ `name` VARCHAR(255) NOT NULL DEFAULT '',
+ `description` TEXT NOT NULL,
+ `category` VARCHAR(50) NOT NULL DEFAULT '',
+ `color` VARCHAR(8) DEFAULT NULL,
+ `template_data` MEDIUMTEXT NOT NULL,
+ `joomla_category_id` INT NOT NULL DEFAULT 0,
+ `access` INT UNSIGNED NOT NULL DEFAULT 1,
+ `published` TINYINT(1) NOT NULL DEFAULT 1,
+ `ordering` INT NOT NULL DEFAULT 0,
+ `checked_out` INT UNSIGNED DEFAULT NULL,
+ `checked_out_time` DATETIME DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `idx_published` (`published`),
+ KEY `idx_alias` (`alias`),
+ KEY `idx_category` (`joomla_category_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
diff --git a/source/packages/plg_system_mokosuiteclient/Extension/MokoSuiteClient.php b/source/packages/plg_system_mokosuiteclient/Extension/MokoSuiteClient.php
index 24657043..ad9b4ab5 100644
--- a/source/packages/plg_system_mokosuiteclient/Extension/MokoSuiteClient.php
+++ b/source/packages/plg_system_mokosuiteclient/Extension/MokoSuiteClient.php
@@ -3023,6 +3023,14 @@ class MokoSuiteClient extends CMSPlugin implements BootableExtensionInterface
$modified = true;
}
+ if (stripos($text, '{template') !== false
+ && (int) $this->params->get('content_templates_enabled', 0) === 1
+ && $this->contentTemplatesTableExists())
+ {
+ $this->processTemplateTags($text);
+ $modified = true;
+ }
+
if (stripos($text, '{article') !== false && (int) $this->params->get('articles_anywhere_enabled', 0) === 1)
{
$this->processArticleTags($text);
@@ -3119,6 +3127,14 @@ class MokoSuiteClient extends CMSPlugin implements BootableExtensionInterface
$changed = true;
}
+ if (stripos($body, '{template') !== false
+ && (int) $this->params->get('content_templates_enabled', 0) === 1
+ && $this->contentTemplatesTableExists())
+ {
+ $this->processTemplateTags($body);
+ $changed = true;
+ }
+
if (stripos($body, '{article') !== false
&& (int) $this->params->get('articles_anywhere_enabled', 0) === 1)
{
@@ -3939,4 +3955,95 @@ class MokoSuiteClient extends CMSPlugin implements BootableExtensionInterface
};
}, $text);
}
+
+ /**
+ * Process {template alias="..."} content tags.
+ *
+ * Loads a content template from the database, decodes the JSON
+ * `template_data` column, and returns the concatenation of its
+ * `introtext` and `fulltext` fields.
+ *
+ * @param string &$text The text to process (modified in place).
+ *
+ * @return void
+ *
+ * @since 02.48.00
+ */
+ private function processTemplateTags(string &$text): void
+ {
+ if (!$this->params->get('content_templates_enabled', 0))
+ {
+ return;
+ }
+
+ $pattern = '#\{template\s+([^}]+)\}#i';
+
+ $text = preg_replace_callback($pattern, function ($match) {
+ $attrs = $this->parseTagAttributes($match[1]);
+ $alias = $attrs['alias'] ?? $attrs['id'] ?? '';
+
+ if (empty($alias))
+ {
+ return $match[0];
+ }
+
+ try
+ {
+ $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('template_data'))
+ ->from($db->quoteName('#__mokosuiteclient_content_templates'))
+ ->where($db->quoteName('published') . ' = 1');
+
+ if (is_numeric($alias))
+ {
+ $query->where($db->quoteName('id') . ' = ' . (int) $alias);
+ }
+ else
+ {
+ $query->where($db->quoteName('alias') . ' = ' . $db->quote($alias));
+ }
+
+ $db->setQuery($query);
+ $data = json_decode($db->loadResult() ?? '{}');
+
+ return ($data->introtext ?? '') . ($data->fulltext ?? '');
+ }
+ catch (\Throwable $e)
+ {
+ return '';
+ }
+ }, $text);
+ }
+
+ /**
+ * Check whether the content_templates DB table exists.
+ *
+ * @return bool
+ *
+ * @since 02.48.00
+ */
+ private function contentTemplatesTableExists(): bool
+ {
+ static $exists = null;
+
+ if ($exists !== null)
+ {
+ return $exists;
+ }
+
+ try
+ {
+ $db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
+ $tables = $db->getTableList();
+ $prefix = $db->getPrefix();
+ $exists = \in_array($prefix . 'mokosuiteclient_content_templates', $tables, true);
+ }
+ catch (\Exception $e)
+ {
+ $exists = false;
+ }
+
+ return $exists;
+ }
}
diff --git a/source/packages/plg_system_mokosuiteclient/Field/ArticlesField.php b/source/packages/plg_system_mokosuiteclient/Field/ArticlesField.php
new file mode 100644
index 00000000..ad5e96d5
--- /dev/null
+++ b/source/packages/plg_system_mokosuiteclient/Field/ArticlesField.php
@@ -0,0 +1,67 @@
+
+ *
+ * @since 02.47.62
+ */
+class ArticlesField extends ListField
+{
+ protected $type = 'Articles';
+
+ protected function getOptions(): array
+ {
+ $options = parent::getOptions();
+
+ $db = Factory::getContainer()->get(DatabaseInterface::class);
+ $query = $db->getQuery(true)
+ ->select([
+ $db->quoteName('a.id', 'value'),
+ $db->quoteName('a.title', 'text'),
+ $db->quoteName('c.title', 'category'),
+ ])
+ ->from($db->quoteName('#__content', 'a'))
+ ->leftJoin($db->quoteName('#__categories', 'c') . ' ON c.id = a.catid')
+ ->where($db->quoteName('a.state') . ' = 1')
+ ->order($db->quoteName('a.title') . ' ASC');
+
+ $db->setQuery($query);
+ $articles = $db->loadObjectList() ?: [];
+
+ foreach ($articles as $article) {
+ $label = $article->text;
+ if (!empty($article->category)) {
+ $label .= ' [' . $article->category . ']';
+ }
+ $options[] = HTMLHelper::_('select.option', $article->value, $label);
+ }
+
+ return $options;
+ }
+}
diff --git a/source/packages/plg_system_mokosuiteclient/mokosuiteclient.xml b/source/packages/plg_system_mokosuiteclient/mokosuiteclient.xml
index 0af93bf4..bb1363cf 100644
--- a/source/packages/plg_system_mokosuiteclient/mokosuiteclient.xml
+++ b/source/packages/plg_system_mokosuiteclient/mokosuiteclient.xml
@@ -123,6 +123,14 @@
+
+
+
+
+