Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c13c2a372e | |||
| 09074e3c00 | |||
| 9bfbf36090 | |||
| 7e5ff12d03 | |||
| 42f7a09bb3 | |||
| 6ad536c0ef | |||
| eb1b112a93 | |||
| 4918879eec | |||
| add973771b | |||
| 5753c307c6 | |||
| bfe4432c78 | |||
| 3c1f3a2421 | |||
| 9017b06c7d | |||
| 1cb5c77bec | |||
| dc5feaa9aa |
@@ -5,7 +5,7 @@
|
|||||||
<display-name>Package - MokoSuiteCross</display-name>
|
<display-name>Package - MokoSuiteCross</display-name>
|
||||||
<org>MokoConsulting</org>
|
<org>MokoConsulting</org>
|
||||||
<description>Cross-posting Joomla content to social media, email marketing, and chat platforms</description>
|
<description>Cross-posting Joomla content to social media, email marketing, and chat platforms</description>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09</version>
|
||||||
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
||||||
</identity>
|
</identity>
|
||||||
<governance>
|
<governance>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: mokocli.Automation
|
# INGROUP: mokocli.Automation
|
||||||
# VERSION: 01.00.00
|
# VERSION: 01.04.09
|
||||||
# BRIEF: Auto-create feature branch when an issue is opened
|
# BRIEF: Auto-create feature branch when an issue is opened
|
||||||
|
|
||||||
name: "Universal: Issue Branch"
|
name: "Universal: Issue Branch"
|
||||||
|
|||||||
+28
-1
@@ -1,6 +1,33 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Instagram plugin**: Cross-post to Instagram via Meta Content Publishing API (2-step container flow)
|
||||||
|
- **YouTube plugin**: Cross-post to YouTube via Data API v3 channel bulletins
|
||||||
|
- **Share Content panel**: Per-article editor panel with platform-specific share text fields
|
||||||
|
- **New placeholders**: {social}, {short}, {chat}, {email_subject}, {email_body} for platform-optimized templates
|
||||||
|
- **Share image control**: Choose intro image, fulltext image, custom image, or no image per article
|
||||||
|
- **Mailchimp templates**: Support Mailchimp saved templates with section injection, plus responsive email wrapper fallback
|
||||||
|
- **Delete from platforms**: New MokoSuiteCrossDeleteInterface for removing cross-posted content from remote platforms
|
||||||
|
- **Delete support**: Twitter, Mastodon, Bluesky, Facebook, LinkedIn, Telegram, Discord (7 of 38 plugins)
|
||||||
|
- **Auto-delete on unpublish**: Component config option to delete from platforms when articles are unpublished or trashed
|
||||||
|
- **UTM auto-tagging**: Append utm_source, utm_medium, utm_campaign to shared URLs with {platform} token support
|
||||||
|
- **Caption rotation**: {random:opt1|opt2|opt3} placeholder picks a random option per post
|
||||||
|
- **{url_raw} placeholder**: Clean article URL without UTM parameters
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Default templates**: Updated to use platform-specific placeholders (social/short/chat/email) with graceful fallback
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Mailchimp**: Fixed broken namespace placeholder in XML manifest
|
||||||
|
- **ConvertKit**: Removed duplicate curl_setopt_array with undefined $token
|
||||||
|
- **Brevo**: Removed duplicate curl_setopt_array with undefined $token and wrong auth header
|
||||||
|
- **Constant Contact**: Removed duplicate curl_setopt_array
|
||||||
|
- **Mailchimp**: Fixed campaign creation checking HTTP 200 instead of 2xx range
|
||||||
|
- **Medium**: Fixed getUserId() returning array instead of string on error
|
||||||
|
- **Bluesky**: Replaced md5() with hash('sha256', ...) for cache key
|
||||||
|
- **ServiceController**: Exception details no longer exposed to client
|
||||||
|
|
||||||
## [01.04.01] --- 2026-06-21
|
## [01.04.01] --- 2026-06-21
|
||||||
|
|
||||||
|
|
||||||
@@ -16,7 +43,7 @@
|
|||||||
## [01.03.00] --- 2026-06-21
|
## [01.03.00] --- 2026-06-21
|
||||||
|
|
||||||
|
|
||||||
<!-- VERSION: 01.04.01 -->
|
<!-- VERSION: 01.04.09 -->
|
||||||
|
|
||||||
All notable changes to MokoSuiteCross will be documented in this file.
|
All notable changes to MokoSuiteCross will be documented in this file.
|
||||||
|
|
||||||
|
|||||||
@@ -1,203 +0,0 @@
|
|||||||
# Makefile for Joomla Extensions
|
|
||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
# MokoSuiteCross — Cross-posting Joomla content to social media, email marketing, and chat platforms
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
# CONFIGURATION - Customize these for your extension
|
|
||||||
# ==============================================================================
|
|
||||||
|
|
||||||
# Extension Configuration
|
|
||||||
EXTENSION_NAME := mokosuitecross
|
|
||||||
EXTENSION_TYPE := package
|
|
||||||
# Options: module, plugin, component, package, template
|
|
||||||
EXTENSION_VERSION := 1.0.0
|
|
||||||
|
|
||||||
# Module Configuration (for modules only)
|
|
||||||
MODULE_TYPE := site
|
|
||||||
# Options: site, admin
|
|
||||||
|
|
||||||
# Plugin Configuration (for plugins only)
|
|
||||||
PLUGIN_GROUP := system
|
|
||||||
# Options: system, content, user, authentication, etc.
|
|
||||||
|
|
||||||
# Directories
|
|
||||||
SRC_DIR := src
|
|
||||||
BUILD_DIR := build
|
|
||||||
DIST_DIR := dist
|
|
||||||
DOCS_DIR := docs
|
|
||||||
|
|
||||||
# Joomla Installation (for local testing - customize paths)
|
|
||||||
JOOMLA_ROOT := /var/www/html/joomla
|
|
||||||
JOOMLA_VERSION := 4
|
|
||||||
|
|
||||||
# Tools
|
|
||||||
PHP := php
|
|
||||||
COMPOSER := composer
|
|
||||||
NPM := npm
|
|
||||||
PHPCS := vendor/bin/phpcs
|
|
||||||
PHPCBF := vendor/bin/phpcbf
|
|
||||||
PHPUNIT := vendor/bin/phpunit
|
|
||||||
ZIP := zip
|
|
||||||
|
|
||||||
# Coding Standards
|
|
||||||
PHPCS_STANDARD := Joomla
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
COLOR_RESET := \033[0m
|
|
||||||
COLOR_GREEN := \033[32m
|
|
||||||
COLOR_YELLOW := \033[33m
|
|
||||||
COLOR_BLUE := \033[34m
|
|
||||||
COLOR_RED := \033[31m
|
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
# TARGETS
|
|
||||||
# ==============================================================================
|
|
||||||
|
|
||||||
.PHONY: help
|
|
||||||
help: ## Show this help message
|
|
||||||
@echo "$(COLOR_BLUE)╔════════════════════════════════════════════════════════════╗$(COLOR_RESET)"
|
|
||||||
@echo "$(COLOR_BLUE)║ Joomla Extension Makefile ║$(COLOR_RESET)"
|
|
||||||
@echo "$(COLOR_BLUE)╚════════════════════════════════════════════════════════════╝$(COLOR_RESET)"
|
|
||||||
@echo ""
|
|
||||||
@echo "Extension: $(EXTENSION_NAME) ($(EXTENSION_TYPE)) v$(EXTENSION_VERSION)"
|
|
||||||
@echo ""
|
|
||||||
@echo "$(COLOR_GREEN)Available targets:$(COLOR_RESET)"
|
|
||||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " $(COLOR_BLUE)%-20s$(COLOR_RESET) %s\n", $$1, $$2}'
|
|
||||||
@echo ""
|
|
||||||
|
|
||||||
.PHONY: install-deps
|
|
||||||
install-deps: ## Install all dependencies (Composer + npm)
|
|
||||||
@echo "$(COLOR_BLUE)Installing dependencies...$(COLOR_RESET)"
|
|
||||||
@if [ -f "composer.json" ]; then \
|
|
||||||
$(COMPOSER) install; \
|
|
||||||
echo "$(COLOR_GREEN)✓ Composer dependencies installed$(COLOR_RESET)"; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
.PHONY: lint
|
|
||||||
lint: ## Run PHP linter (syntax check)
|
|
||||||
@echo "$(COLOR_BLUE)Running PHP linter...$(COLOR_RESET)"
|
|
||||||
@find . -name "*.php" ! -path "./vendor/*" ! -path "./node_modules/*" ! -path "./$(BUILD_DIR)/*" \
|
|
||||||
-exec $(PHP) -l {} \; | grep -v "No syntax errors" || true
|
|
||||||
@echo "$(COLOR_GREEN)✓ PHP linting complete$(COLOR_RESET)"
|
|
||||||
|
|
||||||
.PHONY: phpcs
|
|
||||||
phpcs: ## Run PHP CodeSniffer (Joomla standards)
|
|
||||||
@echo "$(COLOR_BLUE)Running PHP CodeSniffer...$(COLOR_RESET)"
|
|
||||||
@if [ -f "$(PHPCS)" ]; then \
|
|
||||||
$(PHPCS) --standard=$(PHPCS_STANDARD) --extensions=php --ignore=vendor,node_modules,$(BUILD_DIR) .; \
|
|
||||||
else \
|
|
||||||
echo "$(COLOR_YELLOW)⚠ PHP CodeSniffer not installed. Run: make install-deps$(COLOR_RESET)"; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
.PHONY: validate
|
|
||||||
validate: lint phpcs ## Run all validation checks
|
|
||||||
@echo "$(COLOR_GREEN)✓ All validation checks passed$(COLOR_RESET)"
|
|
||||||
|
|
||||||
.PHONY: clean
|
|
||||||
clean: ## Clean build artifacts
|
|
||||||
@echo "$(COLOR_BLUE)Cleaning build artifacts...$(COLOR_RESET)"
|
|
||||||
@rm -rf $(BUILD_DIR) $(DIST_DIR)
|
|
||||||
@echo "$(COLOR_GREEN)✓ Build artifacts cleaned$(COLOR_RESET)"
|
|
||||||
|
|
||||||
MOKO_PLATFORM ?= $(or $(wildcard ../moko-platform),$(wildcard $(HOME)/moko-platform),$(wildcard /opt/moko-platform))
|
|
||||||
MINIFY_SCRIPT := $(MOKO_PLATFORM)/build/minify.js
|
|
||||||
|
|
||||||
.PHONY: minify
|
|
||||||
minify: ## Minify CSS/JS assets
|
|
||||||
@echo "Minifying assets..."
|
|
||||||
@if [ -f "$(MINIFY_SCRIPT)" ]; then \
|
|
||||||
node "$(MINIFY_SCRIPT)" $(SRC_DIR); \
|
|
||||||
elif [ -f "scripts/minify.js" ]; then \
|
|
||||||
node scripts/minify.js; \
|
|
||||||
else \
|
|
||||||
echo "No minify script found"; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
.PHONY: build
|
|
||||||
build: clean validate minify ## Build extension package
|
|
||||||
@echo "$(COLOR_BLUE)Building Joomla extension package...$(COLOR_RESET)"
|
|
||||||
@mkdir -p $(DIST_DIR) $(BUILD_DIR)
|
|
||||||
|
|
||||||
# Determine package prefix based on extension type
|
|
||||||
@case "$(EXTENSION_TYPE)" in \
|
|
||||||
module) \
|
|
||||||
PACKAGE_PREFIX="mod_$(EXTENSION_NAME)"; \
|
|
||||||
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
|
|
||||||
;; \
|
|
||||||
plugin) \
|
|
||||||
PACKAGE_PREFIX="plg_$(PLUGIN_GROUP)_$(EXTENSION_NAME)"; \
|
|
||||||
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
|
|
||||||
;; \
|
|
||||||
component) \
|
|
||||||
PACKAGE_PREFIX="com_$(EXTENSION_NAME)"; \
|
|
||||||
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
|
|
||||||
;; \
|
|
||||||
package) \
|
|
||||||
PACKAGE_PREFIX="pkg_$(EXTENSION_NAME)"; \
|
|
||||||
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
|
|
||||||
;; \
|
|
||||||
template) \
|
|
||||||
PACKAGE_PREFIX="tpl_$(EXTENSION_NAME)"; \
|
|
||||||
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
|
|
||||||
;; \
|
|
||||||
*) \
|
|
||||||
echo "$(COLOR_RED)✗ Unknown extension type: $(EXTENSION_TYPE)$(COLOR_RESET)"; \
|
|
||||||
exit 1; \
|
|
||||||
;; \
|
|
||||||
esac; \
|
|
||||||
\
|
|
||||||
mkdir -p "$$BUILD_TARGET"; \
|
|
||||||
\
|
|
||||||
echo "Building $$PACKAGE_PREFIX..."; \
|
|
||||||
\
|
|
||||||
rsync -av --progress \
|
|
||||||
--exclude='$(BUILD_DIR)' \
|
|
||||||
--exclude='$(DIST_DIR)' \
|
|
||||||
--exclude='.git*' \
|
|
||||||
--exclude='vendor/' \
|
|
||||||
--exclude='node_modules/' \
|
|
||||||
--exclude='tests/' \
|
|
||||||
--exclude='Makefile' \
|
|
||||||
--exclude='composer.json' \
|
|
||||||
--exclude='composer.lock' \
|
|
||||||
--exclude='package.json' \
|
|
||||||
--exclude='package-lock.json' \
|
|
||||||
--exclude='phpunit.xml' \
|
|
||||||
--exclude='*.md' \
|
|
||||||
--exclude='.editorconfig' \
|
|
||||||
. "$$BUILD_TARGET/"; \
|
|
||||||
\
|
|
||||||
cd $(BUILD_DIR) && $(ZIP) -r "../$(DIST_DIR)/$${PACKAGE_PREFIX}-$(EXTENSION_VERSION).zip" "$${PACKAGE_PREFIX}"; \
|
|
||||||
\
|
|
||||||
echo "$(COLOR_GREEN)✓ Package created: $(DIST_DIR)/$${PACKAGE_PREFIX}-$(EXTENSION_VERSION).zip$(COLOR_RESET)"
|
|
||||||
|
|
||||||
.PHONY: package
|
|
||||||
package: build ## Alias for build
|
|
||||||
@echo "$(COLOR_GREEN)✓ Package ready for distribution$(COLOR_RESET)"
|
|
||||||
|
|
||||||
.PHONY: release
|
|
||||||
release: validate build ## Create a release (validate + build)
|
|
||||||
@echo "$(COLOR_GREEN)✓ Release package ready$(COLOR_RESET)"
|
|
||||||
|
|
||||||
.PHONY: version
|
|
||||||
version: ## Display version information
|
|
||||||
@echo "$(COLOR_BLUE)Extension Information:$(COLOR_RESET)"
|
|
||||||
@echo " Name: $(EXTENSION_NAME)"
|
|
||||||
@echo " Type: $(EXTENSION_TYPE)"
|
|
||||||
@echo " Version: $(EXTENSION_VERSION)"
|
|
||||||
|
|
||||||
.PHONY: security-check
|
|
||||||
security-check: ## Run security checks on dependencies
|
|
||||||
@echo "$(COLOR_BLUE)Running security checks...$(COLOR_RESET)"
|
|
||||||
@if [ -f "composer.json" ]; then \
|
|
||||||
$(COMPOSER) audit || echo "$(COLOR_YELLOW)⚠ Vulnerabilities found$(COLOR_RESET)"; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
.PHONY: all
|
|
||||||
all: install-deps validate build ## Run complete build pipeline
|
|
||||||
@echo "$(COLOR_GREEN)✓ Complete build pipeline finished$(COLOR_RESET)"
|
|
||||||
|
|
||||||
# Default target
|
|
||||||
.DEFAULT_GOAL := help
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# MokoSuiteCross
|
# MokoSuiteCross
|
||||||
|
|
||||||
<!-- VERSION: 01.04.01 -->
|
<!-- VERSION: 01.04.09 -->
|
||||||
|
|
||||||
Cross-posting Joomla content to social media, email marketing, and chat platforms for Joomla 5/6.
|
Cross-posting Joomla content to social media, email marketing, and chat platforms for Joomla 5/6.
|
||||||
|
|
||||||
@@ -14,20 +14,27 @@ MokoSuiteCross automatically publishes your Joomla articles to multiple platform
|
|||||||
- **Plugin-based services** — Each platform is a separate plugin; install only what you need
|
- **Plugin-based services** — Each platform is a separate plugin; install only what you need
|
||||||
- **Default bot mode** — Pre-configured bots for Telegram (@mokosuite_bot), Discord, and Slack — just add your channel
|
- **Default bot mode** — Pre-configured bots for Telegram (@mokosuite_bot), Discord, and Slack — just add your channel
|
||||||
- **Post queue** — Scheduled posting, retry on failure, detailed delivery logs
|
- **Post queue** — Scheduled posting, retry on failure, detailed delivery logs
|
||||||
- **Message templates** — Customize post format per platform with placeholders ({title}, {url}, {intro}, {image}, {tags}, {field:xxx})
|
- **Message templates** — Customize post format per platform with placeholders ({title}, {url}, {social}, {short}, {chat}, {email_subject}, {email_body}, {field:xxx})
|
||||||
|
- **Share Content panel** — Per-article fields for platform-optimized text (social, short, chat, email) with image picker
|
||||||
|
- **Caption rotation** — {random:opt1|opt2|opt3} placeholder for varying evergreen re-shares
|
||||||
|
- **UTM tracking** — Auto-append UTM parameters to shared links with {platform} token
|
||||||
|
- **Delete from platforms** — Remove cross-posted content when articles are unpublished/trashed (7 platforms)
|
||||||
- **Post history** — Track what was posted where, with platform response data
|
- **Post history** — Track what was posted where, with platform response data
|
||||||
- **Evergreen re-sharing** — Automatically re-share articles on a configurable interval
|
- **Evergreen re-sharing** — Automatically re-share articles on a configurable interval
|
||||||
- **Category routing** — Route articles to specific services by Joomla category
|
- **Category routing** — Route articles to specific services by Joomla category
|
||||||
|
- **Mailchimp templates** — Use saved Mailchimp templates with section injection, or built-in responsive email wrapper
|
||||||
- **Migration** — Import settings from Perfect Publisher Pro
|
- **Migration** — Import settings from Perfect Publisher Pro
|
||||||
- **REST API** — WebServices plugin for headless/external integration
|
- **REST API** — WebServices plugin for headless/external integration
|
||||||
|
|
||||||
### Supported Platforms (36)
|
### Supported Platforms (38)
|
||||||
|
|
||||||
#### Social Media
|
#### Social Media
|
||||||
| Platform | Plugin | Status |
|
| Platform | Plugin | Status |
|
||||||
|----------|--------|--------|
|
|----------|--------|--------|
|
||||||
| Facebook / Meta | `plg_mokosuitecross_facebook` | Implemented |
|
| Facebook / Meta | `plg_mokosuitecross_facebook` | Implemented |
|
||||||
| X / Twitter | `plg_mokosuitecross_twitter` | Implemented |
|
| X / Twitter | `plg_mokosuitecross_twitter` | Implemented |
|
||||||
|
| Instagram | `plg_mokosuitecross_instagram` | Implemented |
|
||||||
|
| YouTube | `plg_mokosuitecross_youtube` | Implemented |
|
||||||
| LinkedIn | `plg_mokosuitecross_linkedin` | Implemented |
|
| LinkedIn | `plg_mokosuitecross_linkedin` | Implemented |
|
||||||
| Mastodon | `plg_mokosuitecross_mastodon` | Implemented |
|
| Mastodon | `plg_mokosuitecross_mastodon` | Implemented |
|
||||||
| Bluesky | `plg_mokosuitecross_bluesky` | Implemented |
|
| Bluesky | `plg_mokosuitecross_bluesky` | Implemented |
|
||||||
|
|||||||
@@ -24,6 +24,17 @@
|
|||||||
<option value="0">JNO</option>
|
<option value="0">JNO</option>
|
||||||
</field>
|
</field>
|
||||||
|
|
||||||
|
<field
|
||||||
|
name="delete_on_unpublish"
|
||||||
|
type="radio"
|
||||||
|
label="COM_MOKOSUITECROSS_CONFIG_DELETE_ON_UNPUBLISH"
|
||||||
|
description="COM_MOKOSUITECROSS_CONFIG_DELETE_ON_UNPUBLISH_DESC"
|
||||||
|
default="0"
|
||||||
|
class="btn-group">
|
||||||
|
<option value="1">JYES</option>
|
||||||
|
<option value="0">JNO</option>
|
||||||
|
</field>
|
||||||
|
|
||||||
<field
|
<field
|
||||||
name="retry_max"
|
name="retry_max"
|
||||||
type="number"
|
type="number"
|
||||||
@@ -64,6 +75,51 @@
|
|||||||
/>
|
/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset name="utm" label="COM_MOKOSUITECROSS_CONFIG_UTM">
|
||||||
|
<field
|
||||||
|
name="utm_enabled"
|
||||||
|
type="radio"
|
||||||
|
label="COM_MOKOSUITECROSS_CONFIG_UTM_ENABLED"
|
||||||
|
description="COM_MOKOSUITECROSS_CONFIG_UTM_ENABLED_DESC"
|
||||||
|
default="0"
|
||||||
|
class="btn-group">
|
||||||
|
<option value="1">JYES</option>
|
||||||
|
<option value="0">JNO</option>
|
||||||
|
</field>
|
||||||
|
<field
|
||||||
|
name="utm_source"
|
||||||
|
type="text"
|
||||||
|
label="COM_MOKOSUITECROSS_CONFIG_UTM_SOURCE"
|
||||||
|
description="COM_MOKOSUITECROSS_CONFIG_UTM_SOURCE_DESC"
|
||||||
|
default="{platform}"
|
||||||
|
showon="utm_enabled:1"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="utm_medium"
|
||||||
|
type="text"
|
||||||
|
label="COM_MOKOSUITECROSS_CONFIG_UTM_MEDIUM"
|
||||||
|
description="COM_MOKOSUITECROSS_CONFIG_UTM_MEDIUM_DESC"
|
||||||
|
default="social"
|
||||||
|
showon="utm_enabled:1"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="utm_campaign"
|
||||||
|
type="text"
|
||||||
|
label="COM_MOKOSUITECROSS_CONFIG_UTM_CAMPAIGN"
|
||||||
|
description="COM_MOKOSUITECROSS_CONFIG_UTM_CAMPAIGN_DESC"
|
||||||
|
default="mokosuitecross"
|
||||||
|
showon="utm_enabled:1"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="utm_content"
|
||||||
|
type="text"
|
||||||
|
label="COM_MOKOSUITECROSS_CONFIG_UTM_CONTENT"
|
||||||
|
description="COM_MOKOSUITECROSS_CONFIG_UTM_CONTENT_DESC"
|
||||||
|
hint="Optional"
|
||||||
|
showon="utm_enabled:1"
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<fieldset name="evergreen" label="COM_MOKOSUITECROSS_CONFIG_EVERGREEN">
|
<fieldset name="evergreen" label="COM_MOKOSUITECROSS_CONFIG_EVERGREEN">
|
||||||
<field
|
<field
|
||||||
name="evergreen_enabled"
|
name="evergreen_enabled"
|
||||||
|
|||||||
@@ -476,6 +476,19 @@ COM_MOKOSUITECROSS_DASHBOARD_QUEUE_DEPTH_WARNING_TITLE="Large queue backlog"
|
|||||||
COM_MOKOSUITECROSS_DASHBOARD_QUEUE_DEPTH_WARNING="There are %d posts waiting in the queue. Please verify that the Joomla Task Scheduler is running and the MokoSuiteCross scheduled task is enabled in System → Scheduled Tasks."
|
COM_MOKOSUITECROSS_DASHBOARD_QUEUE_DEPTH_WARNING="There are %d posts waiting in the queue. Please verify that the Joomla Task Scheduler is running and the MokoSuiteCross scheduled task is enabled in System → Scheduled Tasks."
|
||||||
|
|
||||||
; First-Publish-Only
|
; First-Publish-Only
|
||||||
|
COM_MOKOSUITECROSS_CONFIG_DELETE_ON_UNPUBLISH="Delete from Platforms on Unpublish"
|
||||||
|
COM_MOKOSUITECROSS_CONFIG_DELETE_ON_UNPUBLISH_DESC="When an article is unpublished or trashed, automatically delete the cross-posted content from remote platforms (where supported)."
|
||||||
|
COM_MOKOSUITECROSS_CONFIG_UTM="UTM Tracking"
|
||||||
|
COM_MOKOSUITECROSS_CONFIG_UTM_ENABLED="Enable UTM Parameters"
|
||||||
|
COM_MOKOSUITECROSS_CONFIG_UTM_ENABLED_DESC="Append UTM tracking parameters to article URLs in cross-posted content for Google Analytics tracking."
|
||||||
|
COM_MOKOSUITECROSS_CONFIG_UTM_SOURCE="UTM Source"
|
||||||
|
COM_MOKOSUITECROSS_CONFIG_UTM_SOURCE_DESC="Value for utm_source. Use {platform} to auto-insert the service type (e.g. facebook, twitter, telegram)."
|
||||||
|
COM_MOKOSUITECROSS_CONFIG_UTM_MEDIUM="UTM Medium"
|
||||||
|
COM_MOKOSUITECROSS_CONFIG_UTM_MEDIUM_DESC="Value for utm_medium. Default: social."
|
||||||
|
COM_MOKOSUITECROSS_CONFIG_UTM_CAMPAIGN="UTM Campaign"
|
||||||
|
COM_MOKOSUITECROSS_CONFIG_UTM_CAMPAIGN_DESC="Value for utm_campaign. Default: mokosuitecross."
|
||||||
|
COM_MOKOSUITECROSS_CONFIG_UTM_CONTENT="UTM Content"
|
||||||
|
COM_MOKOSUITECROSS_CONFIG_UTM_CONTENT_DESC="Optional value for utm_content. Leave empty to omit."
|
||||||
COM_MOKOSUITECROSS_CONFIG_FIRST_PUBLISH_ONLY="First Publish Only"
|
COM_MOKOSUITECROSS_CONFIG_FIRST_PUBLISH_ONLY="First Publish Only"
|
||||||
COM_MOKOSUITECROSS_CONFIG_FIRST_PUBLISH_ONLY_DESC="When enabled, articles are only cross-posted on their first save as published. Subsequent edits to already-published articles will not trigger new cross-posts."
|
COM_MOKOSUITECROSS_CONFIG_FIRST_PUBLISH_ONLY_DESC="When enabled, articles are only cross-posted on their first save as published. Subsequent edits to already-published articles will not trigger new cross-posts."
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="component" method="upgrade">
|
<extension type="component" method="upgrade">
|
||||||
<name>com_mokosuitecross</name>
|
<name>com_mokosuitecross</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ CREATE TABLE IF NOT EXISTS `#__mokosuitecross_posts` (
|
|||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`article_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT 'FK to #__content.id',
|
`article_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT 'FK to #__content.id',
|
||||||
`service_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT 'FK to #__mokosuitecross_services.id',
|
`service_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT 'FK to #__mokosuitecross_services.id',
|
||||||
`status` varchar(20) NOT NULL DEFAULT 'queued' COMMENT 'queued, posting, posted, failed, scheduled',
|
`status` varchar(20) NOT NULL DEFAULT 'queued' COMMENT 'queued, posting, posted, failed, scheduled, deleted',
|
||||||
`message` text NOT NULL COMMENT 'Rendered message sent to platform',
|
`message` text NOT NULL COMMENT 'Rendered message sent to platform',
|
||||||
`platform_post_id` varchar(255) NOT NULL DEFAULT '' COMMENT 'Post ID returned by platform',
|
`platform_post_id` varchar(255) NOT NULL DEFAULT '' COMMENT 'Post ID returned by platform',
|
||||||
`platform_response` text NOT NULL COMMENT 'JSON — full API response from platform',
|
`platform_response` text NOT NULL COMMENT 'JSON — full API response from platform',
|
||||||
@@ -74,25 +74,27 @@ CREATE TABLE IF NOT EXISTS `#__mokosuitecross_logs` (
|
|||||||
-- Insert default templates
|
-- Insert default templates
|
||||||
INSERT INTO `#__mokosuitecross_templates` (`service_type`, `title`, `template_body`, `published`, `ordering`, `created`) VALUES
|
INSERT INTO `#__mokosuitecross_templates` (`service_type`, `title`, `template_body`, `published`, `ordering`, `created`) VALUES
|
||||||
('default', 'Default Template', '{title}\n\n{introtext}\n\n{url}', 1, 1, NOW()),
|
('default', 'Default Template', '{title}\n\n{introtext}\n\n{url}', 1, 1, NOW()),
|
||||||
('twitter', 'Twitter/X Default', '{title}\n\n{url}', 1, 2, NOW()),
|
('twitter', 'Twitter/X Default', '{short}\n\n{url}', 1, 2, NOW()),
|
||||||
('mastodon', 'Mastodon Default', '{title}\n\n{introtext}\n\n{url}\n\n#Joomla', 1, 3, NOW()),
|
('mastodon', 'Mastodon Default', '{social}\n\n{url}\n\n{hashtags}', 1, 3, NOW()),
|
||||||
('mailchimp', 'Mailchimp Default', '<h1>{title}</h1>\n<p>{introtext}</p>\n<p><a href=\"{url}\">Read more</a></p>', 1, 4, NOW()),
|
('mailchimp', 'Mailchimp Default', '<h1>{email_subject}</h1>\n{email_body}\n<p><a href=\"{url}\">Read more</a></p>', 1, 4, NOW()),
|
||||||
('telegram', 'Telegram Default', '<b>{title}</b>\n\n{introtext}\n\n<a href=\"{url}\">Read more</a>', 1, 5, NOW()),
|
('telegram', 'Telegram Default', '<b>{title}</b>\n\n{chat}\n\n<a href=\"{url}\">Read more</a>', 1, 5, NOW()),
|
||||||
('discord', 'Discord Default', '**{title}**\n\n{introtext}\n\n{url}', 1, 6, NOW()),
|
('discord', 'Discord Default', '**{title}**\n\n{chat}\n\n{url}', 1, 6, NOW()),
|
||||||
('slack', 'Slack Default', '*{title}*\n\n{introtext}\n\n{url}', 1, 7, NOW()),
|
('slack', 'Slack Default', '*{title}*\n\n{chat}\n\n{url}', 1, 7, NOW()),
|
||||||
('facebook', 'Facebook Default', '{title}\n\n{introtext}\n\n{url}', 1, 8, NOW()),
|
('facebook', 'Facebook Default', '{social}\n\n{url}', 1, 8, NOW()),
|
||||||
('linkedin', 'LinkedIn Default', '{title}\n\n{introtext}\n\n{url}', 1, 9, NOW()),
|
('linkedin', 'LinkedIn Default', '{social}\n\n{url}\n\n{hashtags}', 1, 9, NOW()),
|
||||||
('bluesky', 'Bluesky Default', '{title}\n\n{url}', 1, 10, NOW()),
|
('bluesky', 'Bluesky Default', '{short}\n\n{url}', 1, 10, NOW()),
|
||||||
('threads', 'Threads Default', '{title}\n\n{introtext}\n\n{url}', 1, 11, NOW()),
|
('threads', 'Threads Default', '{social}\n\n{url}', 1, 11, NOW()),
|
||||||
('teams', 'Teams Default', '**{title}**\n\n{introtext}\n\n[Read more]({url})', 1, 12, NOW()),
|
('teams', 'Teams Default', '**{title}**\n\n{chat}\n\n[Read more]({url})', 1, 12, NOW()),
|
||||||
('medium', 'Medium Default', '{title}\n\n{introtext}\n\n{url}', 1, 13, NOW()),
|
('medium', 'Medium Default', '{title}\n\n{social}\n\n{url}', 1, 13, NOW()),
|
||||||
('wordpress', 'WordPress Default', '{title}\n\n{introtext}\n\n{url}', 1, 14, NOW()),
|
('wordpress', 'WordPress Default', '{title}\n\n{introtext}\n\n{url}', 1, 14, NOW()),
|
||||||
('webhook', 'Webhook Default', '{title}\n\n{introtext}\n\n{url}', 1, 15, NOW()),
|
('webhook', 'Webhook Default', '{title}\n\n{introtext}\n\n{url}', 1, 15, NOW()),
|
||||||
('sendgrid', 'SendGrid Default', '<h1>{title}</h1>\n<p>{introtext}</p>\n<p><a href=\"{url}\">Read more</a></p>', 1, 16, NOW()),
|
('sendgrid', 'SendGrid Default', '<h1>{email_subject}</h1>\n{email_body}\n<p><a href=\"{url}\">Read more</a></p>', 1, 16, NOW()),
|
||||||
('brevo', 'Brevo Default', '<h1>{title}</h1>\n<p>{introtext}</p>\n<p><a href=\"{url}\">Read more</a></p>', 1, 17, NOW()),
|
('brevo', 'Brevo Default', '<h1>{email_subject}</h1>\n{email_body}\n<p><a href=\"{url}\">Read more</a></p>', 1, 17, NOW()),
|
||||||
('ntfy', 'Ntfy Default', '{title}: {introtext}', 1, 18, NOW()),
|
('ntfy', 'Ntfy Default', '{title}: {short}', 1, 18, NOW()),
|
||||||
('reddit', 'Reddit Default', '{title}', 1, 19, NOW()),
|
('reddit', 'Reddit Default', '{title}', 1, 19, NOW()),
|
||||||
('pinterest', 'Pinterest Default', '{title} - {introtext}', 1, 20, NOW());
|
('pinterest', 'Pinterest Default', '{title} - {social}', 1, 20, NOW()),
|
||||||
|
('instagram', 'Instagram Default', '{social}\n\n{hashtags}', 1, 21, NOW()),
|
||||||
|
('youtube', 'YouTube Default', '{social}\n\n{url}', 1, 22, NOW());
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `#__mokosuitecross_category_rules` (
|
CREATE TABLE IF NOT EXISTS `#__mokosuitecross_category_rules` (
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class ServiceController extends FormController
|
|||||||
$app->mimeType = 'application/json';
|
$app->mimeType = 'application/json';
|
||||||
$app->setHeader('Content-Type', 'application/json; charset=utf-8');
|
$app->setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
|
||||||
echo new JsonResponse($e);
|
echo new JsonResponse(['error' => $e->getMessage()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$app->close();
|
$app->close();
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use Joomla\CMS\Component\ComponentHelper;
|
|||||||
use Joomla\CMS\Factory;
|
use Joomla\CMS\Factory;
|
||||||
use Joomla\CMS\Plugin\PluginHelper;
|
use Joomla\CMS\Plugin\PluginHelper;
|
||||||
use Joomla\CMS\Uri\Uri;
|
use Joomla\CMS\Uri\Uri;
|
||||||
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossDeleteInterface;
|
||||||
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -383,11 +384,33 @@ class CrossPostDispatcher
|
|||||||
$authorName = $db->loadResult() ?: '';
|
$authorName = $db->loadResult() ?: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve share image from article attribs
|
||||||
|
$attribs = json_decode($article->attribs ?? '{}', true) ?: [];
|
||||||
|
$imageMode = $attribs['mokosuitecross_share_image'] ?? 'intro';
|
||||||
|
$images = json_decode($article->images ?? '{}');
|
||||||
$introImage = '';
|
$introImage = '';
|
||||||
$images = json_decode($article->images ?? '{}');
|
|
||||||
|
|
||||||
if (!empty($images->image_intro)) {
|
switch ($imageMode) {
|
||||||
$introImage = Uri::root() . ltrim($images->image_intro, '/');
|
case 'fulltext':
|
||||||
|
if (!empty($images->image_fulltext)) {
|
||||||
|
$introImage = Uri::root() . ltrim($images->image_fulltext, '/');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'custom':
|
||||||
|
$customImg = $attribs['mokosuitecross_custom_image'] ?? '';
|
||||||
|
if (!empty($customImg)) {
|
||||||
|
$introImage = Uri::root() . ltrim($customImg, '/');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'none':
|
||||||
|
$introImage = '';
|
||||||
|
break;
|
||||||
|
case 'intro':
|
||||||
|
default:
|
||||||
|
if (!empty($images->image_intro)) {
|
||||||
|
$introImage = Uri::root() . ltrim($images->image_intro, '/');
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$tagNames = [];
|
$tagNames = [];
|
||||||
@@ -410,17 +433,54 @@ class CrossPostDispatcher
|
|||||||
return '#' . preg_replace('/\s+/', '', $tag);
|
return '#' . preg_replace('/\s+/', '', $tag);
|
||||||
}, $tagNames));
|
}, $tagNames));
|
||||||
|
|
||||||
|
// Per-article share text (from article editor Share Content panel)
|
||||||
|
$socialText = $attribs['mokosuitecross_social_text'] ?? '';
|
||||||
|
$shortText = $attribs['mokosuitecross_short_text'] ?? '';
|
||||||
|
$chatText = $attribs['mokosuitecross_chat_text'] ?? '';
|
||||||
|
$emailSubject = $attribs['mokosuitecross_email_subject'] ?? '';
|
||||||
|
$emailBody = $attribs['mokosuitecross_email_body'] ?? '';
|
||||||
|
|
||||||
|
$introStripped = strip_tags(mb_substr($article->introtext ?? '', 0, 280));
|
||||||
|
$titleText = $article->title ?? '';
|
||||||
|
|
||||||
|
// UTM auto-tagging (#154)
|
||||||
|
$componentParams = ComponentHelper::getParams('com_mokosuitecross');
|
||||||
|
$urlRaw = $url;
|
||||||
|
|
||||||
|
if ($componentParams->get('utm_enabled', 0)) {
|
||||||
|
$utmParams = [
|
||||||
|
'utm_source' => $componentParams->get('utm_source', '{platform}'),
|
||||||
|
'utm_medium' => $componentParams->get('utm_medium', 'social'),
|
||||||
|
'utm_campaign' => $componentParams->get('utm_campaign', 'mokosuitecross'),
|
||||||
|
];
|
||||||
|
$utmContent = $componentParams->get('utm_content', '');
|
||||||
|
|
||||||
|
if (!empty($utmContent)) {
|
||||||
|
$utmParams['utm_content'] = $utmContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
$separator = (strpos($url, '?') !== false) ? '&' : '?';
|
||||||
|
$url = $url . $separator . http_build_query($utmParams);
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'{title}' => $article->title ?? '',
|
'{title}' => $titleText,
|
||||||
'{introtext}' => strip_tags(mb_substr($article->introtext ?? '', 0, 280)),
|
'{introtext}' => $introStripped,
|
||||||
'{fulltext}' => strip_tags(mb_substr($article->fulltext ?? '', 0, 500)),
|
'{fulltext}' => strip_tags(mb_substr($article->fulltext ?? '', 0, 500)),
|
||||||
'{url}' => $url,
|
'{url}' => $url,
|
||||||
'{image}' => $introImage,
|
'{url_raw}' => $urlRaw,
|
||||||
'{category}' => $categoryName,
|
'{image}' => $introImage,
|
||||||
'{author}' => $authorName,
|
'{category}' => $categoryName,
|
||||||
'{date}' => Factory::getDate($article->publish_up ?? 'now')->format('Y-m-d'),
|
'{author}' => $authorName,
|
||||||
'{tags}' => $tagsComma,
|
'{date}' => Factory::getDate($article->publish_up ?? 'now')->format('Y-m-d'),
|
||||||
'{hashtags}' => $hashtags,
|
'{tags}' => $tagsComma,
|
||||||
|
'{hashtags}' => $hashtags,
|
||||||
|
// Platform-specific share content (falls back to introtext/title if empty)
|
||||||
|
'{social}' => !empty($socialText) ? $socialText : $introStripped,
|
||||||
|
'{short}' => !empty($shortText) ? $shortText : mb_substr($titleText, 0, 250),
|
||||||
|
'{chat}' => !empty($chatText) ? $chatText : $introStripped,
|
||||||
|
'{email_subject}' => !empty($emailSubject) ? $emailSubject : $titleText,
|
||||||
|
'{email_body}' => !empty($emailBody) ? $emailBody : ($article->fulltext ?? $article->introtext ?? ''),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,6 +519,15 @@ class CrossPostDispatcher
|
|||||||
|
|
||||||
$message = str_replace(array_keys($replacements), array_values($replacements), $template);
|
$message = str_replace(array_keys($replacements), array_values($replacements), $template);
|
||||||
|
|
||||||
|
// Resolve {platform} token in UTM params (replaced with service_type)
|
||||||
|
$message = str_replace('{platform}', $service->service_type, $message);
|
||||||
|
|
||||||
|
// Resolve caption rotation: {random:option1|option2|option3} (#155)
|
||||||
|
$message = preg_replace_callback('/\{random:([^}]+)\}/', function ($matches) {
|
||||||
|
$options = explode('|', $matches[1]);
|
||||||
|
return $options[array_rand($options)];
|
||||||
|
}, $message);
|
||||||
|
|
||||||
// Resolve custom field placeholders: {field:field_name}
|
// Resolve custom field placeholders: {field:field_name}
|
||||||
$message = preg_replace_callback('/\{field:([a-zA-Z0-9_-]+)\}/', function ($matches) use ($db, $article) {
|
$message = preg_replace_callback('/\{field:([a-zA-Z0-9_-]+)\}/', function ($matches) use ($db, $article) {
|
||||||
$fieldName = $matches[1];
|
$fieldName = $matches[1];
|
||||||
@@ -478,6 +547,82 @@ class CrossPostDispatcher
|
|||||||
/**
|
/**
|
||||||
* Write an entry to the activity log.
|
* Write an entry to the activity log.
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Delete cross-posted content from remote platforms for a given article.
|
||||||
|
*
|
||||||
|
* Finds all posts with status 'posted' for this article, resolves the
|
||||||
|
* service plugin, and calls deletePost() if the plugin supports it.
|
||||||
|
*
|
||||||
|
* @param int $articleId The Joomla article ID
|
||||||
|
*/
|
||||||
|
public static function deleteFromPlatforms(int $articleId): void
|
||||||
|
{
|
||||||
|
$db = Factory::getDbo();
|
||||||
|
|
||||||
|
// Find all successfully posted entries for this article
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select('p.*, s.service_type, s.credentials')
|
||||||
|
->from($db->quoteName('#__mokosuitecross_posts', 'p'))
|
||||||
|
->join('INNER', $db->quoteName('#__mokosuitecross_services', 's')
|
||||||
|
. ' ON ' . $db->quoteName('s.id') . ' = ' . $db->quoteName('p.service_id'))
|
||||||
|
->where($db->quoteName('p.article_id') . ' = ' . $articleId)
|
||||||
|
->where($db->quoteName('p.status') . ' = ' . $db->quote('posted'))
|
||||||
|
->where($db->quoteName('p.platform_post_id') . ' != ' . $db->quote(''));
|
||||||
|
|
||||||
|
$db->setQuery($query);
|
||||||
|
$posts = $db->loadObjectList();
|
||||||
|
|
||||||
|
if (empty($posts)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load service plugins
|
||||||
|
PluginHelper::importPlugin('mokosuitecross');
|
||||||
|
$plugins = [];
|
||||||
|
Factory::getApplication()->triggerEvent('onMokoSuiteCrossGetServices', [&$plugins]);
|
||||||
|
|
||||||
|
$pluginMap = [];
|
||||||
|
foreach ($plugins as $plugin) {
|
||||||
|
$pluginMap[$plugin->getServiceType()] = $plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($posts as $post) {
|
||||||
|
$plugin = $pluginMap[$post->service_type] ?? null;
|
||||||
|
|
||||||
|
if (!$plugin instanceof MokoSuiteCrossDeleteInterface) {
|
||||||
|
self::log($db, $post->id, $post->service_id, 'info',
|
||||||
|
'Delete not supported for ' . $post->service_type);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$credentials = json_decode($post->credentials, true) ?: [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = $plugin->deletePost($post->platform_post_id, $credentials);
|
||||||
|
|
||||||
|
if (!empty($result['success'])) {
|
||||||
|
// Mark as deleted
|
||||||
|
$db->setQuery(
|
||||||
|
$db->getQuery(true)
|
||||||
|
->update($db->quoteName('#__mokosuitecross_posts'))
|
||||||
|
->set($db->quoteName('status') . ' = ' . $db->quote('deleted'))
|
||||||
|
->where($db->quoteName('id') . ' = ' . (int) $post->id)
|
||||||
|
);
|
||||||
|
$db->execute();
|
||||||
|
|
||||||
|
self::log($db, $post->id, $post->service_id, 'info',
|
||||||
|
'Deleted from ' . $post->service_type . ': ' . ($result['message'] ?? 'OK'));
|
||||||
|
} else {
|
||||||
|
self::log($db, $post->id, $post->service_id, 'warning',
|
||||||
|
'Delete failed on ' . $post->service_type . ': ' . ($result['message'] ?? 'Unknown error'));
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
self::log($db, $post->id, $post->service_id, 'error',
|
||||||
|
'Delete exception on ' . $post->service_type . ': ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static function log($db, ?int $postId, ?int $serviceId, string $level, string $message): void
|
private static function log($db, ?int $postId, ?int $serviceId, string $level, string $message): void
|
||||||
{
|
{
|
||||||
$log = (object) [
|
$log = (object) [
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package MokoSuiteCross
|
||||||
|
* @subpackage com_mokosuitecross
|
||||||
|
* @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\MokoSuiteCross\Administrator\Service;
|
||||||
|
|
||||||
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional interface for service plugins that support deleting posts
|
||||||
|
* from the remote platform.
|
||||||
|
*
|
||||||
|
* Plugins that implement this can be invoked when a Joomla article
|
||||||
|
* is unpublished or trashed, or when a user manually requests deletion
|
||||||
|
* from the Post Queue view.
|
||||||
|
*/
|
||||||
|
interface MokoSuiteCrossDeleteInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Delete a previously published post from the remote platform.
|
||||||
|
*
|
||||||
|
* @param string $platformPostId The platform-specific post ID
|
||||||
|
* @param array $credentials Decrypted credentials for this service
|
||||||
|
*
|
||||||
|
* @return array ['success' => bool, 'message' => string]
|
||||||
|
*/
|
||||||
|
public function deletePost(string $platformPostId, array $credentials): array;
|
||||||
|
}
|
||||||
+20
@@ -11,3 +11,23 @@ PLG_CONTENT_MOKOSUITECROSS_EVERGREEN_DESC="Automatically re-share this article o
|
|||||||
PLG_CONTENT_MOKOSUITECROSS_EVERGREEN_INTERVAL="Re-share Interval (days)"
|
PLG_CONTENT_MOKOSUITECROSS_EVERGREEN_INTERVAL="Re-share Interval (days)"
|
||||||
PLG_CONTENT_MOKOSUITECROSS_EVERGREEN_INTERVAL_DESC="How many days to wait between automatic re-shares. Default: 30 days."
|
PLG_CONTENT_MOKOSUITECROSS_EVERGREEN_INTERVAL_DESC="How many days to wait between automatic re-shares. Default: 30 days."
|
||||||
PLG_CONTENT_MOKOSUITECROSS_HISTORY="Cross-Post History"
|
PLG_CONTENT_MOKOSUITECROSS_HISTORY="Cross-Post History"
|
||||||
|
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_FIELDSET_SHARE="Share Content"
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_SOCIAL_TEXT="Social Media Text"
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_SOCIAL_TEXT_DESC="Custom text for Facebook, LinkedIn, Threads. Use {social} placeholder in templates. Falls back to intro text if empty."
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_SHORT_TEXT="Short Text (Twitter/Bluesky)"
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_SHORT_TEXT_DESC="Optimized text for character-limited platforms (Twitter 280, Bluesky 300). Use {short} placeholder. Falls back to truncated title."
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_CHAT_TEXT="Chat Text"
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_CHAT_TEXT_DESC="Custom text for Telegram, Discord, Slack, Teams. Use {chat} placeholder. Falls back to intro text."
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_EMAIL_SUBJECT="Email Subject"
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_EMAIL_SUBJECT_DESC="Subject line for Mailchimp, SendGrid, Brevo campaigns. Use {email_subject} placeholder. Falls back to article title."
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_EMAIL_BODY="Email Body"
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_EMAIL_BODY_DESC="HTML content for email campaigns. Use {email_body} placeholder. Falls back to full article text."
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_SHARE_IMAGE="Share Image"
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_SHARE_IMAGE_DESC="Which image to use when cross-posting this article."
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_SHARE_IMAGE_INTRO="Intro Image"
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_SHARE_IMAGE_FULLTEXT="Full Text Image"
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_SHARE_IMAGE_CUSTOM="Custom Image"
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_SHARE_IMAGE_NONE="No Image"
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_CUSTOM_IMAGE="Custom Share Image"
|
||||||
|
PLG_CONTENT_MOKOSUITECROSS_CUSTOM_IMAGE_DESC="Select an image from the media manager to use for cross-posting."
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="content" method="upgrade">
|
<extension type="plugin" group="content" method="upgrade">
|
||||||
<name>Content - MokoSuiteCross</name>
|
<name>Content - MokoSuiteCross</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -140,6 +140,71 @@ class MokoSuiteCrossContent extends CMSPlugin implements SubscriberInterface
|
|||||||
showon="mokosuitecross_skip:0[AND]mokosuitecross_evergreen:1"
|
showon="mokosuitecross_skip:0[AND]mokosuitecross_evergreen:1"
|
||||||
/>
|
/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<fieldset name="mokosuitecross_share" label="PLG_CONTENT_MOKOSUITECROSS_FIELDSET_SHARE">
|
||||||
|
<field
|
||||||
|
name="mokosuitecross_social_text"
|
||||||
|
type="textarea"
|
||||||
|
label="PLG_CONTENT_MOKOSUITECROSS_SOCIAL_TEXT"
|
||||||
|
description="PLG_CONTENT_MOKOSUITECROSS_SOCIAL_TEXT_DESC"
|
||||||
|
rows="3"
|
||||||
|
hint="Optimized for Facebook, LinkedIn, Threads. Leave empty to use intro text."
|
||||||
|
filter="string"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="mokosuitecross_short_text"
|
||||||
|
type="textarea"
|
||||||
|
label="PLG_CONTENT_MOKOSUITECROSS_SHORT_TEXT"
|
||||||
|
description="PLG_CONTENT_MOKOSUITECROSS_SHORT_TEXT_DESC"
|
||||||
|
rows="2"
|
||||||
|
hint="For Twitter (280), Bluesky (300). Leave empty for auto-truncated title."
|
||||||
|
filter="string"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="mokosuitecross_chat_text"
|
||||||
|
type="textarea"
|
||||||
|
label="PLG_CONTENT_MOKOSUITECROSS_CHAT_TEXT"
|
||||||
|
description="PLG_CONTENT_MOKOSUITECROSS_CHAT_TEXT_DESC"
|
||||||
|
rows="3"
|
||||||
|
hint="For Telegram, Discord, Slack, Teams. Leave empty to use intro text."
|
||||||
|
filter="string"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="mokosuitecross_email_subject"
|
||||||
|
type="text"
|
||||||
|
label="PLG_CONTENT_MOKOSUITECROSS_EMAIL_SUBJECT"
|
||||||
|
description="PLG_CONTENT_MOKOSUITECROSS_EMAIL_SUBJECT_DESC"
|
||||||
|
hint="For Mailchimp, SendGrid, Brevo. Leave empty to use article title."
|
||||||
|
filter="string"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="mokosuitecross_email_body"
|
||||||
|
type="editor"
|
||||||
|
label="PLG_CONTENT_MOKOSUITECROSS_EMAIL_BODY"
|
||||||
|
description="PLG_CONTENT_MOKOSUITECROSS_EMAIL_BODY_DESC"
|
||||||
|
filter="safehtml"
|
||||||
|
buttons="true"
|
||||||
|
hide="readmore,pagebreak"
|
||||||
|
height="200"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="mokosuitecross_share_image"
|
||||||
|
type="list"
|
||||||
|
label="PLG_CONTENT_MOKOSUITECROSS_SHARE_IMAGE"
|
||||||
|
description="PLG_CONTENT_MOKOSUITECROSS_SHARE_IMAGE_DESC"
|
||||||
|
default="intro">
|
||||||
|
<option value="intro">PLG_CONTENT_MOKOSUITECROSS_SHARE_IMAGE_INTRO</option>
|
||||||
|
<option value="fulltext">PLG_CONTENT_MOKOSUITECROSS_SHARE_IMAGE_FULLTEXT</option>
|
||||||
|
<option value="custom">PLG_CONTENT_MOKOSUITECROSS_SHARE_IMAGE_CUSTOM</option>
|
||||||
|
<option value="none">PLG_CONTENT_MOKOSUITECROSS_SHARE_IMAGE_NONE</option>
|
||||||
|
</field>
|
||||||
|
<field
|
||||||
|
name="mokosuitecross_custom_image"
|
||||||
|
type="media"
|
||||||
|
label="PLG_CONTENT_MOKOSUITECROSS_CUSTOM_IMAGE"
|
||||||
|
description="PLG_CONTENT_MOKOSUITECROSS_CUSTOM_IMAGE_DESC"
|
||||||
|
showon="mokosuitecross_share_image:custom"
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
</fields>
|
</fields>
|
||||||
</form>
|
</form>
|
||||||
XML;
|
XML;
|
||||||
@@ -325,12 +390,28 @@ XML;
|
|||||||
$value = func_get_arg(2);
|
$value = func_get_arg(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($context !== 'com_content.article' || $value !== 1) {
|
if ($context !== 'com_content.article') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$params = ComponentHelper::getParams('com_mokosuitecross');
|
$params = ComponentHelper::getParams('com_mokosuitecross');
|
||||||
|
|
||||||
|
// Unpublish/trash: delete from platforms if configured
|
||||||
|
if ($value === 0 || $value === -2) {
|
||||||
|
if ($params->get('delete_on_unpublish', 0)) {
|
||||||
|
foreach ($pks as $pk) {
|
||||||
|
CrossPostDispatcher::deleteFromPlatforms((int) $pk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish: auto-post if configured
|
||||||
|
if ($value !== 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!$params->get('auto_post_on_publish', 1)) {
|
if (!$params->get('auto_post_on_publish', 1)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - ActivityPub (Fediverse)</name>
|
<name>MokoSuiteCross - ActivityPub (Fediverse)</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Google Blogger</name>
|
<name>MokoSuiteCross - Google Blogger</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Bluesky</name>
|
<name>MokoSuiteCross - Bluesky</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace Joomla\Plugin\MokoSuiteCross\Bluesky\Extension;
|
|||||||
defined('_JEXEC') or die;
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
use Joomla\CMS\Plugin\CMSPlugin;
|
use Joomla\CMS\Plugin\CMSPlugin;
|
||||||
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossDeleteInterface;
|
||||||
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
||||||
use Joomla\Event\SubscriberInterface;
|
use Joomla\Event\SubscriberInterface;
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ use Joomla\Event\SubscriberInterface;
|
|||||||
* "pds_url": "https://bsky.social" // Optional, defaults to bsky.social
|
* "pds_url": "https://bsky.social" // Optional, defaults to bsky.social
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
class BlueskyService extends CMSPlugin implements SubscriberInterface, MokoSuiteCrossServiceInterface
|
class BlueskyService extends CMSPlugin implements SubscriberInterface, MokoSuiteCrossServiceInterface, MokoSuiteCrossDeleteInterface
|
||||||
{
|
{
|
||||||
public static function getSubscribedEvents(): array
|
public static function getSubscribedEvents(): array
|
||||||
{
|
{
|
||||||
@@ -127,7 +128,7 @@ class BlueskyService extends CMSPlugin implements SubscriberInterface, MokoSuite
|
|||||||
|
|
||||||
private function authenticateWithCache(string $pds, string $handle, string $appPwd): array
|
private function authenticateWithCache(string $pds, string $handle, string $appPwd): array
|
||||||
{
|
{
|
||||||
$cacheKey = md5($pds . $handle);
|
$cacheKey = hash('sha256', $pds . $handle);
|
||||||
|
|
||||||
if (isset(self::$sessionCache[$cacheKey])) {
|
if (isset(self::$sessionCache[$cacheKey])) {
|
||||||
$cached = self::$sessionCache[$cacheKey];
|
$cached = self::$sessionCache[$cacheKey];
|
||||||
@@ -175,6 +176,69 @@ class BlueskyService extends CMSPlugin implements SubscriberInterface, MokoSuite
|
|||||||
return json_decode($response, true) ?: [];
|
return json_decode($response, true) ?: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function deletePost(string $platformPostId, array $credentials): array
|
||||||
|
{
|
||||||
|
$pds = rtrim($credentials['pds_url'] ?? 'https://bsky.social', '/');
|
||||||
|
$handle = $credentials['handle'] ?? '';
|
||||||
|
$appPwd = $credentials['app_password'] ?? '';
|
||||||
|
|
||||||
|
if (empty($handle) || empty($appPwd)) {
|
||||||
|
return ['success' => false, 'message' => 'Missing credentials.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse AT URI: at://did:plc:xxx/app.bsky.feed.post/rkey
|
||||||
|
$parts = explode('/', $platformPostId);
|
||||||
|
$rkey = end($parts);
|
||||||
|
|
||||||
|
if (empty($rkey)) {
|
||||||
|
return ['success' => false, 'message' => 'Invalid AT URI -- could not extract rkey.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate (uses cached session if still valid)
|
||||||
|
$authData = $this->authenticateWithCache($pds, $handle, $appPwd);
|
||||||
|
|
||||||
|
if (empty($authData['accessJwt'])) {
|
||||||
|
return ['success' => false, 'message' => 'Authentication failed.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$postData = json_encode([
|
||||||
|
'repo' => $authData['did'],
|
||||||
|
'collection' => 'app.bsky.feed.post',
|
||||||
|
'rkey' => $rkey,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$ch = curl_init($pds . '/xrpc/com.atproto.repo.deleteRecord');
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_POSTFIELDS => $postData,
|
||||||
|
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $authData['accessJwt'], 'Content-Type: application/json'],
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP,
|
||||||
|
CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => 'Connection error: ' . $curlError];
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode === 200) {
|
||||||
|
return ['success' => true, 'message' => 'Post deleted successfully.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($response, true) ?: [];
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => $data['message'] ?? 'Delete failed with HTTP ' . $httpCode];
|
||||||
|
}
|
||||||
|
|
||||||
public function getSupportedMediaTypes(): array
|
public function getSupportedMediaTypes(): array
|
||||||
{
|
{
|
||||||
return ['image'];
|
return ['image'];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Brevo (Sendinblue)</name>
|
<name>MokoSuiteCross - Brevo (Sendinblue)</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -70,15 +70,6 @@ class BrevoService extends CMSPlugin implements SubscriberInterface, MokoSuiteCr
|
|||||||
CURLOPT_TIMEOUT => 30,
|
CURLOPT_TIMEOUT => 30,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
curl_setopt_array($ch, [
|
|
||||||
CURLOPT_URL => 'https://api.brevo.com/v3/emailCampaigns',
|
|
||||||
CURLOPT_POST => true,
|
|
||||||
CURLOPT_POSTFIELDS => $postData,
|
|
||||||
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, 'Content-Type: application/json'],
|
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
|
||||||
CURLOPT_TIMEOUT => 30,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response = curl_exec($ch);
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
if ($response === false) {
|
if ($response === false) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Constant Contact</name>
|
<name>MokoSuiteCross - Constant Contact</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
-9
@@ -73,15 +73,6 @@ class ConstantcontactService extends CMSPlugin implements SubscriberInterface, M
|
|||||||
CURLOPT_TIMEOUT => 30,
|
CURLOPT_TIMEOUT => 30,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
curl_setopt_array($ch, [
|
|
||||||
CURLOPT_URL => 'https://api.cc.email/v3/emails',
|
|
||||||
CURLOPT_POST => true,
|
|
||||||
CURLOPT_POSTFIELDS => $postData,
|
|
||||||
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, 'Content-Type: application/json'],
|
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
|
||||||
CURLOPT_TIMEOUT => 30,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response = curl_exec($ch);
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
if ($response === false) {
|
if ($response === false) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - ConvertKit</name>
|
<name>MokoSuiteCross - ConvertKit</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -66,15 +66,6 @@ class ConvertkitService extends CMSPlugin implements SubscriberInterface, MokoSu
|
|||||||
CURLOPT_TIMEOUT => 30,
|
CURLOPT_TIMEOUT => 30,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
curl_setopt_array($ch, [
|
|
||||||
CURLOPT_URL => 'https://api.convertkit.com/v3/broadcasts',
|
|
||||||
CURLOPT_POST => true,
|
|
||||||
CURLOPT_POSTFIELDS => $postData,
|
|
||||||
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, 'Content-Type: application/json'],
|
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
|
||||||
CURLOPT_TIMEOUT => 30,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response = curl_exec($ch);
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
if ($response === false) {
|
if ($response === false) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Dev.to</name>
|
<name>MokoSuiteCross - Dev.to</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Discord</name>
|
<name>MokoSuiteCross - Discord</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace Joomla\Plugin\MokoSuiteCross\Discord\Extension;
|
|||||||
defined('_JEXEC') or die;
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
use Joomla\CMS\Plugin\CMSPlugin;
|
use Joomla\CMS\Plugin\CMSPlugin;
|
||||||
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossDeleteInterface;
|
||||||
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
||||||
use Joomla\Event\SubscriberInterface;
|
use Joomla\Event\SubscriberInterface;
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ use Joomla\Event\SubscriberInterface;
|
|||||||
* "webhook_url": "https://discord.com/api/webhooks/..." // Only for custom mode
|
* "webhook_url": "https://discord.com/api/webhooks/..." // Only for custom mode
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
class DiscordService extends CMSPlugin implements SubscriberInterface, MokoSuiteCrossServiceInterface
|
class DiscordService extends CMSPlugin implements SubscriberInterface, MokoSuiteCrossServiceInterface, MokoSuiteCrossDeleteInterface
|
||||||
{
|
{
|
||||||
public static function getSubscribedEvents(): array
|
public static function getSubscribedEvents(): array
|
||||||
{
|
{
|
||||||
@@ -126,6 +127,44 @@ class DiscordService extends CMSPlugin implements SubscriberInterface, MokoSuite
|
|||||||
return $this->params->get('default_webhook_url', '');
|
return $this->params->get('default_webhook_url', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function deletePost(string $platformPostId, array $credentials): array
|
||||||
|
{
|
||||||
|
$webhookUrl = $this->resolveWebhook($credentials);
|
||||||
|
|
||||||
|
if (empty($webhookUrl)) {
|
||||||
|
return ['success' => false, 'message' => 'Missing webhook URL.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$apiUrl = $webhookUrl . '/messages/' . $platformPostId;
|
||||||
|
|
||||||
|
$ch = curl_init($apiUrl);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_CUSTOMREQUEST => 'DELETE',
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => 'Connection error: ' . $curlError];
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode === 204) {
|
||||||
|
return ['success' => true, 'message' => 'Message deleted successfully.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($response, true) ?: [];
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => $data['message'] ?? 'Delete failed (HTTP ' . $httpCode . ').'];
|
||||||
|
}
|
||||||
|
|
||||||
public function getSupportedMediaTypes(): array
|
public function getSupportedMediaTypes(): array
|
||||||
{
|
{
|
||||||
return ['image', 'video'];
|
return ['image', 'video'];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Facebook / Meta</name>
|
<name>MokoSuiteCross - Facebook / Meta</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace Joomla\Plugin\MokoSuiteCross\Facebook\Extension;
|
|||||||
defined('_JEXEC') or die;
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
use Joomla\CMS\Plugin\CMSPlugin;
|
use Joomla\CMS\Plugin\CMSPlugin;
|
||||||
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossDeleteInterface;
|
||||||
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
||||||
use Joomla\Event\SubscriberInterface;
|
use Joomla\Event\SubscriberInterface;
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ use Joomla\Event\SubscriberInterface;
|
|||||||
* "page_id": "..." // Required — Facebook Page ID
|
* "page_id": "..." // Required — Facebook Page ID
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
class FacebookService extends CMSPlugin implements SubscriberInterface, MokoSuiteCrossServiceInterface
|
class FacebookService extends CMSPlugin implements SubscriberInterface, MokoSuiteCrossServiceInterface, MokoSuiteCrossDeleteInterface
|
||||||
{
|
{
|
||||||
public static function getSubscribedEvents(): array
|
public static function getSubscribedEvents(): array
|
||||||
{
|
{
|
||||||
@@ -161,6 +162,44 @@ class FacebookService extends CMSPlugin implements SubscriberInterface, MokoSuit
|
|||||||
return $this->params->get('default_page_access_token', '');
|
return $this->params->get('default_page_access_token', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function deletePost(string $platformPostId, array $credentials): array
|
||||||
|
{
|
||||||
|
$token = $this->resolveToken($credentials);
|
||||||
|
|
||||||
|
if (empty($token)) {
|
||||||
|
return ['success' => false, 'message' => 'Missing access token.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$apiUrl = 'https://graph.facebook.com/v19.0/' . $platformPostId . '?access_token=' . $token;
|
||||||
|
|
||||||
|
$ch = curl_init($apiUrl);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_CUSTOMREQUEST => 'DELETE',
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => 'Connection error: ' . $curlError];
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
$data = json_decode($response, true) ?: [];
|
||||||
|
|
||||||
|
if ($httpCode === 200 && !empty($data['success'])) {
|
||||||
|
return ['success' => true, 'message' => 'Post deleted successfully.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => $data['error']['message'] ?? 'Delete failed (HTTP ' . $httpCode . ').'];
|
||||||
|
}
|
||||||
|
|
||||||
public function getSupportedMediaTypes(): array
|
public function getSupportedMediaTypes(): array
|
||||||
{
|
{
|
||||||
return ['image', 'video', 'gif'];
|
return ['image', 'video', 'gif'];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Ghost</name>
|
<name>MokoSuiteCross - Ghost</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Google Business Profile</name>
|
<name>MokoSuiteCross - Google Business Profile</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Google Chat</name>
|
<name>MokoSuiteCross - Google Chat</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Hashnode</name>
|
<name>MokoSuiteCross - Hashnode</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package MokoSuiteCross
|
||||||
|
* @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;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
|
<name>MokoSuiteCross - Instagram</name>
|
||||||
|
<version>01.04.09-dev</version>
|
||||||
|
<creationDate>2026-06-23</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>PLG_MOKOSUITECROSS_INSTAGRAM_DESCRIPTION</description>
|
||||||
|
|
||||||
|
<namespace path="src">Joomla\Plugin\MokoSuiteCross\Instagram</namespace>
|
||||||
|
|
||||||
|
<files>
|
||||||
|
<filename plugin="instagram">instagram.php</filename>
|
||||||
|
<folder>src</folder>
|
||||||
|
<folder>services</folder>
|
||||||
|
<folder>language</folder>
|
||||||
|
</files>
|
||||||
|
|
||||||
|
<languages>
|
||||||
|
<language tag="en-GB">language/en-GB/plg_mokosuitecross_instagram.ini</language>
|
||||||
|
<language tag="en-GB">language/en-GB/plg_mokosuitecross_instagram.sys.ini</language>
|
||||||
|
</languages>
|
||||||
|
<config>
|
||||||
|
<fields name="params">
|
||||||
|
<fieldset name="basic" label="PLG_MOKOSUITECROSS_INSTAGRAM_FIELDSET_DEFAULTS">
|
||||||
|
<field
|
||||||
|
name="default_webhook_url"
|
||||||
|
type="url"
|
||||||
|
label="PLG_MOKOSUITECROSS_INSTAGRAM_DEFAULT_WEBHOOK"
|
||||||
|
description="PLG_MOKOSUITECROSS_INSTAGRAM_DEFAULT_WEBHOOK_DESC"
|
||||||
|
size="60"
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
</fields>
|
||||||
|
</config>
|
||||||
|
</extension>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
PLG_MOKOSUITECROSS_INSTAGRAM="MokoSuiteCross - Instagram"
|
||||||
|
PLG_MOKOSUITECROSS_INSTAGRAM_DESCRIPTION="Cross-post Joomla articles to Instagram via Meta Content Publishing API."
|
||||||
|
PLG_MOKOSUITECROSS_INSTAGRAM_FIELDSET_DEFAULTS="Default Settings"
|
||||||
|
PLG_MOKOSUITECROSS_INSTAGRAM_DEFAULT_WEBHOOK="Default Webhook URL"
|
||||||
|
PLG_MOKOSUITECROSS_INSTAGRAM_DEFAULT_WEBHOOK_DESC="Pre-configured MokoSuite webhook URL. Services using default mode will use this URL."
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
PLG_MOKOSUITECROSS_INSTAGRAM="MokoSuiteCross - Instagram"
|
||||||
|
PLG_MOKOSUITECROSS_INSTAGRAM_DESCRIPTION="Cross-post Joomla articles to Instagram via Meta Content Publishing API."
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package MokoSuiteCross
|
||||||
|
* @subpackage plg_mokosuitecross_instagram
|
||||||
|
* @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\Extension\PluginInterface;
|
||||||
|
use Joomla\CMS\Factory;
|
||||||
|
use Joomla\CMS\Plugin\PluginHelper;
|
||||||
|
use Joomla\DI\Container;
|
||||||
|
use Joomla\DI\ServiceProviderInterface;
|
||||||
|
use Joomla\Event\DispatcherInterface;
|
||||||
|
use Joomla\Plugin\MokoSuiteCross\Instagram\Extension\InstagramService;
|
||||||
|
|
||||||
|
return new class () implements ServiceProviderInterface {
|
||||||
|
public function register(Container $container): void
|
||||||
|
{
|
||||||
|
$container->set(
|
||||||
|
PluginInterface::class,
|
||||||
|
function (Container $container) {
|
||||||
|
$plugin = new InstagramService(
|
||||||
|
$container->get(DispatcherInterface::class),
|
||||||
|
(array) PluginHelper::getPlugin('mokosuitecross', 'instagram')
|
||||||
|
);
|
||||||
|
$plugin->setApplication(Factory::getApplication());
|
||||||
|
|
||||||
|
return $plugin;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package MokoSuiteCross
|
||||||
|
* @subpackage plg_mokosuitecross_instagram
|
||||||
|
* @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\MokoSuiteCross\Instagram\Extension;
|
||||||
|
|
||||||
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
|
use Joomla\CMS\Plugin\CMSPlugin;
|
||||||
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
||||||
|
use Joomla\Event\SubscriberInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instagram service plugin for MokoSuiteCross.
|
||||||
|
*
|
||||||
|
* Uses the Meta Content Publishing API — a 2-step flow:
|
||||||
|
* 1. Create a media container via POST /{ig_user_id}/media
|
||||||
|
* 2. Publish the container via POST /{ig_user_id}/media_publish
|
||||||
|
*/
|
||||||
|
class InstagramService extends CMSPlugin implements SubscriberInterface, MokoSuiteCrossServiceInterface
|
||||||
|
{
|
||||||
|
public static function getSubscribedEvents(): array
|
||||||
|
{
|
||||||
|
return ['onMokoSuiteCrossGetServices' => 'onMokoSuiteCrossGetServices'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onMokoSuiteCrossGetServices(&$services): void
|
||||||
|
{
|
||||||
|
$services[] = $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getServiceType(): string { return 'instagram'; }
|
||||||
|
public function getServiceName(): string { return 'Instagram'; }
|
||||||
|
public function getMaxLength(): int { return 2200; }
|
||||||
|
public function supportsMedia(): bool { return true; }
|
||||||
|
|
||||||
|
public function publish(string $message, array $media, array $credentials, array $params): array
|
||||||
|
{
|
||||||
|
$token = $this->resolveCredential($credentials, 'access_token');
|
||||||
|
$accountId = $credentials['instagram_account_id'] ?? '';
|
||||||
|
|
||||||
|
if (empty($token) || empty($accountId)) {
|
||||||
|
return ['success' => false, 'platform_post_id' => '', 'response' => ['error' => 'Missing access token or Instagram account ID.']];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Create media container
|
||||||
|
$containerUrl = 'https://graph.facebook.com/v19.0/' . urlencode($accountId) . '/media';
|
||||||
|
$containerData = [
|
||||||
|
'caption' => mb_substr($message, 0, 2200),
|
||||||
|
'access_token' => $token,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Attach image if provided
|
||||||
|
if (!empty($media[0])) {
|
||||||
|
$containerData['image_url'] = $media[0];
|
||||||
|
} else {
|
||||||
|
return ['success' => false, 'platform_post_id' => '', 'response' => ['error' => 'Instagram requires at least one image or video.']];
|
||||||
|
}
|
||||||
|
|
||||||
|
$ch = curl_init($containerUrl);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_POSTFIELDS => http_build_query($containerData),
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return ['success' => false, 'platform_post_id' => '', 'response' => ['error' => 'Connection error: ' . $curlError]];
|
||||||
|
|
||||||
|
}
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
$data = json_decode($response, true) ?: [];
|
||||||
|
|
||||||
|
if ($httpCode < 200 || $httpCode >= 300 || empty($data['id'])) {
|
||||||
|
return ['success' => false, 'platform_post_id' => '', 'response' => $data];
|
||||||
|
}
|
||||||
|
|
||||||
|
$containerId = $data['id'];
|
||||||
|
|
||||||
|
// Step 2: Publish the container
|
||||||
|
$publishUrl = 'https://graph.facebook.com/v19.0/' . urlencode($accountId) . '/media_publish';
|
||||||
|
$publishData = [
|
||||||
|
'creation_id' => $containerId,
|
||||||
|
'access_token' => $token,
|
||||||
|
];
|
||||||
|
|
||||||
|
$ch = curl_init($publishUrl);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_POSTFIELDS => http_build_query($publishData),
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return ['success' => false, 'platform_post_id' => '', 'response' => ['error' => 'Connection error: ' . $curlError]];
|
||||||
|
|
||||||
|
}
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
$data = json_decode($response, true) ?: [];
|
||||||
|
|
||||||
|
if ($httpCode >= 200 && $httpCode < 300 && !empty($data['id'])) {
|
||||||
|
return ['success' => true, 'platform_post_id' => (string) $data['id'], 'response' => $data];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['success' => false, 'platform_post_id' => '', 'response' => $data];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateCredentials(array $credentials): array
|
||||||
|
{
|
||||||
|
$token = $this->resolveCredential($credentials, 'access_token');
|
||||||
|
$accountId = $credentials['instagram_account_id'] ?? '';
|
||||||
|
|
||||||
|
if (empty($token) || empty($accountId)) {
|
||||||
|
return ['valid' => false, 'message' => 'Access token and Instagram account ID are required.', 'account_name' => ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
$ch = curl_init('https://graph.facebook.com/v19.0/me?fields=id,username&access_token=' . urlencode($token));
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 10,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return ['valid' => false, 'message' => 'Connection error: ' . $curlError, 'account_name' => ''];
|
||||||
|
|
||||||
|
}
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
$data = json_decode($response, true) ?: [];
|
||||||
|
|
||||||
|
if (!empty($data['id'])) {
|
||||||
|
$name = $data['username'] ?? $data['id'];
|
||||||
|
return ['valid' => true, 'message' => 'Connected', 'account_name' => '@' . $name];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['valid' => false, 'message' => $data['error']['message'] ?? 'Failed to verify credentials.', 'account_name' => ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveCredential(array $credentials, string $key): string
|
||||||
|
{
|
||||||
|
$mode = $credentials['mode'] ?? 'default';
|
||||||
|
|
||||||
|
if ($mode === 'custom') {
|
||||||
|
return $credentials[$key] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->params->get('default_' . $key, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSupportedMediaTypes(): array
|
||||||
|
{
|
||||||
|
return ['image', 'video'];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - LinkedIn</name>
|
<name>MokoSuiteCross - LinkedIn</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace Joomla\Plugin\MokoSuiteCross\Linkedin\Extension;
|
|||||||
defined('_JEXEC') or die;
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
use Joomla\CMS\Plugin\CMSPlugin;
|
use Joomla\CMS\Plugin\CMSPlugin;
|
||||||
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossDeleteInterface;
|
||||||
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
||||||
use Joomla\Event\SubscriberInterface;
|
use Joomla\Event\SubscriberInterface;
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ use Joomla\Event\SubscriberInterface;
|
|||||||
* "person_id": "..." // LinkedIn Person URN (fallback)
|
* "person_id": "..." // LinkedIn Person URN (fallback)
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
class LinkedinService extends CMSPlugin implements SubscriberInterface, MokoSuiteCrossServiceInterface
|
class LinkedinService extends CMSPlugin implements SubscriberInterface, MokoSuiteCrossServiceInterface, MokoSuiteCrossDeleteInterface
|
||||||
{
|
{
|
||||||
public static function getSubscribedEvents(): array
|
public static function getSubscribedEvents(): array
|
||||||
{
|
{
|
||||||
@@ -147,6 +148,46 @@ class LinkedinService extends CMSPlugin implements SubscriberInterface, MokoSuit
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function deletePost(string $platformPostId, array $credentials): array
|
||||||
|
{
|
||||||
|
$token = $credentials['access_token'] ?? '';
|
||||||
|
|
||||||
|
if (empty($token)) {
|
||||||
|
return ['success' => false, 'message' => 'Missing access token.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$encodedId = urlencode($platformPostId);
|
||||||
|
$apiUrl = 'https://api.linkedin.com/v2/ugcPosts/' . $encodedId;
|
||||||
|
|
||||||
|
$ch = curl_init($apiUrl);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_CUSTOMREQUEST => 'DELETE',
|
||||||
|
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => 'Connection error: ' . $curlError];
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode === 204) {
|
||||||
|
return ['success' => true, 'message' => 'Post deleted successfully.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($response, true) ?: [];
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => $data['message'] ?? 'Delete failed (HTTP ' . $httpCode . ').'];
|
||||||
|
}
|
||||||
|
|
||||||
public function getSupportedMediaTypes(): array
|
public function getSupportedMediaTypes(): array
|
||||||
{
|
{
|
||||||
return ['image', 'video'];
|
return ['image', 'video'];
|
||||||
|
|||||||
+5
@@ -7,3 +7,8 @@ PLG_MOKOSUITECROSS_MAILCHIMP_DEFAULT_FROM_EMAIL="Default From Email"
|
|||||||
PLG_MOKOSUITECROSS_MAILCHIMP_DEFAULT_FROM_EMAIL_DESC="Default sender email address for Mailchimp campaigns."
|
PLG_MOKOSUITECROSS_MAILCHIMP_DEFAULT_FROM_EMAIL_DESC="Default sender email address for Mailchimp campaigns."
|
||||||
PLG_MOKOSUITECROSS_MAILCHIMP_AUTO_SEND="Auto Send"
|
PLG_MOKOSUITECROSS_MAILCHIMP_AUTO_SEND="Auto Send"
|
||||||
PLG_MOKOSUITECROSS_MAILCHIMP_AUTO_SEND_DESC="Automatically send the campaign on creation instead of saving as draft."
|
PLG_MOKOSUITECROSS_MAILCHIMP_AUTO_SEND_DESC="Automatically send the campaign on creation instead of saving as draft."
|
||||||
|
PLG_MOKOSUITECROSS_MAILCHIMP_FIELDSET_TEMPLATE="Email Template"
|
||||||
|
PLG_MOKOSUITECROSS_MAILCHIMP_TEMPLATE_ID="Mailchimp Template ID"
|
||||||
|
PLG_MOKOSUITECROSS_MAILCHIMP_TEMPLATE_ID_DESC="Numeric ID of a saved Mailchimp template. Article content is injected into the template section. Leave empty to use the built-in responsive wrapper."
|
||||||
|
PLG_MOKOSUITECROSS_MAILCHIMP_TEMPLATE_SECTION="Template Section Name"
|
||||||
|
PLG_MOKOSUITECROSS_MAILCHIMP_TEMPLATE_SECTION_DESC="The editable section name in your Mailchimp template where article content is injected. Default: body_content."
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Mailchimp</name>
|
<name>MokoSuiteCross - Mailchimp</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<license>GPL-3.0-or-later</license>
|
<license>GPL-3.0-or-later</license>
|
||||||
<description>PLG_MOKOSUITECROSS_MAILCHIMP_DESCRIPTION</description>
|
<description>PLG_MOKOSUITECROSS_MAILCHIMP_DESCRIPTION</description>
|
||||||
|
|
||||||
<namespace path="src">Joomla\Plugin\MokoSuiteCross${CLASS_NAME}</namespace>
|
<namespace path="src">Joomla\Plugin\MokoSuiteCross\Mailchimp</namespace>
|
||||||
|
|
||||||
<files>
|
<files>
|
||||||
<filename plugin="mailchimp">mailchimp.php</filename>
|
<filename plugin="mailchimp">mailchimp.php</filename>
|
||||||
@@ -51,6 +51,24 @@
|
|||||||
<option value="1">JYES</option>
|
<option value="1">JYES</option>
|
||||||
</field>
|
</field>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<fieldset name="template" label="PLG_MOKOSUITECROSS_MAILCHIMP_FIELDSET_TEMPLATE">
|
||||||
|
<field
|
||||||
|
name="template_id"
|
||||||
|
type="text"
|
||||||
|
label="PLG_MOKOSUITECROSS_MAILCHIMP_TEMPLATE_ID"
|
||||||
|
description="PLG_MOKOSUITECROSS_MAILCHIMP_TEMPLATE_ID_DESC"
|
||||||
|
hint="Leave empty for built-in responsive template"
|
||||||
|
filter="integer"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="template_section"
|
||||||
|
type="text"
|
||||||
|
label="PLG_MOKOSUITECROSS_MAILCHIMP_TEMPLATE_SECTION"
|
||||||
|
description="PLG_MOKOSUITECROSS_MAILCHIMP_TEMPLATE_SECTION_DESC"
|
||||||
|
default="body_content"
|
||||||
|
showon="template_id!:"
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
</fields>
|
</fields>
|
||||||
</config>
|
</config>
|
||||||
</extension>
|
</extension>
|
||||||
|
|||||||
@@ -95,14 +95,30 @@ class MailchimpService extends CMSPlugin implements SubscriberInterface, MokoSui
|
|||||||
|
|
||||||
$data = json_decode($response, true) ?: [];
|
$data = json_decode($response, true) ?: [];
|
||||||
|
|
||||||
if ($httpCode !== 200 || empty($data['id'])) {
|
if ($httpCode < 200 || $httpCode >= 300 || empty($data['id'])) {
|
||||||
return ['success' => false, 'platform_post_id' => '', 'response' => $data];
|
return ['success' => false, 'platform_post_id' => '', 'response' => $data];
|
||||||
}
|
}
|
||||||
|
|
||||||
$campaignId = $data['id'];
|
$campaignId = $data['id'];
|
||||||
|
|
||||||
// Set campaign content (HTML)
|
// Set campaign content — template injection or responsive wrapper
|
||||||
$contentData = json_encode(['html' => $message]);
|
$templateId = (int) $this->params->get('template_id', 0);
|
||||||
|
$templateSection = $this->params->get('template_section', 'body_content');
|
||||||
|
|
||||||
|
if ($templateId > 0) {
|
||||||
|
// Inject article content into a saved Mailchimp template section
|
||||||
|
$contentData = json_encode([
|
||||||
|
'template' => [
|
||||||
|
'id' => $templateId,
|
||||||
|
'sections' => [
|
||||||
|
$templateSection => $message,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// Wrap in responsive email skeleton
|
||||||
|
$contentData = json_encode(['html' => $this->wrapEmailHtml($message)]);
|
||||||
|
}
|
||||||
|
|
||||||
$ch = curl_init("https://{$dc}.api.mailchimp.com/3.0/campaigns/{$campaignId}/content");
|
$ch = curl_init("https://{$dc}.api.mailchimp.com/3.0/campaigns/{$campaignId}/content");
|
||||||
curl_setopt_array($ch, [
|
curl_setopt_array($ch, [
|
||||||
@@ -185,6 +201,27 @@ class MailchimpService extends CMSPlugin implements SubscriberInterface, MokoSui
|
|||||||
return end($parts) ?: 'us1';
|
return end($parts) ?: 'us1';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap content in a responsive email HTML skeleton.
|
||||||
|
* Used when no Mailchimp template ID is configured.
|
||||||
|
*/
|
||||||
|
private function wrapEmailHtml(string $content): string
|
||||||
|
{
|
||||||
|
return '<!DOCTYPE html>'
|
||||||
|
. '<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"></head>'
|
||||||
|
. '<body style="margin:0;padding:0;background-color:#f4f4f4;font-family:Arial,Helvetica,sans-serif;">'
|
||||||
|
. '<table width="100%" cellpadding="0" cellspacing="0" role="presentation" style="background-color:#f4f4f4;">'
|
||||||
|
. '<tr><td align="center" style="padding:20px 0;">'
|
||||||
|
. '<table width="600" cellpadding="0" cellspacing="0" role="presentation" style="background-color:#ffffff;border-radius:4px;">'
|
||||||
|
. '<tr><td style="padding:30px 40px;">'
|
||||||
|
. $content
|
||||||
|
. '</td></tr>'
|
||||||
|
. '</table>'
|
||||||
|
. '</td></tr>'
|
||||||
|
. '</table>'
|
||||||
|
. '</body></html>';
|
||||||
|
}
|
||||||
|
|
||||||
public function getSupportedMediaTypes(): array
|
public function getSupportedMediaTypes(): array
|
||||||
{
|
{
|
||||||
return ['image'];
|
return ['image'];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Mastodon</name>
|
<name>MokoSuiteCross - Mastodon</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace Joomla\Plugin\MokoSuiteCross\Mastodon\Extension;
|
|||||||
defined('_JEXEC') or die;
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
use Joomla\CMS\Plugin\CMSPlugin;
|
use Joomla\CMS\Plugin\CMSPlugin;
|
||||||
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossDeleteInterface;
|
||||||
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
||||||
use Joomla\Event\SubscriberInterface;
|
use Joomla\Event\SubscriberInterface;
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ use Joomla\Event\SubscriberInterface;
|
|||||||
* "access_token": "..."
|
* "access_token": "..."
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
class MastodonService extends CMSPlugin implements SubscriberInterface, MokoSuiteCrossServiceInterface
|
class MastodonService extends CMSPlugin implements SubscriberInterface, MokoSuiteCrossServiceInterface, MokoSuiteCrossDeleteInterface
|
||||||
{
|
{
|
||||||
public static function getSubscribedEvents(): array
|
public static function getSubscribedEvents(): array
|
||||||
{
|
{
|
||||||
@@ -120,6 +121,46 @@ class MastodonService extends CMSPlugin implements SubscriberInterface, MokoSuit
|
|||||||
return ['valid' => false, 'message' => 'Failed', 'account_name' => ''];
|
return ['valid' => false, 'message' => 'Failed', 'account_name' => ''];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function deletePost(string $platformPostId, array $credentials): array
|
||||||
|
{
|
||||||
|
$instance = rtrim($credentials['instance_url'] ?? '', '/');
|
||||||
|
$token = $credentials['access_token'] ?? '';
|
||||||
|
|
||||||
|
if (empty($instance) || empty($token)) {
|
||||||
|
return ['success' => false, 'message' => 'Missing credentials.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$ch = curl_init($instance . '/api/v1/statuses/' . $platformPostId);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_CUSTOMREQUEST => 'DELETE',
|
||||||
|
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP,
|
||||||
|
CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => 'Connection error: ' . $curlError];
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode === 200) {
|
||||||
|
return ['success' => true, 'message' => 'Status deleted successfully.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($response, true) ?: [];
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => $data['error'] ?? 'Delete failed with HTTP ' . $httpCode];
|
||||||
|
}
|
||||||
|
|
||||||
public function getSupportedMediaTypes(): array
|
public function getSupportedMediaTypes(): array
|
||||||
{
|
{
|
||||||
return ['image', 'video', 'gif'];
|
return ['image', 'video', 'gif'];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Matrix / Element</name>
|
<name>MokoSuiteCross - Matrix / Element</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Medium</name>
|
<name>MokoSuiteCross - Medium</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ class MediumService extends CMSPlugin implements SubscriberInterface, MokoSuiteC
|
|||||||
|
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
|
||||||
return ['valid' => false, 'message' => 'Connection error: ' . $curlError, 'account_name' => ''];
|
return '';
|
||||||
|
|
||||||
}
|
}
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - MokoSuiteCalendar Events</name>
|
<name>MokoSuiteCross - MokoSuiteCalendar Events</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - MokoSuiteGallery</name>
|
<name>MokoSuiteCross - MokoSuiteGallery</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Nostr</name>
|
<name>MokoSuiteCross - Nostr</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Ntfy Push Notifications</name>
|
<name>MokoSuiteCross - Ntfy Push Notifications</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Pinterest</name>
|
<name>MokoSuiteCross - Pinterest</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Reddit</name>
|
<name>MokoSuiteCross - Reddit</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - RSS Feed</name>
|
<name>MokoSuiteCross - RSS Feed</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - SendGrid</name>
|
<name>MokoSuiteCross - SendGrid</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Slack</name>
|
<name>MokoSuiteCross - Slack</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Microsoft Teams</name>
|
<name>MokoSuiteCross - Microsoft Teams</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace Joomla\Plugin\MokoSuiteCross\Telegram\Extension;
|
|||||||
defined('_JEXEC') or die;
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
use Joomla\CMS\Plugin\CMSPlugin;
|
use Joomla\CMS\Plugin\CMSPlugin;
|
||||||
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossDeleteInterface;
|
||||||
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
||||||
use Joomla\Event\SubscriberInterface;
|
use Joomla\Event\SubscriberInterface;
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ use Joomla\Event\SubscriberInterface;
|
|||||||
* "chat_id": "-100xxxxxxx" // Required — channel/group/user chat ID
|
* "chat_id": "-100xxxxxxx" // Required — channel/group/user chat ID
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
class TelegramService extends CMSPlugin implements SubscriberInterface, MokoSuiteCrossServiceInterface
|
class TelegramService extends CMSPlugin implements SubscriberInterface, MokoSuiteCrossServiceInterface, MokoSuiteCrossDeleteInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Default MokoSuite Bot token — resolved at runtime from component params.
|
* Default MokoSuite Bot token — resolved at runtime from component params.
|
||||||
@@ -222,6 +223,52 @@ class TelegramService extends CMSPlugin implements SubscriberInterface, MokoSuit
|
|||||||
return $r;
|
return $r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function deletePost(string $platformPostId, array $credentials): array
|
||||||
|
{
|
||||||
|
$botToken = $this->resolveBotToken($credentials);
|
||||||
|
$chatId = $credentials['chat_id'] ?? '';
|
||||||
|
|
||||||
|
if (empty($botToken) || empty($chatId)) {
|
||||||
|
return ['success' => false, 'message' => 'Missing bot token or chat_id.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$apiUrl = 'https://api.telegram.org/bot' . $botToken . '/deleteMessage';
|
||||||
|
|
||||||
|
$postData = json_encode([
|
||||||
|
'chat_id' => $chatId,
|
||||||
|
'message_id' => (int) $platformPostId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$ch = curl_init($apiUrl);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_POSTFIELDS => $postData,
|
||||||
|
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => 'Connection error: ' . $curlError];
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
$data = json_decode($response, true) ?: [];
|
||||||
|
|
||||||
|
if ($httpCode === 200 && !empty($data['ok'])) {
|
||||||
|
return ['success' => true, 'message' => 'Message deleted successfully.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => $data['description'] ?? 'Delete failed (HTTP ' . $httpCode . ').'];
|
||||||
|
}
|
||||||
|
|
||||||
public function getSupportedMediaTypes(): array
|
public function getSupportedMediaTypes(): array
|
||||||
{
|
{
|
||||||
return ['image', 'video', 'document'];
|
return ['image', 'video', 'document'];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Telegram</name>
|
<name>MokoSuiteCross - Telegram</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Threads (Meta)</name>
|
<name>MokoSuiteCross - Threads (Meta)</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - TikTok</name>
|
<name>MokoSuiteCross - TikTok</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Tumblr</name>
|
<name>MokoSuiteCross - Tumblr</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace Joomla\Plugin\MokoSuiteCross\Twitter\Extension;
|
|||||||
defined('_JEXEC') or die;
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
use Joomla\CMS\Plugin\CMSPlugin;
|
use Joomla\CMS\Plugin\CMSPlugin;
|
||||||
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossDeleteInterface;
|
||||||
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
||||||
use Joomla\Event\SubscriberInterface;
|
use Joomla\Event\SubscriberInterface;
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ use Joomla\Event\SubscriberInterface;
|
|||||||
* Bearer tokens are app-only and cannot create tweets — OAuth 1.0a
|
* Bearer tokens are app-only and cannot create tweets — OAuth 1.0a
|
||||||
* with consumer key/secret + access token/secret is required.
|
* with consumer key/secret + access token/secret is required.
|
||||||
*/
|
*/
|
||||||
class TwitterService extends CMSPlugin implements SubscriberInterface, MokoSuiteCrossServiceInterface
|
class TwitterService extends CMSPlugin implements SubscriberInterface, MokoSuiteCrossServiceInterface, MokoSuiteCrossDeleteInterface
|
||||||
{
|
{
|
||||||
public static function getSubscribedEvents(): array
|
public static function getSubscribedEvents(): array
|
||||||
{
|
{
|
||||||
@@ -203,6 +204,50 @@ class TwitterService extends CMSPlugin implements SubscriberInterface, MokoSuite
|
|||||||
return 'OAuth ' . implode(', ', $parts);
|
return 'OAuth ' . implode(', ', $parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function deletePost(string $platformPostId, array $credentials): array
|
||||||
|
{
|
||||||
|
$apiUrl = 'https://api.twitter.com/2/tweets/' . $platformPostId;
|
||||||
|
|
||||||
|
$consumerKey = $credentials['api_key'] ?? '';
|
||||||
|
$consumerSecret = $credentials['api_secret'] ?? '';
|
||||||
|
$accessToken = $credentials['access_token'] ?? '';
|
||||||
|
$tokenSecret = $credentials['access_token_secret'] ?? '';
|
||||||
|
|
||||||
|
if (!$consumerKey || !$consumerSecret || !$accessToken || !$tokenSecret) {
|
||||||
|
return ['success' => false, 'message' => 'Missing OAuth 1.0a credentials. All 4 keys are required.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$authHeader = $this->buildOAuth1Header('DELETE', $apiUrl, $consumerKey, $consumerSecret, $accessToken, $tokenSecret);
|
||||||
|
|
||||||
|
$ch = curl_init($apiUrl);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_CUSTOMREQUEST => 'DELETE',
|
||||||
|
CURLOPT_HTTPHEADER => ['Authorization: ' . $authHeader],
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => 'Connection error: ' . $curlError];
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
$data = json_decode($response, true) ?: [];
|
||||||
|
|
||||||
|
if ($httpCode === 200 && !empty($data['data']['deleted']) && $data['data']['deleted'] === true) {
|
||||||
|
return ['success' => true, 'message' => 'Tweet deleted successfully.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['success' => false, 'message' => $data['detail'] ?? $data['title'] ?? 'Delete failed with HTTP ' . $httpCode];
|
||||||
|
}
|
||||||
|
|
||||||
public function getSupportedMediaTypes(): array
|
public function getSupportedMediaTypes(): array
|
||||||
{
|
{
|
||||||
return ['image', 'video', 'gif'];
|
return ['image', 'video', 'gif'];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - X / Twitter</name>
|
<name>MokoSuiteCross - X / Twitter</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - Generic Webhook</name>
|
<name>MokoSuiteCross - Generic Webhook</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - WhatsApp Business</name>
|
<name>MokoSuiteCross - WhatsApp Business</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
<name>MokoSuiteCross - WordPress</name>
|
<name>MokoSuiteCross - WordPress</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
PLG_MOKOSUITECROSS_YOUTUBE="MokoSuiteCross - YouTube"
|
||||||
|
PLG_MOKOSUITECROSS_YOUTUBE_DESCRIPTION="Cross-post Joomla articles to YouTube community posts."
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
PLG_MOKOSUITECROSS_YOUTUBE="MokoSuiteCross - YouTube"
|
||||||
|
PLG_MOKOSUITECROSS_YOUTUBE_DESCRIPTION="Cross-post Joomla articles to YouTube community posts."
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package MokoSuiteCross
|
||||||
|
* @subpackage plg_mokosuitecross_youtube
|
||||||
|
* @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\Extension\PluginInterface;
|
||||||
|
use Joomla\CMS\Factory;
|
||||||
|
use Joomla\CMS\Plugin\PluginHelper;
|
||||||
|
use Joomla\DI\Container;
|
||||||
|
use Joomla\DI\ServiceProviderInterface;
|
||||||
|
use Joomla\Event\DispatcherInterface;
|
||||||
|
use Joomla\Plugin\MokoSuiteCross\Youtube\Extension\YoutubeService;
|
||||||
|
|
||||||
|
return new class () implements ServiceProviderInterface {
|
||||||
|
public function register(Container $container): void
|
||||||
|
{
|
||||||
|
$container->set(
|
||||||
|
PluginInterface::class,
|
||||||
|
function (Container $container) {
|
||||||
|
$plugin = new YoutubeService(
|
||||||
|
$container->get(DispatcherInterface::class),
|
||||||
|
(array) PluginHelper::getPlugin('mokosuitecross', 'youtube')
|
||||||
|
);
|
||||||
|
$plugin->setApplication(Factory::getApplication());
|
||||||
|
|
||||||
|
return $plugin;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package MokoSuiteCross
|
||||||
|
* @subpackage plg_mokosuitecross_youtube
|
||||||
|
* @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\MokoSuiteCross\Youtube\Extension;
|
||||||
|
|
||||||
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
|
use Joomla\CMS\Plugin\CMSPlugin;
|
||||||
|
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
||||||
|
use Joomla\Event\SubscriberInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* YouTube service plugin for MokoSuiteCross.
|
||||||
|
*
|
||||||
|
* Posts to YouTube via the Data API v3 channel bulletins.
|
||||||
|
*
|
||||||
|
* Credentials:
|
||||||
|
* access_token - OAuth 2.0 token with youtube.force-ssl scope
|
||||||
|
* channel_id - YouTube channel ID
|
||||||
|
*/
|
||||||
|
class YoutubeService extends CMSPlugin implements SubscriberInterface, MokoSuiteCrossServiceInterface
|
||||||
|
{
|
||||||
|
public static function getSubscribedEvents(): array
|
||||||
|
{
|
||||||
|
return ['onMokoSuiteCrossGetServices' => 'onMokoSuiteCrossGetServices'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onMokoSuiteCrossGetServices(&$services): void
|
||||||
|
{
|
||||||
|
$services[] = $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getServiceType(): string { return 'youtube'; }
|
||||||
|
public function getServiceName(): string { return 'YouTube'; }
|
||||||
|
public function getMaxLength(): int { return 5000; }
|
||||||
|
public function supportsMedia(): bool { return true; }
|
||||||
|
|
||||||
|
public function publish(string $message, array $media, array $credentials, array $params): array
|
||||||
|
{
|
||||||
|
$token = $credentials['access_token'] ?? '';
|
||||||
|
$channelId = $credentials['channel_id'] ?? '';
|
||||||
|
|
||||||
|
if (empty($token) || empty($channelId)) {
|
||||||
|
return ['success' => false, 'platform_post_id' => '', 'response' => ['error' => 'Missing access token or channel ID']];
|
||||||
|
}
|
||||||
|
|
||||||
|
$postData = json_encode([
|
||||||
|
'snippet' => [
|
||||||
|
'channelId' => $channelId,
|
||||||
|
'description' => $message,
|
||||||
|
],
|
||||||
|
'contentDetails' => [
|
||||||
|
'bulletin' => [
|
||||||
|
'resourceId' => [
|
||||||
|
'kind' => 'youtube#channel',
|
||||||
|
'channelId' => $channelId,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_URL => 'https://www.googleapis.com/youtube/v3/activities?part=snippet,contentDetails',
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_POSTFIELDS => $postData,
|
||||||
|
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, 'Content-Type: application/json'],
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
return ['success' => false, 'platform_post_id' => '', 'response' => ['error' => 'Connection error: ' . $curlError]];
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
$data = json_decode($response, true) ?: [];
|
||||||
|
|
||||||
|
if ($httpCode >= 200 && $httpCode < 300) {
|
||||||
|
return ['success' => true, 'platform_post_id' => $data['id'] ?? '', 'response' => $data];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['success' => false, 'platform_post_id' => '', 'response' => $data];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateCredentials(array $credentials): array
|
||||||
|
{
|
||||||
|
$token = $credentials['access_token'] ?? '';
|
||||||
|
|
||||||
|
if (empty($token)) {
|
||||||
|
return ['valid' => false, 'message' => 'Missing access token', 'account_name' => ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
$ch = curl_init('https://www.googleapis.com/youtube/v3/channels?part=snippet&mine=true');
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 10,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
return ['valid' => false, 'message' => 'Connection error: ' . $curlError, 'account_name' => ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_close($ch);
|
||||||
|
$data = json_decode($response, true) ?: [];
|
||||||
|
|
||||||
|
if (!empty($data['items'][0]['snippet']['title'])) {
|
||||||
|
return ['valid' => true, 'message' => 'Connected', 'account_name' => $data['items'][0]['snippet']['title']];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['valid' => false, 'message' => 'Invalid token or no channel found', 'account_name' => ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSupportedMediaTypes(): array
|
||||||
|
{
|
||||||
|
return ['image', 'video'];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<!DOCTYPE html><title></title>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package MokoSuiteCross
|
||||||
|
* @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;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<extension type="plugin" group="mokosuitecross" method="upgrade">
|
||||||
|
<name>MokoSuiteCross - Youtube</name>
|
||||||
|
<version>01.04.09-dev</version>
|
||||||
|
<creationDate>2026-06-23</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>PLG_MOKOSUITECROSS_YOUTUBE_DESCRIPTION</description>
|
||||||
|
|
||||||
|
<namespace path="src">Joomla\Plugin\MokoSuiteCross\Youtube</namespace>
|
||||||
|
|
||||||
|
<files>
|
||||||
|
<filename plugin="youtube">youtube.php</filename>
|
||||||
|
<folder>src</folder>
|
||||||
|
<folder>services</folder>
|
||||||
|
<folder>language</folder>
|
||||||
|
</files>
|
||||||
|
|
||||||
|
<languages>
|
||||||
|
<language tag="en-GB">language/en-GB/plg_mokosuitecross_youtube.ini</language>
|
||||||
|
<language tag="en-GB">language/en-GB/plg_mokosuitecross_youtube.sys.ini</language>
|
||||||
|
</languages>
|
||||||
|
<config>
|
||||||
|
<fields name="params">
|
||||||
|
<fieldset name="basic" label="PLG_MOKOSUITECROSS_YOUTUBE_FIELDSET_DEFAULTS">
|
||||||
|
<field
|
||||||
|
name="default_webhook_url"
|
||||||
|
type="url"
|
||||||
|
label="PLG_MOKOSUITECROSS_YOUTUBE_DEFAULT_WEBHOOK"
|
||||||
|
description="PLG_MOKOSUITECROSS_YOUTUBE_DEFAULT_WEBHOOK_DESC"
|
||||||
|
size="60"
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
</fields>
|
||||||
|
</config>
|
||||||
|
</extension>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="system" method="upgrade">
|
<extension type="plugin" group="system" method="upgrade">
|
||||||
<name>System - MokoSuiteCross</name>
|
<name>System - MokoSuiteCross</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="system" method="upgrade">
|
<extension type="plugin" group="system" method="upgrade">
|
||||||
<name>System - MokoSuiteCross Events</name>
|
<name>System - MokoSuiteCross Events</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="system" method="upgrade">
|
<extension type="plugin" group="system" method="upgrade">
|
||||||
<name>System - MokoSuiteCross Gallery</name>
|
<name>System - MokoSuiteCross Gallery</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="task" method="upgrade">
|
<extension type="plugin" group="task" method="upgrade">
|
||||||
<name>Task - MokoSuiteCross Queue Processor</name>
|
<name>Task - MokoSuiteCross Queue Processor</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="webservices" method="upgrade">
|
<extension type="plugin" group="webservices" method="upgrade">
|
||||||
<name>Web Services - MokoSuiteCross</name>
|
<name>Web Services - MokoSuiteCross</name>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<extension type="package" method="upgrade">
|
<extension type="package" method="upgrade">
|
||||||
<name>MokoSuiteCross</name>
|
<name>MokoSuiteCross</name>
|
||||||
<packagename>mokosuitecross</packagename>
|
<packagename>mokosuitecross</packagename>
|
||||||
<version>01.04.01</version>
|
<version>01.04.09-dev</version>
|
||||||
<creationDate>2026-05-28</creationDate>
|
<creationDate>2026-05-28</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
@@ -60,6 +60,8 @@
|
|||||||
<file type="plugin" id="tiktok" group="mokosuitecross">plg_mokosuitecross_tiktok.zip</file>
|
<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="mokosuitecalendar" group="mokosuitecross">plg_mokosuitecross_mokosuitecalendar.zip</file>
|
||||||
<file type="plugin" id="mokosuitegallery" group="mokosuitecross">plg_mokosuitecross_mokosuitegallery.zip</file>
|
<file type="plugin" id="mokosuitegallery" group="mokosuitecross">plg_mokosuitecross_mokosuitegallery.zip</file>
|
||||||
|
<file type="plugin" id="instagram" group="mokosuitecross">plg_mokosuitecross_instagram.zip</file>
|
||||||
|
<file type="plugin" id="youtube" group="mokosuitecross">plg_mokosuitecross_youtube.zip</file>
|
||||||
|
|
||||||
<!-- Content Source Plugins (system group) -->
|
<!-- Content Source Plugins (system group) -->
|
||||||
<file type="plugin" id="mokosuitecross_events" group="system">plg_system_mokosuitecross_events.zip</file>
|
<file type="plugin" id="mokosuitecross_events" group="system">plg_system_mokosuitecross_events.zip</file>
|
||||||
|
|||||||
Reference in New Issue
Block a user