| Current Path : /var/www/html/plugins/system/membershipprosms/src/Extension/ |
| Current File : /var/www/html/plugins/system/membershipprosms/src/Extension/SMS.php |
<?php
/**
* @package Joomla
* @subpackage Membership Pro
* @author Tuan Pham Ngoc
* @copyright Copyright (C) 2012 - 2026 Ossolution Team
* @license GNU/GPL, see LICENSE.php
*/
namespace JoomDonation\MembershipPro\Plugin\System\SMS\Extension;
use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Event\DispatcherInterface;
use Joomla\Event\Event;
use Joomla\Event\SubscriberInterface;
use OSSolution\MembershipPro\Admin\Event\SMS\SendingSMSReminder;
defined('_JEXEC') or die;
class SMS extends CMSPlugin implements SubscriberInterface
{
use DatabaseAwareTrait;
public static function getSubscribedEvents(): array
{
return [
'onAfterRespond' => 'onAfterRespond',
'onAfterStoreSubscription' => 'onAfterStoreSubscription',
'onMembershipActive' => 'onMembershipActive',
];
}
/**
* Constructor.
*
* @param DispatcherInterface $dispatcher The dispatcher
* @param array $config An optional associative array of configuration settings
*/
public function __construct(DispatcherInterface $dispatcher, array $config)
{
parent::__construct($dispatcher, $config);
}
/**
* Generate invoice number after registrant complete payment for registration
*
* @param Event $event
*
* @return void
*/
public function onMembershipActive(Event $event): void
{
[$row] = array_values($event->getArguments());
// Workaround to prevent listening to event trigger with same name (from our other extensions)
if (!property_exists($row, 'plan_id')
|| !property_exists($row, 'first_sms_reminder_sent'))
{
return;
}
if (!str_contains($row->payment_method, 'os_offline'))
{
$this->sendSMSMessageToAdmin($row);
}
}
/**
* Generate invoice number after registrant complete registration in case he uses offline payment
*
* @param Event $event
*
* @return void
*/
public function onAfterStoreSubscription(Event $event): void
{
/* @var \OSMembershipTableSubscriber $row */
[$row] = array_values($event->getArguments());
if (str_contains($row->payment_method, 'os_offline'))
{
$this->sendSMSMessageToAdmin($row);
}
}
/**
* Method to send SMS message to administrator
*
* @param \OSMembershipTableSubscriber $row
*/
private function sendSMSMessageToAdmin($row)
{
$app = $this->getApplication();
if ($app->isClient('administrator'))
{
return;
}
$phones = $this->params->get('phones');
if (!$phones)
{
return;
}
$phones = explode(',', $phones);
$phones = array_filter($phones);
if (!count($phones))
{
return;
}
// Get extra data for the registration record
$fieldSuffix = \OSMembershipHelper::getFieldSuffix($row->language);
$plan = \OSMembershipHelperDatabase::getPlan($row->plan_id, $fieldSuffix);
// Admin does not allow sending SMS, stop
if (!$plan->enable_sms_reminder)
{
return;
}
$message = \OSMembershipHelper::getMessages();
switch ($row->act)
{
case 'renew':
if (!empty($plan->new_subscription_renewal_admin_sms))
{
$smsMessage = $plan->new_subscription_renewal_admin_sms;
}
else
{
$smsMessage = $message->new_subscription_renewal_admin_sms;
}
break;
case 'upgrade':
if (!empty($plan->new_subscription_upgrade_admin_sms))
{
$smsMessage = $plan->new_subscription_upgrade_admin_sms;
}
else
{
$smsMessage = $message->new_subscription_upgrade_admin_sms;
}
break;
default:
if (!empty($plan->new_subscription_admin_sms))
{
$smsMessage = $plan->new_subscription_admin_sms;
}
else
{
$smsMessage = $message->new_subscription_admin_sms;
}
break;
}
if (!trim((string) $smsMessage))
{
return;
}
$replaces = \OSMembershipHelper::buildSMSTags($row);
$replaces['plan_title'] = $plan->title;
foreach ($replaces as $key => $value)
{
$value = (string) $value;
$smsMessage = str_ireplace('[' . $key . ']', $value, $smsMessage);
}
$admins = [];
foreach ($phones as $phone)
{
$admin = clone $row;
$admin->phone = $phone;
$admin->sms_message = $smsMessage;
$admins[] = $admin;
}
// Trigger
if (count($admins))
{
PluginHelper::importPlugin('membershipprosms');
$event = new SendingSMSReminder([
'rows' => $admins,
]);
$app->getDispatcher()->dispatch($event->getName(), $event);
}
}
/**
* Handle onAfterRespond event to send SMS reminder
*
* @return void
*
* @throws \Exception
*/
public function onAfterRespond(Event $event): void
{
if (!$this->canRun())
{
return;
}
$db = $this->getDatabase();
$query = $db->getQuery(true);
if (!trim($this->params->get('trigger_reminder_code', '')))
{
//Store last run time
$this->params->set('last_run', time());
$query->update('#__extensions')
->set('params = ' . $db->quote($this->params->toString()))
->where('`element`= "ebsmsreminder"')
->where('`folder`= "system"');
try
{
// Lock the tables to prevent multiple plugin executions causing a race condition
$db->lockTable('#__extensions');
}
catch (\Exception $e)
{
// If we can't lock the tables it's too risk continuing execution
return;
}
try
{
// Update the plugin parameters
$result = $db->setQuery($query)->execute();
$this->clearCacheGroups(['com_plugins'], [0, 1]);
}
catch (\Exception $exc)
{
// If we failed to execute
$db->unlockTables();
$result = false;
}
try
{
// Unlock the tables after writing
$db->unlockTables();
}
catch (\Exception $e)
{
// If we can't lock the tables assume we have somehow failed
$result = false;
}
// Abort on failure
if (!$result)
{
return;
}
}
// Require library + register autoloader
require_once JPATH_ADMINISTRATOR . '/components/com_osmembership/loader.php';
// Send first, second and third SMS reminder
$this->sendSMSReminder(1);
$this->sendSMSReminder(2);
$this->sendSMSReminder(3);
// Send extra SMS reminder if available
$fields = array_keys($this->getDatabase()->getTableColumns('#__osmembership_plans'));
if (in_array('send_fourth_reminder', $fields) && in_array('fourth_reminder_sms', $fields))
{
$this->sendSMSReminder(4);
}
if (in_array('send_fifth_reminder', $fields) && in_array('fifth_reminder_sms', $fields))
{
$this->sendSMSReminder(5);
}
if (in_array('send_sixth_reminder', $fields) && in_array('sixth_reminder_sms', $fields))
{
$this->sendSMSReminder(6);
}
}
/**
* Register listeners
*
* @return void
*/
public function registerListeners()
{
if (!ComponentHelper::isEnabled('com_osmembership'))
{
return;
}
parent::registerListeners();
}
/**
* Method to send sms reminder to subscribers
*
* @param int $number
*/
private function sendSMSReminder($number)
{
if (!in_array($number, [1, 2, 3, 4, 5, 6]))
{
return;
}
$db = $this->getDatabase();
// Workaround to allow supporting different sms messages in each plan
$fields = array_keys($db->getTableColumns('#__osmembership_plans'));
switch ($number)
{
case 2:
$smsMessageField = 'second_reminder_sms';
if (in_array('send_second_sms_reminder', $fields))
{
$sendReminderField = 'b.send_second_sms_reminder';
}
else
{
$sendReminderField = 'b.send_second_reminder';
}
$reminderSentField = 'a.second_sms_reminder_sent';
break;
case 3:
$smsMessageField = 'third_reminder_sms';
if (in_array('send_third_sms_reminder', $fields))
{
$sendReminderField = 'b.send_third_sms_reminder';
}
else
{
$sendReminderField = 'b.send_third_reminder';
}
$reminderSentField = 'a.third_sms_reminder_sent';
break;
case 4:
$smsMessageField = 'fourth_reminder_sms';
if (in_array('send_fourth_sms_reminder', $fields))
{
$sendReminderField = 'b.send_fourth_sms_reminder';
}
else
{
$sendReminderField = 'b.send_fourth_reminder';
}
$reminderSentField = 'a.fourth_sms_reminder_sent';
break;
case 5:
$smsMessageField = 'fifth_reminder_sms';
if (in_array('send_fifth_sms_reminder', $fields))
{
$sendReminderField = 'b.send_fifth_sms_reminder';
}
else
{
$sendReminderField = 'b.send_fifth_reminder';
}
$reminderSentField = 'a.fifth_sms_reminder_sent';
break;
case 6:
$smsMessageField = 'sixth_reminder_sms';
if (in_array('send_sixth_sms_reminder', $fields))
{
$sendReminderField = 'b.send_sixth_sms_reminder';
}
else
{
$sendReminderField = 'b.send_sixth_reminder';
}
$reminderSentField = 'a.sixth_sms_reminder_sent';
break;
default:
$smsMessageField = 'first_reminder_sms';
if (in_array('send_first_sms_reminder', $fields))
{
$sendReminderField = 'b.send_first_sms_reminder';
}
else
{
$sendReminderField = 'b.send_first_reminder';
}
$reminderSentField = 'a.first_sms_reminder_sent';
break;
}
$message = \OSMembershipHelper::getMessages();
// Stop processing it further if the sms message is not configured
if (!trim((string) $message->{$smsMessageField}))
{
return;
}
$numberEmailSendEachTime = (int) $this->params->get('number_subscribers', 0) ?: 15;
$planFields = [];
if (in_array('first_reminder_sms', $fields))
{
$planFields[] = 'b.first_reminder_sms';
}
if (in_array('second_reminder_sms', $fields))
{
$planFields[] = 'b.second_reminder_sms';
}
if (in_array('third_reminder_sms', $fields))
{
$planFields[] = 'b.third_reminder_sms';
}
if (in_array('fourth_reminder_sms', $fields))
{
$planFields[] = 'b.fourth_reminder_sms';
}
if (in_array('fifth_reminder_sms', $fields))
{
$planFields[] = 'b.fifth_reminder_sms';
}
if (in_array('sixth_reminder_sms', $fields))
{
$planFields[] = 'b.sixth_reminder_sms';
}
$query = $db->getQuery(true)
->select('a.*, b.title AS plan_title, b.recurring_subscription, b.number_payments, c.username')
->select('IF(b.send_first_reminder > 0, DATEDIFF(to_date, NOW()), DATEDIFF(NOW(), to_date)) AS number_days')
->from('#__osmembership_subscribers AS a')
->innerJoin('#__osmembership_plans AS b ON a.plan_id = b.id')
->leftJoin('#__users AS c ON a.user_id = c.id')
->where("$sendReminderField != 0")
->where('b.lifetime_membership != 1')
->where('b.enable_sms_reminder = 1')
->where('a.published IN (1, 2)')
->where("$reminderSentField = 0")
->where('a.group_admin_id = 0')
->where(
"IF($sendReminderField > 0, $sendReminderField >= DATEDIFF(to_date, NOW()) AND DATEDIFF(to_date, NOW()) >= 0, DATEDIFF(NOW(), to_date) >= ABS($sendReminderField) AND DATEDIFF(NOW(), to_date) <= 60)"
)
->order('a.to_date');
$db->setQuery($query, 0, $numberEmailSendEachTime);
if (count($planFields))
{
$query->select($db->quoteName($planFields));
}
try
{
$rows = $db->loadObjectList();
}
catch (\Exception $e)
{
$rows = [];
}
if (!count($rows))
{
return;
}
$ids = [];
foreach ($rows as $row)
{
$ids[] = $row->id;
if (!$row->phone)
{
continue;
}
if ($this->isSubscriptionRenewed($row))
{
continue;
}
if (!empty($row->{$smsMessageField}))
{
$smsMessage = $row->{$smsMessageField};
}
else
{
$smsMessage = $message->{$smsMessageField};
}
$replaces = \OSMembershipHelper::buildSMSTags($row);
foreach ($replaces as $key => $value)
{
$value = (string) $value;
$smsMessage = str_ireplace('[' . $key . ']', $value, $smsMessage);
}
$row->sms_message = $smsMessage;
}
PluginHelper::importPlugin('membershipprosms');
$event = new SendingSMSReminder(['rows' => $rows]);
$result = $this->getApplication()->getDispatcher()->dispatch($event->getName(), $event)->getArgument('result', []);
if (in_array(true, $result, true))
{
$query->clear()
->update('#__osmembership_subscribers AS a')
->set("$reminderSentField = 1")
->whereIn('id', $ids);
$db->setQuery($query)
->execute();
}
}
/**
* Method to check if the subscription was renewed
*
* @param \stdClass $row
*
* @return bool
*/
private function isSubscriptionRenewed($row)
{
if (!$row->user_id)
{
return false;
}
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('COUNT(*)')
->from('#__osmembership_subscribers')
->where('plan_id = ' . $row->plan_id)
->where('user_id = ' . (int) $row->user_id)
->where('published = 1')
->where('id > ' . $row->id);
$db->setQuery($query);
return (int) $db->loadResult() > 0;
}
/**
* Method to check whether this plugin should be run
*
* @return bool
*/
private function canRun()
{
// Process sending reminder on every page load if debug mode enabled
if ($this->params->get('debug', 0))
{
return true;
}
$app = $this->getApplication();
// If trigger reminder code is set, we will only process sending reminder from cron job
if (trim($this->params->get('trigger_reminder_code', ''))
&& trim($this->params->get('trigger_reminder_code')) != $app->getInput()->getString(
'trigger_reminder_code'
))
{
return false;
}
// If time ranges is set and current time is not within these specified ranges, we won't process sending reminder
if ($this->params->get('time_ranges'))
{
$withinTimeRage = false;
$date = Factory::getDate('Now', $app->get('offset'));
$currentHour = $date->format('G', true);
$timeRanges = explode(';', $this->params->get('time_ranges'));// Time ranges format 6,10;14,20
foreach ($timeRanges as $timeRange)
{
if (!str_contains($timeRange, ','))
{
continue;
}
[$fromHour, $toHour] = explode(',', $timeRange);
if ($fromHour <= $currentHour && $toHour >= $currentHour)
{
$withinTimeRage = true;
break;
}
}
if (!$withinTimeRage)
{
return false;
}
}
if (!trim($this->params->get('trigger_reminder_code', '')))
{
// Send reminder if the last time reminder emails are sent was more than 20 minutes ago
$lastRun = (int) $this->params->get('last_run', 0);
if ((time() - $lastRun) < 1200)
{
return false;
}
}
return true;
}
/**
* Clears cache groups. We use it to clear the plugins cache after we update the last run timestamp.
*
* @param array $clearGroups The cache groups to clean
* @param array $cacheClients The cache clients (site, admin) to clean
*
* @return void
*
* @since 2.0.4
*/
private function clearCacheGroups(array $clearGroups, array $cacheClients = [0, 1])
{
$app = $this->getApplication();
foreach ($clearGroups as $group)
{
foreach ($cacheClients as $clientId)
{
try
{
$options = [
'defaultgroup' => $group,
'cachebase' => ($clientId) ? JPATH_ADMINISTRATOR . '/cache' :
$app->get('cache_path', JPATH_SITE . '/cache'),
];
$cache = Cache::getInstance('callback', $options);
$cache->clean();
}
catch (\Exception $e)
{
// Ignore it
}
}
}
}
}