fix: remove duplicate curl_setopt_array calls in 4 service plugins (#139)
SendGrid and Reddit had a second curl_setopt_array that referenced an undefined $token variable, silently breaking auth. TikTok and Pinterest had identical duplicates (no variable bug but dead code). Removes the duplicate block from each plugin's publish() method.
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package MokoSuiteCross
|
||||
* @subpackage plg_mokosuitecross_pinterest
|
||||
* @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\Pinterest\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
/**
|
||||
* Pinterest service plugin for MokoSuiteCross.
|
||||
*
|
||||
* API: https://api.pinterest.com/v5/pins
|
||||
*/
|
||||
class PinterestService 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 'pinterest'; }
|
||||
public function getServiceName(): string { return 'Pinterest'; }
|
||||
public function getMaxLength(): int { return 500; }
|
||||
public function supportsMedia(): bool { return true; }
|
||||
|
||||
public function publish(string $message, array $media, array $credentials, array $params): array
|
||||
{
|
||||
$token = $credentials['access_token'] ?? '';
|
||||
$boardId = $credentials['board_id'] ?? '';
|
||||
|
||||
if (empty($token) || empty($boardId)) {
|
||||
return ['success' => false, 'platform_post_id' => '', 'response' => ['error' => 'Missing access token or board ID']];
|
||||
}
|
||||
|
||||
if (empty($media[0])) {
|
||||
return ['success' => false, 'platform_post_id' => '', 'response' => ['error' => 'Pinterest requires an image']];
|
||||
}
|
||||
|
||||
$postData = json_encode([
|
||||
'board_id' => $boardId,
|
||||
'title' => mb_substr(strip_tags($message), 0, 100),
|
||||
'description' => mb_substr($message, 0, 500),
|
||||
'media_source' => [
|
||||
'source_type' => 'image_url',
|
||||
'url' => $media[0],
|
||||
],
|
||||
]);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => 'https://api.pinterest.com/v5/pins',
|
||||
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'] ?? $data['uri'] ?? '', '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://api.pinterest.com/v5/user_account');
|
||||
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['username'])) {
|
||||
return ['valid' => true, 'message' => 'Connected', 'account_name' => $data['username']];
|
||||
}
|
||||
|
||||
return ['valid' => false, 'message' => 'Invalid token', 'account_name' => ''];
|
||||
}
|
||||
|
||||
public function getSupportedMediaTypes(): array
|
||||
{
|
||||
return ['image'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package MokoSuiteCross
|
||||
* @subpackage plg_mokosuitecross_reddit
|
||||
* @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\Reddit\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
/**
|
||||
* Reddit service plugin for MokoSuiteCross.
|
||||
*
|
||||
* API: https://oauth.reddit.com/api/submit
|
||||
*/
|
||||
class RedditService 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 'reddit'; }
|
||||
public function getServiceName(): string { return 'Reddit'; }
|
||||
public function getMaxLength(): int { return 300; }
|
||||
public function supportsMedia(): bool { return true; }
|
||||
|
||||
public function publish(string $message, array $media, array $credentials, array $params): array
|
||||
{
|
||||
$accessToken = $credentials['access_token'] ?? '';
|
||||
$subreddit = $credentials['subreddit'] ?? '';
|
||||
|
||||
if (empty($accessToken) || empty($subreddit)) {
|
||||
return ['success' => false, 'platform_post_id' => '', 'response' => ['error' => 'Missing access token or subreddit']];
|
||||
}
|
||||
|
||||
$title = $params['title'] ?? mb_substr(strip_tags($message), 0, 300);
|
||||
|
||||
$postData = http_build_query([
|
||||
'sr' => $subreddit,
|
||||
'kind' => 'self',
|
||||
'title' => $title,
|
||||
'text' => $message,
|
||||
]);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => 'https://oauth.reddit.com/api/submit',
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $postData,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . $accessToken,
|
||||
'User-Agent: MokoSuiteCross/1.0',
|
||||
],
|
||||
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'] ?? $data['uri'] ?? '', '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://oauth.reddit.com/api/v1/me');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, 'User-Agent: MokoSuiteCross/1.0'],
|
||||
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['name'])) {
|
||||
return ['valid' => true, 'message' => 'Connected', 'account_name' => 'u/' . $data['name']];
|
||||
}
|
||||
|
||||
return ['valid' => false, 'message' => 'Invalid token', 'account_name' => ''];
|
||||
}
|
||||
|
||||
public function getSupportedMediaTypes(): array
|
||||
{
|
||||
return ['image'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package MokoSuiteCross
|
||||
* @subpackage plg_mokosuitecross_sendgrid
|
||||
* @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\Sendgrid\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
/**
|
||||
* SendGrid service plugin for MokoSuiteCross.
|
||||
*
|
||||
* API: https://api.sendgrid.com/v3/marketing/singlesends
|
||||
*/
|
||||
class SendgridService 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 'sendgrid'; }
|
||||
public function getServiceName(): string { return 'SendGrid'; }
|
||||
public function getMaxLength(): int { return 0; }
|
||||
public function supportsMedia(): bool { return true; }
|
||||
|
||||
public function publish(string $message, array $media, array $credentials, array $params): array
|
||||
{
|
||||
$apiKey = $credentials['api_key'] ?? '';
|
||||
$listId = $credentials['list_id'] ?? '';
|
||||
$senderEmail = $credentials['sender_email'] ?? '';
|
||||
$senderName = $credentials['sender_name'] ?? 'Newsletter';
|
||||
|
||||
if (empty($apiKey) || empty($senderEmail)) {
|
||||
return ['success' => false, 'platform_post_id' => '', 'response' => ['error' => 'Missing API key or sender email']];
|
||||
}
|
||||
|
||||
$subject = $params['subject'] ?? mb_substr(strip_tags($message), 0, 150);
|
||||
|
||||
$postData = json_encode([
|
||||
'name' => $subject,
|
||||
'send_to' => !empty($listId) ? ['list_ids' => [$listId]] : ['all' => true],
|
||||
'email_config' => [
|
||||
'subject' => $subject,
|
||||
'html_content' => $message,
|
||||
'sender_id' => null,
|
||||
'custom_unsubscribe_url' => '',
|
||||
],
|
||||
]);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => 'https://api.sendgrid.com/v3/marketing/singlesends',
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $postData,
|
||||
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey, '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'] ?? $data['uri'] ?? '', 'response' => $data];
|
||||
}
|
||||
|
||||
return ['success' => false, 'platform_post_id' => '', 'response' => $data];
|
||||
}
|
||||
|
||||
public function validateCredentials(array $credentials): array
|
||||
{
|
||||
$key = $credentials['api_key'] ?? '';
|
||||
|
||||
if (empty($key)) {
|
||||
return ['valid' => false, 'message' => 'Missing API key', 'account_name' => ''];
|
||||
}
|
||||
|
||||
$ch = curl_init('https://api.sendgrid.com/v3/user/profile');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key],
|
||||
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['first_name'])) {
|
||||
return ['valid' => true, 'message' => 'Connected', 'account_name' => $data['first_name'] . ' ' . ($data['last_name'] ?? '')];
|
||||
}
|
||||
|
||||
return ['valid' => false, 'message' => 'Invalid API key', 'account_name' => ''];
|
||||
}
|
||||
|
||||
public function getSupportedMediaTypes(): array
|
||||
{
|
||||
return ['image'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package MokoSuiteCross
|
||||
* @subpackage plg_mokosuitecross_tiktok
|
||||
* @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\Tiktok\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Component\MokoSuiteCross\Administrator\Service\MokoSuiteCrossServiceInterface;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
/**
|
||||
* TikTok service plugin for MokoSuiteCross.
|
||||
*
|
||||
* API: https://open.tiktokapis.com/v2/post/publish/content/init/
|
||||
*/
|
||||
class TiktokService 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 'tiktok'; }
|
||||
public function getServiceName(): string { return 'TikTok'; }
|
||||
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 = $credentials['access_token'] ?? '';
|
||||
|
||||
if (empty($token)) {
|
||||
return ['success' => false, 'platform_post_id' => '', 'response' => ['error' => 'Missing access token']];
|
||||
}
|
||||
|
||||
if (empty($media[0])) {
|
||||
return ['success' => false, 'platform_post_id' => '', 'response' => ['error' => 'TikTok requires a video or image']];
|
||||
}
|
||||
|
||||
$postData = json_encode([
|
||||
'post_info' => [
|
||||
'title' => mb_substr(strip_tags($message), 0, 150),
|
||||
'description' => mb_substr($message, 0, 2200),
|
||||
'privacy_level' => 'SELF_ONLY',
|
||||
'disable_comment' => false,
|
||||
],
|
||||
'source_info' => [
|
||||
'source' => 'PULL_FROM_URL',
|
||||
'video_url' => $media[0],
|
||||
],
|
||||
]);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => 'https://open.tiktokapis.com/v2/post/publish/content/init/',
|
||||
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'] ?? $data['uri'] ?? '', '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://open.tiktokapis.com/v2/user/info/?fields=display_name,username');
|
||||
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['data']['user']['display_name'])) {
|
||||
return ['valid' => true, 'message' => 'Connected', 'account_name' => $data['data']['user']['display_name']];
|
||||
}
|
||||
|
||||
return ['valid' => false, 'message' => 'Invalid token', 'account_name' => ''];
|
||||
}
|
||||
|
||||
public function getSupportedMediaTypes(): array
|
||||
{
|
||||
return ['image', 'video'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user