generated from MokoConsulting/Template-Joomla
feat: add editor scaffolding — plugin manifest, Extension, SQL (3 tables), 3 helpers (Profile, Media, Template)
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
||||
PLG_EDITORS_MOKOSUITEEDITOR="Editors - MokoSuite Editor"
|
||||
PLG_EDITORS_MOKOSUITEEDITOR_DESC="Advanced WYSIWYG editor built on TinyMCE 7 with CodeMirror 6 source editing, role-based profiles, and integrated media management."
|
||||
PLG_EDITORS_MOKOSUITEEDITOR_PROFILES="Editor Profiles"
|
||||
PLG_EDITORS_MOKOSUITEEDITOR_DEFAULT_PROFILE="Default Profile"
|
||||
PLG_EDITORS_MOKOSUITEEDITOR_ADMIN_TOOLBAR="Admin Toolbar Layout"
|
||||
PLG_EDITORS_MOKOSUITEEDITOR_AUTHOR_TOOLBAR="Author Toolbar Layout"
|
||||
PLG_EDITORS_MOKOSUITEEDITOR_BASIC_TOOLBAR="Basic Toolbar Layout"
|
||||
PLG_EDITORS_MOKOSUITEEDITOR_MEDIA="Media Settings"
|
||||
PLG_EDITORS_MOKOSUITEEDITOR_MAX_UPLOAD_MB="Max Upload Size (MB)"
|
||||
PLG_EDITORS_MOKOSUITEEDITOR_AUTO_RESIZE_WIDTH="Auto Resize Width (px)"
|
||||
PLG_EDITORS_MOKOSUITEEDITOR_WEBP_CONVERSION="Auto Convert to WebP"
|
||||
PLG_EDITORS_MOKOSUITEEDITOR_WEBP_QUALITY="WebP Quality"
|
||||
PLG_EDITORS_MOKOSUITEEDITOR_SOURCE_EDITING="Source Editing"
|
||||
PLG_EDITORS_MOKOSUITEEDITOR_ENABLE_SOURCE_EDITING="Enable Source Editing"
|
||||
PLG_EDITORS_MOKOSUITEEDITOR_CODEMIRROR_THEME="CodeMirror Theme"
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
PLG_EDITORS_MOKOSUITEEDITOR="Editors - MokoSuite Editor"
|
||||
PLG_EDITORS_MOKOSUITEEDITOR_DESC="Advanced WYSIWYG editor built on TinyMCE 7 with CodeMirror 6 source editing, role-based profiles, and integrated media management."
|
||||
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension type="plugin" group="editors" method="upgrade">
|
||||
<name>Editors - MokoSuite Editor</name>
|
||||
<element>mokosuiteeditor</element>
|
||||
<author>Moko Consulting</author>
|
||||
<creationDate>2026-06-23</creationDate>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>01.00.10</version>
|
||||
<php_minimum>8.3</php_minimum>
|
||||
<description>PLG_EDITORS_MOKOSUITEEDITOR_DESC</description>
|
||||
<namespace path="src">Moko\Plugin\Editors\MokoSuiteEditor</namespace>
|
||||
<files>
|
||||
<folder>src</folder>
|
||||
<folder>services</folder>
|
||||
<folder>language</folder>
|
||||
<folder>sql</folder>
|
||||
</files>
|
||||
<languages folder="language">
|
||||
<language tag="en-GB">en-GB/plg_editors_mokosuiteeditor.ini</language>
|
||||
<language tag="en-GB">en-GB/plg_editors_mokosuiteeditor.sys.ini</language>
|
||||
</languages>
|
||||
<install>
|
||||
<sql>
|
||||
<file driver="mysql" charset="utf8">sql/install.mysql.sql</file>
|
||||
</sql>
|
||||
</install>
|
||||
<uninstall>
|
||||
<sql>
|
||||
<file driver="mysql" charset="utf8">sql/uninstall.mysql.sql</file>
|
||||
</sql>
|
||||
</uninstall>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="profiles" label="PLG_EDITORS_MOKOSUITEEDITOR_PROFILES">
|
||||
<field name="default_profile" type="list" default="admin" label="PLG_EDITORS_MOKOSUITEEDITOR_DEFAULT_PROFILE">
|
||||
<option value="admin">Admin (full toolbar)</option>
|
||||
<option value="author">Author (standard toolbar)</option>
|
||||
<option value="basic">Basic (minimal toolbar)</option>
|
||||
</field>
|
||||
<field name="admin_toolbar" type="textarea" rows="3" default="undo redo | blocks fontfamily fontsize | bold italic underline strikethrough | link image media table | align lineheight | numlist bullist indent outdent | emoticons charmap | removeformat | code fullscreen" label="PLG_EDITORS_MOKOSUITEEDITOR_ADMIN_TOOLBAR" />
|
||||
<field name="author_toolbar" type="textarea" rows="3" default="undo redo | blocks | bold italic underline | link image | align | numlist bullist | removeformat | code" label="PLG_EDITORS_MOKOSUITEEDITOR_AUTHOR_TOOLBAR" />
|
||||
<field name="basic_toolbar" type="textarea" rows="3" default="undo redo | bold italic underline | link | numlist bullist | removeformat" label="PLG_EDITORS_MOKOSUITEEDITOR_BASIC_TOOLBAR" />
|
||||
</fieldset>
|
||||
<fieldset name="media" label="PLG_EDITORS_MOKOSUITEEDITOR_MEDIA">
|
||||
<field name="max_upload_mb" type="number" default="10" label="PLG_EDITORS_MOKOSUITEEDITOR_MAX_UPLOAD_MB" min="1" max="100" />
|
||||
<field name="auto_resize_width" type="number" default="1920" label="PLG_EDITORS_MOKOSUITEEDITOR_AUTO_RESIZE_WIDTH" min="0" max="4096" />
|
||||
<field name="webp_conversion" type="radio" default="1" label="PLG_EDITORS_MOKOSUITEEDITOR_WEBP_CONVERSION" class="btn-group btn-group-yesno">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field name="webp_quality" type="number" default="85" label="PLG_EDITORS_MOKOSUITEEDITOR_WEBP_QUALITY" min="1" max="100" showon="webp_conversion:1" />
|
||||
</fieldset>
|
||||
<fieldset name="source_editing" label="PLG_EDITORS_MOKOSUITEEDITOR_SOURCE_EDITING">
|
||||
<field name="enable_source_editing" type="radio" default="1" label="PLG_EDITORS_MOKOSUITEEDITOR_ENABLE_SOURCE_EDITING" class="btn-group btn-group-yesno">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field name="codemirror_theme" type="list" default="one-dark" label="PLG_EDITORS_MOKOSUITEEDITOR_CODEMIRROR_THEME" showon="enable_source_editing:1">
|
||||
<option value="one-dark">One Dark</option>
|
||||
<option value="one-light">One Light</option>
|
||||
<option value="basic-dark">Basic Dark</option>
|
||||
<option value="basic-light">Basic Light</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuite
|
||||
* @subpackage plg_editors_mokosuiteeditor
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Extension\PluginInterface;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Moko\Plugin\Editors\MokoSuiteEditor\Extension\MokoSuiteEditor;
|
||||
|
||||
return new class implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container): void
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new MokoSuiteEditor($dispatcher, (array) PluginHelper::getPlugin('editors', 'mokosuiteeditor'));
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
--
|
||||
-- MokoSuite Editor Tables
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `#__mokosuiteeditor_profiles` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`title` VARCHAR(255) NOT NULL,
|
||||
`user_groups` JSON DEFAULT NULL,
|
||||
`toolbar_config` JSON DEFAULT NULL,
|
||||
`allowed_tags` TEXT DEFAULT NULL,
|
||||
`source_editing` TINYINT NOT NULL DEFAULT 1,
|
||||
`media_upload` TINYINT NOT NULL DEFAULT 1,
|
||||
`max_upload_mb` INT UNSIGNED NOT NULL DEFAULT 10,
|
||||
`auto_resize_width` INT UNSIGNED NOT NULL DEFAULT 1920,
|
||||
`webp_conversion` TINYINT NOT NULL DEFAULT 1,
|
||||
`published` TINYINT NOT NULL DEFAULT 1,
|
||||
`ordering` INT NOT NULL DEFAULT 0,
|
||||
`created` DATETIME NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_published` (`published`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `#__mokosuiteeditor_templates` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`title` VARCHAR(255) NOT NULL,
|
||||
`content` TEXT NOT NULL,
|
||||
`category` VARCHAR(100) NOT NULL DEFAULT '',
|
||||
`thumbnail` VARCHAR(500) NOT NULL DEFAULT '',
|
||||
`published` TINYINT NOT NULL DEFAULT 1,
|
||||
`ordering` INT NOT NULL DEFAULT 0,
|
||||
`created` DATETIME NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_category` (`category`),
|
||||
KEY `idx_published` (`published`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `#__mokosuiteeditor_media_presets` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`title` VARCHAR(255) NOT NULL,
|
||||
`max_width` INT UNSIGNED NOT NULL DEFAULT 1920,
|
||||
`max_height` INT UNSIGNED NOT NULL DEFAULT 1080,
|
||||
`quality` INT UNSIGNED NOT NULL DEFAULT 85,
|
||||
`format` ENUM('webp','jpg','png') NOT NULL DEFAULT 'webp',
|
||||
`created` DATETIME NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
--
|
||||
-- Default profiles
|
||||
--
|
||||
|
||||
INSERT INTO `#__mokosuiteeditor_profiles` (`title`, `user_groups`, `toolbar_config`, `allowed_tags`, `source_editing`, `media_upload`, `max_upload_mb`, `auto_resize_width`, `webp_conversion`, `published`, `ordering`, `created`)
|
||||
VALUES
|
||||
('Admin', '[8]', '{"toolbar": "undo redo | blocks fontfamily fontsize | bold italic underline strikethrough | link image media table | align lineheight | numlist bullist indent outdent | emoticons charmap | removeformat | code fullscreen"}', NULL, 1, 1, 50, 1920, 1, 1, 1, NOW()),
|
||||
('Author', '[3,4]', '{"toolbar": "undo redo | blocks | bold italic underline | link image | align | numlist bullist | removeformat | code"}', '<p><a><strong><em><ul><ol><li><h2><h3><h4><img><table><tr><td><th><blockquote>', 1, 1, 10, 1920, 1, 1, 2, NOW()),
|
||||
('Basic', '[2]', '{"toolbar": "undo redo | bold italic underline | link | numlist bullist | removeformat"}', '<p><a><strong><em><ul><ol><li>', 0, 0, 5, 1024, 1, 1, 3, NOW());
|
||||
@@ -0,0 +1,7 @@
|
||||
--
|
||||
-- MokoSuite Editor — Uninstall
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `#__mokosuiteeditor_media_presets`;
|
||||
DROP TABLE IF EXISTS `#__mokosuiteeditor_templates`;
|
||||
DROP TABLE IF EXISTS `#__mokosuiteeditor_profiles`;
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuite
|
||||
* @subpackage plg_editors_mokosuiteeditor
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Moko\Plugin\Editors\MokoSuiteEditor\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Editor\EditorProviderInterface;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
/**
|
||||
* MokoSuite Editor plugin.
|
||||
*
|
||||
* TinyMCE 7 based WYSIWYG editor with CodeMirror 6 source editing,
|
||||
* role-based profiles, and integrated media management.
|
||||
*
|
||||
* @since 01.00.00
|
||||
*/
|
||||
class MokoSuiteEditor extends CMSPlugin implements SubscriberInterface, EditorProviderInterface
|
||||
{
|
||||
protected $autoloadLanguage = true;
|
||||
|
||||
/**
|
||||
* Returns an array of events this subscriber will listen to.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onEditorSetup' => 'onEditorSetup',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the editor setup event.
|
||||
*
|
||||
* @param \Joomla\Event\Event $event The event object.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 01.00.00
|
||||
*/
|
||||
public function onEditorSetup(\Joomla\Event\Event $event): void
|
||||
{
|
||||
// Register this editor with Joomla's editor manager
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content of the editor area.
|
||||
*
|
||||
* @param string $editorId The editor instance identifier.
|
||||
*
|
||||
* @return string JavaScript to retrieve editor content.
|
||||
*
|
||||
* @since 01.00.00
|
||||
*/
|
||||
public function getContent(string $editorId): string
|
||||
{
|
||||
return "tinymce.get('{$editorId}')?.getContent() ?? ''";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the content of the editor area.
|
||||
*
|
||||
* @param string $editorId The editor instance identifier.
|
||||
* @param string $content The content to set.
|
||||
*
|
||||
* @return string JavaScript to set editor content.
|
||||
*
|
||||
* @since 01.00.00
|
||||
*/
|
||||
public function setContent(string $editorId, string $content): string
|
||||
{
|
||||
$escapedContent = addslashes($content);
|
||||
|
||||
return "tinymce.get('{$editorId}')?.setContent('{$escapedContent}')";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the editor extended buttons (XTD).
|
||||
*
|
||||
* @param string $editorId The editor instance identifier.
|
||||
*
|
||||
* @return array Array of button objects.
|
||||
*
|
||||
* @since 01.00.00
|
||||
*/
|
||||
public function getButtons(string $editorId): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuite
|
||||
* @subpackage plg_editors_mokosuiteeditor
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Moko\Plugin\Editors\MokoSuiteEditor\Helper;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
|
||||
/**
|
||||
* Media upload, resize, and WebP conversion for the editor.
|
||||
*/
|
||||
class MediaHelper
|
||||
{
|
||||
/**
|
||||
* Upload a file respecting the profile's media constraints.
|
||||
*
|
||||
* @param array $file The $_FILES entry (name, tmp_name, size, type, error).
|
||||
* @param int $profileId The editor profile ID for size/format limits.
|
||||
*
|
||||
* @return array ['success' => bool, 'path' => string, 'error' => string]
|
||||
*
|
||||
* @since 01.00.00
|
||||
*/
|
||||
public static function upload(array $file, int $profileId): array
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->quoteName('#__mokosuiteeditor_profiles'))
|
||||
->where($db->quoteName('id') . ' = ' . (int) $profileId);
|
||||
|
||||
$profile = $db->setQuery($query)->loadObject();
|
||||
|
||||
if (!$profile || !$profile->media_upload) {
|
||||
return ['success' => false, 'path' => '', 'error' => 'Media upload not permitted for this profile.'];
|
||||
}
|
||||
|
||||
$maxBytes = ($profile->max_upload_mb ?: 10) * 1024 * 1024;
|
||||
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
return ['success' => false, 'path' => '', 'error' => 'Upload error code: ' . $file['error']];
|
||||
}
|
||||
|
||||
if ($file['size'] > $maxBytes) {
|
||||
return ['success' => false, 'path' => '', 'error' => 'File exceeds maximum upload size of ' . $profile->max_upload_mb . ' MB.'];
|
||||
}
|
||||
|
||||
$uploadDir = JPATH_ROOT . '/images/mokosuiteeditor/' . date('Y/m');
|
||||
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'];
|
||||
|
||||
if (!in_array($extension, $allowed, true)) {
|
||||
return ['success' => false, 'path' => '', 'error' => 'File type not allowed.'];
|
||||
}
|
||||
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0755, true);
|
||||
}
|
||||
|
||||
$filename = uniqid('mse_', true) . '.' . $extension;
|
||||
$destPath = $uploadDir . '/' . $filename;
|
||||
|
||||
if (!move_uploaded_file($file['tmp_name'], $destPath)) {
|
||||
return ['success' => false, 'path' => '', 'error' => 'Failed to move uploaded file.'];
|
||||
}
|
||||
|
||||
// Auto-resize if configured
|
||||
if ($profile->auto_resize_width > 0 && in_array($extension, ['jpg', 'jpeg', 'png', 'webp'], true)) {
|
||||
self::resize($destPath, (int) $profile->auto_resize_width);
|
||||
}
|
||||
|
||||
// WebP conversion if enabled
|
||||
if ($profile->webp_conversion && in_array($extension, ['jpg', 'jpeg', 'png'], true)) {
|
||||
$webpPath = self::convertToWebP($destPath);
|
||||
|
||||
if ($webpPath) {
|
||||
$destPath = $webpPath;
|
||||
}
|
||||
}
|
||||
|
||||
$relativePath = str_replace(JPATH_ROOT . '/', '', $destPath);
|
||||
|
||||
return ['success' => true, 'path' => $relativePath, 'error' => ''];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize an image to a maximum width, preserving aspect ratio.
|
||||
*
|
||||
* @param string $path Absolute path to the image file.
|
||||
* @param int $maxWidth Maximum width in pixels.
|
||||
*
|
||||
* @return bool True on success.
|
||||
*
|
||||
* @since 01.00.00
|
||||
*/
|
||||
public static function resize(string $path, int $maxWidth): bool
|
||||
{
|
||||
if (!file_exists($path) || $maxWidth <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$info = getimagesize($path);
|
||||
|
||||
if ($info === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
[$width, $height] = $info;
|
||||
|
||||
if ($width <= $maxWidth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$ratio = $maxWidth / $width;
|
||||
$newHeight = (int) round($height * $ratio);
|
||||
|
||||
$source = match ($info['mime']) {
|
||||
'image/jpeg' => imagecreatefromjpeg($path),
|
||||
'image/png' => imagecreatefrompng($path),
|
||||
'image/webp' => imagecreatefromwebp($path),
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (!$source) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$resized = imagecreatetruecolor($maxWidth, $newHeight);
|
||||
imagecopyresampled($resized, $source, 0, 0, 0, 0, $maxWidth, $newHeight, $width, $height);
|
||||
|
||||
$result = match ($info['mime']) {
|
||||
'image/jpeg' => imagejpeg($resized, $path, 90),
|
||||
'image/png' => imagepng($resized, $path, 6),
|
||||
'image/webp' => imagewebp($resized, $path, 85),
|
||||
default => false,
|
||||
};
|
||||
|
||||
imagedestroy($source);
|
||||
imagedestroy($resized);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an image to WebP format.
|
||||
*
|
||||
* @param string $path Absolute path to the source image.
|
||||
* @param int $quality WebP quality (1-100).
|
||||
*
|
||||
* @return string|null Path to the WebP file, or null on failure.
|
||||
*
|
||||
* @since 01.00.00
|
||||
*/
|
||||
public static function convertToWebP(string $path, int $quality = 85): ?string
|
||||
{
|
||||
if (!file_exists($path) || !function_exists('imagewebp')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$info = getimagesize($path);
|
||||
|
||||
if ($info === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$source = match ($info['mime']) {
|
||||
'image/jpeg' => imagecreatefromjpeg($path),
|
||||
'image/png' => imagecreatefrompng($path),
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (!$source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$webpPath = preg_replace('/\.(jpe?g|png)$/i', '.webp', $path);
|
||||
|
||||
if (imagewebp($source, $webpPath, $quality)) {
|
||||
imagedestroy($source);
|
||||
|
||||
// Remove original file
|
||||
if ($webpPath !== $path) {
|
||||
unlink($path);
|
||||
}
|
||||
|
||||
return $webpPath;
|
||||
}
|
||||
|
||||
imagedestroy($source);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a listing of media files for the browser.
|
||||
*
|
||||
* @param string $path Relative path under images/mokosuiteeditor/.
|
||||
*
|
||||
* @return array Array of ['name', 'path', 'size', 'type', 'modified'] entries.
|
||||
*
|
||||
* @since 01.00.00
|
||||
*/
|
||||
public static function getMediaBrowser(string $path = ''): array
|
||||
{
|
||||
$basePath = JPATH_ROOT . '/images/mokosuiteeditor';
|
||||
$fullPath = $basePath . ($path ? '/' . ltrim($path, '/') : '');
|
||||
|
||||
// Prevent directory traversal
|
||||
$realBase = realpath($basePath);
|
||||
$realFull = realpath($fullPath);
|
||||
|
||||
if ($realFull === false || !str_starts_with($realFull, $realBase)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$items = [];
|
||||
|
||||
if (!is_dir($realFull)) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
$entries = scandir($realFull);
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
if ($entry === '.' || $entry === '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entryPath = $realFull . '/' . $entry;
|
||||
$relative = str_replace(JPATH_ROOT . '/', '', $entryPath);
|
||||
|
||||
$items[] = [
|
||||
'name' => $entry,
|
||||
'path' => $relative,
|
||||
'size' => is_file($entryPath) ? filesize($entryPath) : 0,
|
||||
'type' => is_dir($entryPath) ? 'folder' : mime_content_type($entryPath),
|
||||
'modified' => date('Y-m-d H:i:s', filemtime($entryPath)),
|
||||
];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuite
|
||||
* @subpackage plg_editors_mokosuiteeditor
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Moko\Plugin\Editors\MokoSuiteEditor\Helper;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
|
||||
/**
|
||||
* Editor profile management — match user groups to editor profiles, retrieve toolbar configs.
|
||||
*/
|
||||
class ProfileHelper
|
||||
{
|
||||
/**
|
||||
* Get the most specific editor profile for a given user.
|
||||
*
|
||||
* Matches the user's Joomla groups against the profile user_groups JSON column.
|
||||
* Returns the first published match ordered by `ordering ASC`.
|
||||
*
|
||||
* @param int $userId The Joomla user ID.
|
||||
*
|
||||
* @return object|null Profile row or null if none matched.
|
||||
*
|
||||
* @since 01.00.00
|
||||
*/
|
||||
public static function getForUser(int $userId): ?object
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$user = Factory::getApplication()->getIdentity();
|
||||
$groups = $user->getAuthorisedGroups();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->quoteName('#__mokosuiteeditor_profiles'))
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
->order('ordering ASC');
|
||||
|
||||
$profiles = $db->setQuery($query)->loadObjectList();
|
||||
|
||||
foreach ($profiles as $profile) {
|
||||
$profileGroups = json_decode($profile->user_groups, true) ?: [];
|
||||
|
||||
if (array_intersect($groups, $profileGroups)) {
|
||||
return $profile;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: return first published profile
|
||||
return $profiles[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the toolbar configuration for a specific profile.
|
||||
*
|
||||
* @param int $profileId The profile ID.
|
||||
*
|
||||
* @return array Decoded toolbar_config or empty array.
|
||||
*
|
||||
* @since 01.00.00
|
||||
*/
|
||||
public static function getToolbarConfig(int $profileId): array
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName('toolbar_config'))
|
||||
->from($db->quoteName('#__mokosuiteeditor_profiles'))
|
||||
->where($db->quoteName('id') . ' = ' . (int) $profileId);
|
||||
|
||||
$result = $db->setQuery($query)->loadResult();
|
||||
|
||||
return $result ? (json_decode($result, true) ?: []) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all published profiles.
|
||||
*
|
||||
* @return array Array of profile objects.
|
||||
*
|
||||
* @since 01.00.00
|
||||
*/
|
||||
public static function getAllProfiles(): array
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->quoteName('#__mokosuiteeditor_profiles'))
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
->order('ordering ASC');
|
||||
|
||||
return $db->setQuery($query)->loadObjectList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuite
|
||||
* @subpackage plg_editors_mokosuiteeditor
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Moko\Plugin\Editors\MokoSuiteEditor\Helper;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
|
||||
/**
|
||||
* Content template management — pre-built HTML snippets for insertion into the editor.
|
||||
*/
|
||||
class TemplateHelper
|
||||
{
|
||||
/**
|
||||
* Get all published templates, optionally filtered by category.
|
||||
*
|
||||
* @param string|null $category Category slug filter, or null for all.
|
||||
*
|
||||
* @return array Array of template objects.
|
||||
*
|
||||
* @since 01.00.00
|
||||
*/
|
||||
public static function getAll(?string $category = null): array
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->quoteName('#__mokosuiteeditor_templates'))
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
->order('ordering ASC');
|
||||
|
||||
if ($category !== null && $category !== '') {
|
||||
$query->where($db->quoteName('category') . ' = ' . $db->quote($category));
|
||||
}
|
||||
|
||||
return $db->setQuery($query)->loadObjectList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single template by ID.
|
||||
*
|
||||
* @param int $id The template ID.
|
||||
*
|
||||
* @return object|null Template row or null.
|
||||
*
|
||||
* @since 01.00.00
|
||||
*/
|
||||
public static function getById(int $id): ?object
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->quoteName('#__mokosuiteeditor_templates'))
|
||||
->where($db->quoteName('id') . ' = ' . (int) $id)
|
||||
->where($db->quoteName('published') . ' = 1');
|
||||
|
||||
$result = $db->setQuery($query)->loadObject();
|
||||
|
||||
return $result ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a template's HTML content ready for insertion into the editor.
|
||||
*
|
||||
* @param int $templateId The template ID.
|
||||
*
|
||||
* @return string The template HTML content, or empty string if not found.
|
||||
*
|
||||
* @since 01.00.00
|
||||
*/
|
||||
public static function insertTemplate(int $templateId): string
|
||||
{
|
||||
$template = self::getById($templateId);
|
||||
|
||||
return $template ? $template->content : '';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user