Fix: add missing system plugins to package manifest, remove old src/ (#137) #145

Merged
jmiller merged 2 commits from dev into main 2026-06-21 22:48:17 +00:00
74 changed files with 55 additions and 1178 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
<display-name>Package - MokoSuiteCross</display-name>
<org>MokoConsulting</org>
<description>Cross-posting Joomla content to social media, email marketing, and chat platforms</description>
<version>01.03.00</version>
<version>01.03.01</version>
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
</identity>
<governance>
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokoplatform.Automation
# VERSION: 01.03.00
# VERSION: 01.03.01
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
+4 -1
View File
@@ -2,11 +2,14 @@
## [Unreleased]
### Fixed
- **Package manifest**: Added missing `plg_system_mokosuitecross_events` and `plg_system_mokosuitecross_gallery` to `pkg_mokosuitecross.xml` — these system plugins were not installed with the package
- **Cleanup**: Removed old `src/` directory (pre-rename cruft with `mokojoomcross` files)
## [01.03.00] --- 2026-06-21
<!-- VERSION: 01.03.00 -->
<!-- VERSION: 01.03.01 -->
All notable changes to MokoSuiteCross will be documented in this file.
+1 -1
View File
@@ -1,6 +1,6 @@
# MokoSuiteCross
<!-- VERSION: 01.03.00 -->
<!-- VERSION: 01.03.01 -->
Cross-posting Joomla content to social media, email marketing, and chat platforms for Joomla 5/6.
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="component" method="upgrade">
<name>com_mokosuitecross</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="content" method="upgrade">
<name>Content - MokoSuiteCross</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - ActivityPub (Fediverse)</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Google Blogger</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Bluesky</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Brevo (Sendinblue)</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Constant Contact</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - ConvertKit</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Dev.to</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Discord</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Facebook / Meta</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Ghost</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Google Business Profile</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Google Chat</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Hashnode</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - LinkedIn</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Mailchimp</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Mastodon</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Matrix / Element</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Medium</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - MokoSuiteCalendar Events</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - MokoSuiteGallery</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Nostr</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Ntfy Push Notifications</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Pinterest</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Reddit</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - RSS Feed</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - SendGrid</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Slack</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Microsoft Teams</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Telegram</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Threads (Meta)</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - TikTok</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Tumblr</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - X / Twitter</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - Generic Webhook</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - WhatsApp Business</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="mokosuitecross" method="upgrade">
<name>MokoSuiteCross - WordPress</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="system" method="upgrade">
<name>System - MokoSuiteCross</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="system" method="upgrade">
<name>System - MokoSuiteCross Events</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="system" method="upgrade">
<name>System - MokoSuiteCross Gallery</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="task" method="upgrade">
<name>Task - MokoSuiteCross Queue Processor</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="webservices" method="upgrade">
<name>Web Services - MokoSuiteCross</name>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
+5 -1
View File
@@ -2,7 +2,7 @@
<extension type="package" method="upgrade">
<name>MokoSuiteCross</name>
<packagename>mokosuitecross</packagename>
<version>01.03.00</version>
<version>01.03.01-dev</version>
<creationDate>2026-05-28</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -60,6 +60,10 @@
<file type="plugin" id="tiktok" group="mokosuitecross">plg_mokosuitecross_tiktok.zip</file>
<file type="plugin" id="mokosuitecalendar" group="mokosuitecross">plg_mokosuitecross_mokosuitecalendar.zip</file>
<file type="plugin" id="mokosuitegallery" group="mokosuitecross">plg_mokosuitecross_mokosuitegallery.zip</file>
<!-- Content Source Plugins (system group) -->
<file type="plugin" id="mokosuitecross_events" group="system">plg_system_mokosuitecross_events.zip</file>
<file type="plugin" id="mokosuitecross_gallery" group="system">plg_system_mokosuitecross_gallery.zip</file>
</files>
<languages>
-1
View File
@@ -1 +0,0 @@
<\!DOCTYPE html><title></title>
-1
View File
@@ -1 +0,0 @@
<\!DOCTYPE html><title></title>
-1
View File
@@ -1 +0,0 @@
<\!DOCTYPE html><title></title>
-54
View File
@@ -1,54 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<config>
<fieldset name="component" label="COM_MOKOJOOMCROSS_CONFIG_COMPONENT">
<field
name="auto_post_on_publish"
type="radio"
label="COM_MOKOJOOMCROSS_CONFIG_AUTO_POST"
description="COM_MOKOJOOMCROSS_CONFIG_AUTO_POST_DESC"
default="1"
class="btn-group">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field
name="retry_max"
type="number"
label="COM_MOKOJOOMCROSS_CONFIG_RETRY_MAX"
description="COM_MOKOJOOMCROSS_CONFIG_RETRY_MAX_DESC"
default="3"
min="0"
max="10"
/>
<field
name="retry_delay"
type="number"
label="COM_MOKOJOOMCROSS_CONFIG_RETRY_DELAY"
description="COM_MOKOJOOMCROSS_CONFIG_RETRY_DELAY_DESC"
default="300"
min="60"
max="3600"
/>
<field
name="log_retention_days"
type="number"
label="COM_MOKOJOOMCROSS_CONFIG_LOG_RETENTION"
description="COM_MOKOJOOMCROSS_CONFIG_LOG_RETENTION_DESC"
default="90"
min="7"
max="365"
/>
<field
name="default_template"
type="textarea"
label="COM_MOKOJOOMCROSS_CONFIG_DEFAULT_TEMPLATE"
description="COM_MOKOJOOMCROSS_CONFIG_DEFAULT_TEMPLATE_DESC"
default="{title}\n\n{introtext}\n\n{url}"
rows="4"
/>
</fieldset>
</config>
@@ -1,60 +0,0 @@
; MokoJoomCross — Admin Backend Language File
; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License: GPL-3.0-or-later
COM_MOKOJOOMCROSS="MokoJoomCross"
COM_MOKOJOOMCROSS_DESCRIPTION="Cross-posting Joomla content to social media, email marketing, and chat platforms"
; Submenu
COM_MOKOJOOMCROSS_SUBMENU_DASHBOARD="Dashboard"
COM_MOKOJOOMCROSS_SUBMENU_POSTS="Post Queue"
COM_MOKOJOOMCROSS_SUBMENU_SERVICES="Services"
COM_MOKOJOOMCROSS_SUBMENU_LOGS="Activity Logs"
; Dashboard
COM_MOKOJOOMCROSS_DASHBOARD_ACTIVE_SERVICES="Active Services"
COM_MOKOJOOMCROSS_DASHBOARD_QUEUED="Queued"
COM_MOKOJOOMCROSS_DASHBOARD_POSTED="Posted"
COM_MOKOJOOMCROSS_DASHBOARD_FAILED="Failed"
COM_MOKOJOOMCROSS_DASHBOARD_QUICK_LINKS="Quick Links"
; Migration
COM_MOKOJOOMCROSS_MIGRATION_TITLE="Migrate from Perfect Publisher Pro"
COM_MOKOJOOMCROSS_MIGRATION_DESCRIPTION="We detected Perfect Publisher Pro settings. Import your service configurations to MokoJoomCross."
COM_MOKOJOOMCROSS_MIGRATION_BUTTON="Start Migration"
COM_MOKOJOOMCROSS_MIGRATION_SUCCESS="Migration complete: %d service(s) imported, %d skipped."
COM_MOKOJOOMCROSS_MIGRATION_ERROR="Migration encountered errors: %s"
; Services
COM_MOKOJOOMCROSS_FIELD_SERVICE_TYPE="Service Type"
COM_MOKOJOOMCROSS_SELECT_SERVICE_TYPE="- Select Service Type -"
COM_MOKOJOOMCROSS_FIELDSET_CREDENTIALS="API Credentials"
COM_MOKOJOOMCROSS_FIELD_CREDENTIALS="Credentials (JSON)"
COM_MOKOJOOMCROSS_FIELD_CREDENTIALS_DESC="JSON object with API keys and tokens for this service. Keys vary by platform."
; Posts
COM_MOKOJOOMCROSS_FILTER_SEARCH="Search"
COM_MOKOJOOMCROSS_FILTER_STATUS="Status"
COM_MOKOJOOMCROSS_SELECT_STATUS="- Select Status -"
COM_MOKOJOOMCROSS_FILTER_SERVICE_TYPE="Service Type"
COM_MOKOJOOMCROSS_CREATED_ASC="Created ascending"
COM_MOKOJOOMCROSS_CREATED_DESC="Created descending"
COM_MOKOJOOMCROSS_STATUS_ASC="Status ascending"
COM_MOKOJOOMCROSS_STATUS_DESC="Status descending"
; Actions
COM_MOKOJOOMCROSS_ACTION_CROSSPOST="Cross-post"
COM_MOKOJOOMCROSS_ACTION_MIGRATE="Migrate"
; Configuration
COM_MOKOJOOMCROSS_CONFIG_COMPONENT="MokoJoomCross Settings"
COM_MOKOJOOMCROSS_CONFIG_AUTO_POST="Auto-post on Publish"
COM_MOKOJOOMCROSS_CONFIG_AUTO_POST_DESC="Automatically cross-post articles when they are published"
COM_MOKOJOOMCROSS_CONFIG_RETRY_MAX="Max Retries"
COM_MOKOJOOMCROSS_CONFIG_RETRY_MAX_DESC="Maximum number of retry attempts for failed posts"
COM_MOKOJOOMCROSS_CONFIG_RETRY_DELAY="Retry Delay (seconds)"
COM_MOKOJOOMCROSS_CONFIG_RETRY_DELAY_DESC="Seconds to wait before retrying a failed post"
COM_MOKOJOOMCROSS_CONFIG_LOG_RETENTION="Log Retention (days)"
COM_MOKOJOOMCROSS_CONFIG_LOG_RETENTION_DESC="Number of days to keep activity logs"
COM_MOKOJOOMCROSS_CONFIG_DEFAULT_TEMPLATE="Default Message Template"
COM_MOKOJOOMCROSS_CONFIG_DEFAULT_TEMPLATE_DESC="Default template for cross-posts. Placeholders: {title}, {url}, {introtext}, {image}, {category}, {author}"
@@ -1,149 +0,0 @@
<?php
/**
* @package MokoJoomCross
* @subpackage com_mokojoomcross
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace Joomla\Component\MokoJoomCross\Administrator\Helper;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
/**
* Migration helper for importing settings from Perfect Publisher Pro.
*
* Reads Perfect Publisher Pro's component params and plugin configurations
* and maps them to MokoJoomCross service records.
*/
class MigrationHelper
{
/**
* Service type mapping from Perfect Publisher Pro to MokoJoomCross.
*
* @var array
*/
private const SERVICE_MAP = [
'facebook' => 'facebook',
'twitter' => 'twitter',
'linkedin' => 'linkedin',
'telegram' => 'telegram',
];
/**
* Run the full migration from Perfect Publisher Pro.
*
* @return array ['migrated' => int, 'skipped' => int, 'errors' => string[]]
*/
public static function migrate(): array
{
$db = Factory::getDbo();
$result = ['migrated' => 0, 'skipped' => 0, 'errors' => []];
// Read Perfect Publisher Pro component params
$query = $db->getQuery(true)
->select($db->quoteName('params'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' LIKE ' . $db->quote('%perfectpublisher%'))
->where($db->quoteName('type') . ' = ' . $db->quote('component'));
$db->setQuery($query);
$rawParams = $db->loadResult();
if (!$rawParams) {
$result['errors'][] = 'Perfect Publisher Pro not found or has no configuration.';
return $result;
}
$params = json_decode($rawParams, true);
if (!is_array($params)) {
$result['errors'][] = 'Could not parse Perfect Publisher Pro configuration.';
return $result;
}
// Iterate known service mappings and create MokoJoomCross service records
foreach (self::SERVICE_MAP as $ppKey => $mjcType) {
$credentials = self::extractCredentials($params, $ppKey);
if (empty($credentials)) {
$result['skipped']++;
continue;
}
// Check if service already exists
$query = $db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__mokojoomcross_services'))
->where($db->quoteName('service_type') . ' = ' . $db->quote($mjcType));
$db->setQuery($query);
if ((int) $db->loadResult() > 0) {
$result['skipped']++;
continue;
}
// Insert new service record
$service = (object) [
'title' => ucfirst($mjcType) . ' (migrated from PP Pro)',
'alias' => $mjcType . '-migrated',
'service_type' => $mjcType,
'credentials' => json_encode($credentials),
'params' => '{}',
'published' => 0, // Disabled until user verifies
'ordering' => 0,
'created' => Factory::getDate()->toSql(),
'modified' => Factory::getDate()->toSql(),
'created_by' => Factory::getApplication()->getIdentity()->id ?? 0,
];
$db->insertObject('#__mokojoomcross_services', $service);
$result['migrated']++;
}
// Clear migration flag
$query = $db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = ' . $db->quote('{}'))
->where($db->quoteName('type') . ' = ' . $db->quote('component'))
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokojoomcross'));
$db->setQuery($query);
$db->execute();
return $result;
}
/**
* Extract credentials for a specific service from PP Pro params.
*
* @param array $params PP Pro component params
* @param string $serviceKey Service key in PP Pro params
*
* @return array Credential key/value pairs (empty if none found)
*/
private static function extractCredentials(array $params, string $serviceKey): array
{
$credentials = [];
// PP Pro uses various key patterns: {service}_app_id, {service}_api_key, etc.
$prefixes = [$serviceKey . '_', $serviceKey . 'api_', $serviceKey . '-'];
foreach ($params as $key => $value) {
foreach ($prefixes as $prefix) {
if (str_starts_with($key, $prefix) && !empty($value)) {
$cleanKey = str_replace($prefix, '', $key);
$credentials[$cleanKey] = $value;
}
}
}
return $credentials;
}
}
@@ -1,73 +0,0 @@
<?php
/**
* @package MokoJoomCross
* @subpackage com_mokojoomcross
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace Joomla\Component\MokoJoomCross\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
class DashboardModel extends BaseDatabaseModel
{
/**
* Get summary statistics for the dashboard.
*
* @return object Stats object with counts
*/
public function getStats(): object
{
$db = $this->getDatabase();
$stats = new \stdClass();
// Active services count
$query = $db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__mokojoomcross_services'))
->where($db->quoteName('published') . ' = 1');
$db->setQuery($query);
$stats->active_services = (int) $db->loadResult();
// Posts by status
foreach (['queued', 'posted', 'failed'] as $status) {
$query = $db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__mokojoomcross_posts'))
->where($db->quoteName('status') . ' = ' . $db->quote($status));
$db->setQuery($query);
$stats->{$status . '_count'} = (int) $db->loadResult();
}
return $stats;
}
/**
* Check if Perfect Publisher Pro migration is available.
*
* @return bool
*/
public function isMigrationAvailable(): bool
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select($db->quoteName('params'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' . $db->quote('component'))
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokojoomcross'));
$db->setQuery($query);
$params = json_decode($db->loadResult() ?: '{}', true);
return !empty($params['migration_available']);
}
}
@@ -1,39 +0,0 @@
<?php
/**
* @package MokoJoomCross
* @subpackage com_mokojoomcross
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace Joomla\Component\MokoJoomCross\Administrator\View\Dashboard;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected $stats;
protected $migrationAvailable;
public function display($tpl = null): void
{
$this->stats = $this->get('Stats');
$this->migrationAvailable = $this->get('MigrationAvailable');
$this->addToolbar();
parent::display($tpl);
}
protected function addToolbar(): void
{
ToolbarHelper::title('MokoJoomCross — Dashboard', 'share-alt');
ToolbarHelper::preferences('com_mokojoomcross');
}
}
@@ -1,41 +0,0 @@
<?php
/**
* @package MokoJoomCross
* @subpackage com_mokojoomcross
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace Joomla\Component\MokoJoomCross\Administrator\View\Posts;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected $items;
protected $pagination;
protected $state;
public function display($tpl = null): void
{
$this->items = $this->get('Items');
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
$this->addToolbar();
parent::display($tpl);
}
protected function addToolbar(): void
{
ToolbarHelper::title('MokoJoomCross — Post Queue', 'share-alt');
ToolbarHelper::deleteList('', 'posts.delete', 'JTOOLBAR_DELETE');
}
}
@@ -1,90 +0,0 @@
<?php
/**
* @package MokoJoomCross
* @subpackage com_mokojoomcross
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* SPDX-License-Identifier: GPL-3.0-or-later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
/** @var \Joomla\Component\MokoJoomCross\Administrator\View\Dashboard\HtmlView $this */
$stats = $this->stats;
?>
<div class="row">
<div class="col-lg-9">
<div class="row">
<div class="col-sm-6 col-md-3">
<div class="card text-center mb-3">
<div class="card-body">
<h5 class="card-title"><?php echo Text::_('COM_MOKOJOOMCROSS_DASHBOARD_ACTIVE_SERVICES'); ?></h5>
<p class="display-4"><?php echo $stats->active_services; ?></p>
</div>
</div>
</div>
<div class="col-sm-6 col-md-3">
<div class="card text-center mb-3">
<div class="card-body">
<h5 class="card-title"><?php echo Text::_('COM_MOKOJOOMCROSS_DASHBOARD_QUEUED'); ?></h5>
<p class="display-4 text-warning"><?php echo $stats->queued_count; ?></p>
</div>
</div>
</div>
<div class="col-sm-6 col-md-3">
<div class="card text-center mb-3">
<div class="card-body">
<h5 class="card-title"><?php echo Text::_('COM_MOKOJOOMCROSS_DASHBOARD_POSTED'); ?></h5>
<p class="display-4 text-success"><?php echo $stats->posted_count; ?></p>
</div>
</div>
</div>
<div class="col-sm-6 col-md-3">
<div class="card text-center mb-3">
<div class="card-body">
<h5 class="card-title"><?php echo Text::_('COM_MOKOJOOMCROSS_DASHBOARD_FAILED'); ?></h5>
<p class="display-4 text-danger"><?php echo $stats->failed_count; ?></p>
</div>
</div>
</div>
</div>
<?php if ($this->migrationAvailable) : ?>
<div class="alert alert-info">
<h4 class="alert-heading"><?php echo Text::_('COM_MOKOJOOMCROSS_MIGRATION_TITLE'); ?></h4>
<p><?php echo Text::_('COM_MOKOJOOMCROSS_MIGRATION_DESCRIPTION'); ?></p>
<a href="<?php echo Route::_('index.php?option=com_mokojoomcross&task=dashboard.migrate'); ?>"
class="btn btn-primary">
<?php echo Text::_('COM_MOKOJOOMCROSS_MIGRATION_BUTTON'); ?>
</a>
</div>
<?php endif; ?>
</div>
<div class="col-lg-3">
<div class="card">
<div class="card-body">
<h5 class="card-title"><?php echo Text::_('COM_MOKOJOOMCROSS_DASHBOARD_QUICK_LINKS'); ?></h5>
<div class="list-group list-group-flush">
<a href="<?php echo Route::_('index.php?option=com_mokojoomcross&view=services'); ?>"
class="list-group-item list-group-item-action">
<?php echo Text::_('COM_MOKOJOOMCROSS_SUBMENU_SERVICES'); ?>
</a>
<a href="<?php echo Route::_('index.php?option=com_mokojoomcross&view=posts'); ?>"
class="list-group-item list-group-item-action">
<?php echo Text::_('COM_MOKOJOOMCROSS_SUBMENU_POSTS'); ?>
</a>
<a href="<?php echo Route::_('index.php?option=com_mokojoomcross&view=logs'); ?>"
class="list-group-item list-group-item-action">
<?php echo Text::_('COM_MOKOJOOMCROSS_SUBMENU_LOGS'); ?>
</a>
</div>
</div>
</div>
</div>
</div>
@@ -1 +0,0 @@
<\!DOCTYPE html><title></title>
@@ -1,2 +0,0 @@
PLG_CONTENT_MOKOJOOMCROSS="Content - MokoJoomCross"
PLG_CONTENT_MOKOJOOMCROSS_DESCRIPTION="Adds cross-post status badges to articles in the admin backend."
@@ -1,85 +0,0 @@
<?php
/**
* @package MokoJoomCross
* @subpackage plg_content_mokojoomcross
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace Joomla\Plugin\Content\MokoJoomCross\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
/**
* Content plugin that adds cross-post status badges to article views.
*/
class MokoJoomCrossContent extends CMSPlugin implements SubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'onContentBeforeDisplay' => 'onContentBeforeDisplay',
];
}
/**
* Add cross-post status indicator before article content in admin.
*
* @param string $context The context
* @param object $article The article
* @param object $params The article params
* @param int $page The page number
*
* @return string HTML to prepend to the article
*/
public function onContentBeforeDisplay(string $context, &$article, &$params, int $page = 0): string
{
if ($context !== 'com_content.article') {
return '';
}
$app = $this->getApplication();
if (!$app->isClient('administrator')) {
return '';
}
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('p.status, s.service_type')
->from($db->quoteName('#__mokojoomcross_posts', 'p'))
->join('LEFT', $db->quoteName('#__mokojoomcross_services', 's')
. ' ON ' . $db->quoteName('s.id') . ' = ' . $db->quoteName('p.service_id'))
->where($db->quoteName('p.article_id') . ' = ' . (int) $article->id)
->order($db->quoteName('p.created') . ' DESC');
$db->setQuery($query, 0, 10);
$posts = $db->loadObjectList();
if (empty($posts)) {
return '';
}
$badges = '';
foreach ($posts as $post) {
$class = match ($post->status) {
'posted' => 'badge bg-success',
'failed' => 'badge bg-danger',
'queued' => 'badge bg-warning',
default => 'badge bg-secondary',
};
$badges .= '<span class="' . $class . ' me-1">' . htmlspecialchars($post->service_type) . '</span>';
}
return '<div class="mokojoomcross-status mb-2">' . $badges . '</div>';
}
}
@@ -1,2 +0,0 @@
PLG_MOKOJOOMCROSS_BLUESKY="MokoJoomCross - Bluesky"
PLG_MOKOJOOMCROSS_BLUESKY_DESCRIPTION="Cross-post Joomla articles to Bluesky."
@@ -1,2 +0,0 @@
PLG_MOKOJOOMCROSS_DISCORD="MokoJoomCross - Discord"
PLG_MOKOJOOMCROSS_DISCORD_DESCRIPTION="Cross-post Joomla articles to Discord."
@@ -1,2 +0,0 @@
PLG_MOKOJOOMCROSS_FACEBOOK="MokoJoomCross - Facebook / Meta"
PLG_MOKOJOOMCROSS_FACEBOOK_DESCRIPTION="Cross-post Joomla articles to Facebook / Meta."
@@ -1,2 +0,0 @@
PLG_MOKOJOOMCROSS_LINKEDIN="MokoJoomCross - LinkedIn"
PLG_MOKOJOOMCROSS_LINKEDIN_DESCRIPTION="Cross-post Joomla articles to LinkedIn."
@@ -1,2 +0,0 @@
PLG_MOKOJOOMCROSS_MAILCHIMP="MokoJoomCross - Mailchimp"
PLG_MOKOJOOMCROSS_MAILCHIMP_DESCRIPTION="Cross-post Joomla articles to Mailchimp."
@@ -1,2 +0,0 @@
PLG_MOKOJOOMCROSS_MASTODON="MokoJoomCross - Mastodon"
PLG_MOKOJOOMCROSS_MASTODON_DESCRIPTION="Cross-post Joomla articles to Mastodon."
@@ -1,2 +0,0 @@
PLG_MOKOJOOMCROSS_SLACK="MokoJoomCross - Slack"
PLG_MOKOJOOMCROSS_SLACK_DESCRIPTION="Cross-post Joomla articles to Slack."
@@ -1,2 +0,0 @@
PLG_MOKOJOOMCROSS_TELEGRAM="MokoJoomCross - Telegram"
PLG_MOKOJOOMCROSS_TELEGRAM_DESCRIPTION="Cross-post Joomla articles to Telegram."
@@ -1,122 +0,0 @@
<?php
/**
* @package MokoJoomCross
* @subpackage plg_mokojoomcross_twitter
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace Joomla\Plugin\MokoJoomCross\Twitter\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Component\MokoJoomCross\Administrator\Service\MokoJoomCrossServiceInterface;
use Joomla\Event\SubscriberInterface;
/**
* X/Twitter service plugin for MokoJoomCross.
*
* Uses Twitter API v2 with OAuth 2.0 Bearer Token.
*
* Credentials format:
* {
* "bearer_token": "...",
* "api_key": "...",
* "api_secret": "...",
* "access_token": "...",
* "access_token_secret": "..."
* }
*/
class TwitterService extends CMSPlugin implements SubscriberInterface, MokoJoomCrossServiceInterface
{
public static function getSubscribedEvents(): array
{
return [
'onMokoJoomCrossGetServices' => 'onMokoJoomCrossGetServices',
];
}
public function onMokoJoomCrossGetServices(&$services): void
{
$services[] = $this;
}
public function getServiceType(): string
{
return 'twitter';
}
public function getServiceName(): string
{
return 'X / Twitter';
}
public function publish(string $message, array $media, array $credentials, array $params): array
{
// Twitter API v2 tweet creation
$apiUrl = 'https://api.twitter.com/2/tweets';
$postData = json_encode(['text' => mb_substr($message, 0, 280)]);
$ch = curl_init($apiUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postData,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . ($credentials['bearer_token'] ?? ''),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true) ?: [];
if ($httpCode === 201 && !empty($data['data']['id'])) {
return ['success' => true, 'platform_post_id' => $data['data']['id'], 'response' => $data];
}
return ['success' => false, 'platform_post_id' => '', 'response' => $data];
}
public function validateCredentials(array $credentials): array
{
$apiUrl = 'https://api.twitter.com/2/users/me';
$ch = curl_init($apiUrl);
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . ($credentials['bearer_token'] ?? '')],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true) ?: [];
if (!empty($data['data']['username'])) {
return ['valid' => true, 'message' => 'Connected', 'account_name' => '@' . $data['data']['username']];
}
return ['valid' => false, 'message' => $data['detail'] ?? 'Failed', 'account_name' => ''];
}
public function getMaxLength(): int
{
return 280;
}
public function supportsMedia(): bool
{
return true;
}
}
@@ -1,166 +0,0 @@
<?php
/**
* @package MokoJoomCross
* @subpackage plg_system_mokojoomcross
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace Joomla\Plugin\System\MokoJoomCross\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Component\MokoJoomCross\Administrator\Service\MokoJoomCrossServiceInterface;
use Joomla\Event\SubscriberInterface;
/**
* System plugin that triggers cross-posting when Joomla articles are published.
*
* Listens for onContentAfterSave events on com_content articles. When an article
* transitions to published state, it dispatches the post to all enabled service
* plugins in the `mokojoomcross` plugin group.
*/
class MokoJoomCross extends CMSPlugin implements SubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'onContentAfterSave' => 'onContentAfterSave',
];
}
/**
* Triggered after a content item is saved.
*
* @param string $context The context (e.g. 'com_content.article')
* @param object $article The article object
* @param bool $isNew Whether this is a new article
*
* @return void
*/
public function onContentAfterSave(string $context, $article, bool $isNew): void
{
// Only process Joomla articles
if ($context !== 'com_content.article') {
return;
}
// Only cross-post when article is published
if ((int) ($article->state ?? 0) !== 1) {
return;
}
// Check global auto-post setting
$componentParams = ComponentHelper::getParams('com_mokojoomcross');
if (!$componentParams->get('auto_post_on_publish', 1)) {
return;
}
$this->dispatchCrossPost($article);
}
/**
* Dispatch article to all enabled service plugins.
*
* @param object $article The article object
*
* @return void
*/
private function dispatchCrossPost(object $article): void
{
$db = Factory::getDbo();
// Load all enabled services
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__mokojoomcross_services'))
->where($db->quoteName('published') . ' = 1')
->order($db->quoteName('ordering') . ' ASC');
$db->setQuery($query);
$services = $db->loadObjectList();
if (empty($services)) {
return;
}
// Import service plugins
PluginHelper::importPlugin('mokojoomcross');
foreach ($services as $service) {
// Queue the post
$post = (object) [
'article_id' => $article->id,
'service_id' => $service->id,
'status' => 'queued',
'message' => $this->renderTemplate($article, $service),
'created' => Factory::getDate()->toSql(),
'modified' => Factory::getDate()->toSql(),
];
$db->insertObject('#__mokojoomcross_posts', $post);
// Log the queue action
$log = (object) [
'post_id' => $db->insertid(),
'service_id' => $service->id,
'level' => 'info',
'message' => sprintf('Article "%s" queued for %s', $article->title, $service->service_type),
'context' => json_encode(['article_id' => $article->id]),
'created' => Factory::getDate()->toSql(),
];
$db->insertObject('#__mokojoomcross_logs', $log);
}
}
/**
* Render the message template for a service.
*
* @param object $article The article
* @param object $service The service record
*
* @return string Rendered message
*/
private function renderTemplate(object $article, object $service): string
{
$db = Factory::getDbo();
// Try service-specific template first, fall back to default
$query = $db->getQuery(true)
->select($db->quoteName('template_body'))
->from($db->quoteName('#__mokojoomcross_templates'))
->where($db->quoteName('published') . ' = 1')
->where('(' . $db->quoteName('service_type') . ' = ' . $db->quote($service->service_type)
. ' OR ' . $db->quoteName('service_type') . ' = ' . $db->quote('default') . ')')
->order('CASE WHEN ' . $db->quoteName('service_type') . ' = '
. $db->quote($service->service_type) . ' THEN 0 ELSE 1 END')
->setLimit(1);
$db->setQuery($query);
$template = $db->loadResult() ?: '{title}\n\n{url}';
// Build article URL
$url = \Joomla\CMS\Uri\Uri::root() . 'index.php?option=com_content&view=article&id=' . $article->id;
// Replace placeholders
$replacements = [
'{title}' => $article->title ?? '',
'{introtext}' => strip_tags(mb_substr($article->introtext ?? '', 0, 280)),
'{url}' => $url,
'{image}' => json_decode($article->images ?? '{}')->image_intro ?? '',
'{category}' => '',
'{author}' => '',
];
return str_replace(array_keys($replacements), array_values($replacements), $template);
}
}
@@ -1,52 +0,0 @@
<?php
/**
* @package MokoJoomCross
* @subpackage plg_webservices_mokojoomcross
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace Joomla\Plugin\WebServices\MokoJoomCross\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Router\ApiRouter;
use Joomla\Event\SubscriberInterface;
/**
* WebServices plugin providing REST API endpoints for MokoJoomCross.
*
* Endpoints:
* GET /api/index.php/v1/mokojoomcross/posts — List cross-posts
* GET /api/index.php/v1/mokojoomcross/posts/:id — Get single post
* GET /api/index.php/v1/mokojoomcross/services — List services
* POST /api/index.php/v1/mokojoomcross/crosspost — Trigger cross-post for an article
*/
class MokoJoomCrossWebServices extends CMSPlugin implements SubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'onBeforeApiRoute' => 'onBeforeApiRoute',
];
}
public function onBeforeApiRoute(&$router): void
{
$router->createCRUDRoutes(
'v1/mokojoomcross/posts',
'posts',
['component' => 'com_mokojoomcross']
);
$router->createCRUDRoutes(
'v1/mokojoomcross/services',
'services',
['component' => 'com_mokojoomcross']
);
}
}
-44
View File
@@ -1,44 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="package" method="upgrade">
<name>MokoJoomCross</name>
<packagename>mokojoomcross</packagename>
<version>01.01.00</version>
<creationDate>2026-05-28</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>GPL-3.0-or-later</license>
<description>PKG_MOKOJOOMCROSS_DESCRIPTION</description>
<scriptfile>script.php</scriptfile>
<files folder="packages">
<!-- Core -->
<file type="component" id="com_mokojoomcross">com_mokojoomcross.zip</file>
<file type="plugin" id="mokojoomcross" group="system">plg_system_mokojoomcross.zip</file>
<file type="plugin" id="mokojoomcross" group="content">plg_content_mokojoomcross.zip</file>
<file type="plugin" id="mokojoomcross" group="webservices">plg_webservices_mokojoomcross.zip</file>
<!-- Service Plugins (mokojoomcross group) -->
<file type="plugin" id="facebook" group="mokojoomcross">plg_mokojoomcross_facebook.zip</file>
<file type="plugin" id="twitter" group="mokojoomcross">plg_mokojoomcross_twitter.zip</file>
<file type="plugin" id="linkedin" group="mokojoomcross">plg_mokojoomcross_linkedin.zip</file>
<file type="plugin" id="mastodon" group="mokojoomcross">plg_mokojoomcross_mastodon.zip</file>
<file type="plugin" id="bluesky" group="mokojoomcross">plg_mokojoomcross_bluesky.zip</file>
<file type="plugin" id="mailchimp" group="mokojoomcross">plg_mokojoomcross_mailchimp.zip</file>
<file type="plugin" id="telegram" group="mokojoomcross">plg_mokojoomcross_telegram.zip</file>
<file type="plugin" id="discord" group="mokojoomcross">plg_mokojoomcross_discord.zip</file>
<file type="plugin" id="slack" group="mokojoomcross">plg_mokojoomcross_slack.zip</file>
</files>
<languages>
<language tag="en-GB">language/en-GB/pkg_mokojoomcross.sys.ini</language>
</languages>
<updateservers>
<server type="extension" name="MokoJoomCross Updates">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomCross/updates.xml</server>
</updateservers>
<dlid prefix="dlid=" suffix=""/>
<blockChildUninstall>true</blockChildUninstall>
</extension>
-133
View File
@@ -1,133 +0,0 @@
<?php
/**
* @package MokoJoomCross
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* SPDX-License-Identifier: GPL-3.0-or-later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Language\Text;
class Pkg_MokoJoomCrossInstallerScript
{
/**
* Minimum PHP version required
*
* @var string
*/
protected $minimumPhp = '8.1.0';
/**
* Called before any install/update/uninstall action.
*
* @param string $type Action type (install, update, uninstall)
* @param InstallerAdapter $parent Installer adapter
*
* @return bool
*/
public function preflight(string $type, InstallerAdapter $parent): bool
{
if (version_compare(PHP_VERSION, $this->minimumPhp, '<')) {
Factory::getApplication()->enqueueMessage(
Text::sprintf('PKG_MOKOJOOMCROSS_PHP_VERSION_ERROR', $this->minimumPhp),
'error'
);
return false;
}
return true;
}
/**
* Called after install/update.
*
* @param string $type Action type
* @param InstallerAdapter $parent Installer adapter
*
* @return void
*/
public function postflight(string $type, InstallerAdapter $parent): void
{
$db = Factory::getDbo();
if ($type === 'install') {
// Enable core plugins automatically on fresh install
$corePlugins = [
['system', 'mokojoomcross'],
['content', 'mokojoomcross'],
['webservices', 'mokojoomcross'],
];
foreach ($corePlugins as [$folder, $element]) {
$query = $db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('enabled') . ' = 1')
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote($folder))
->where($db->quoteName('element') . ' = ' . $db->quote($element));
$db->setQuery($query);
$db->execute();
}
// Enable all service plugins in the mokojoomcross group
$query = $db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('enabled') . ' = 1')
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('mokojoomcross'));
$db->setQuery($query);
$db->execute();
// Check for Perfect Publisher Pro and offer migration
$this->detectPerfectPublisherPro($db);
}
}
/**
* Detect Perfect Publisher Pro installation and store migration flag.
*
* @param \Joomla\Database\DatabaseInterface $db Database driver
*
* @return void
*/
private function detectPerfectPublisherPro($db): void
{
$query = $db->getQuery(true)
->select($db->quoteName(['element', 'params']))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' LIKE ' . $db->quote('%perfectpublisher%'))
->where($db->quoteName('type') . ' = ' . $db->quote('component'));
$db->setQuery($query);
$result = $db->loadObject();
if ($result) {
Factory::getApplication()->enqueueMessage(
Text::_('PKG_MOKOJOOMCROSS_MIGRATION_DETECTED'),
'notice'
);
// Store migration availability in component params
$query = $db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = ' . $db->quote(json_encode([
'migration_available' => 'perfectpublisher',
'migration_source_params' => $result->params,
])))
->where($db->quoteName('type') . ' = ' . $db->quote('component'))
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokojoomcross'));
$db->setQuery($query);
$db->execute();
}
}
}