Files
MokoSuiteBackup/source/packages/com_mokobackup/src/Engine/NotificationSender.php
T
Jonathan Miller a13f7ca6a6
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
chore: rename src/ to source/ per MokoStandards convention
Update all references in Makefile, manifest.xml, .gitignore, and CI
workflows (ci-joomla, pr-check, repo-health) to use source/ as the
primary directory with src/ as a fallback for compatibility.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 08:08:33 -05:00

179 lines
5.1 KiB
PHP

<?php
/**
* @package MokoJoomBackup
* @subpackage com_mokobackup
* @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
*
* Sends email notifications on backup success or failure.
* Uses Joomla's built-in mail system (Factory::getMailer()).
*/
namespace Joomla\Component\MokoBackup\Administrator\Engine;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
class NotificationSender
{
/**
* Send a backup notification email.
*
* @param object $profile Profile object with notification settings
* @param object $record Backup record object with results
* @param bool $success Whether the backup succeeded
* @param string $logText Backup log text
*
* @return bool True if email was sent
*/
public static function send(object $profile, object $record, bool $success, string $logText = ''): bool
{
$notifyEmail = trim($profile->notify_email ?? '');
$notifyUserGroups = $profile->notify_user_groups ?? '';
// Resolve user group members to email addresses
$groupEmails = self::resolveUserGroupEmails($notifyUserGroups);
if (empty($notifyEmail) && empty($groupEmails)) {
return false;
}
// Check notification preferences
if ($success && empty($profile->notify_on_success)) {
return false;
}
if (!$success && empty($profile->notify_on_failure)) {
return false;
}
try {
$mailer = Factory::getMailer();
$config = Factory::getApplication()->getConfig();
$siteName = $config->get('sitename', 'Joomla Site');
$siteUrl = Uri::root();
// Parse recipient list (comma-separated) + user group emails
$recipients = array_map('trim', explode(',', $notifyEmail));
$recipients = array_merge($recipients, $groupEmails);
$recipients = array_unique(array_filter($recipients, fn($e) => filter_var($e, FILTER_VALIDATE_EMAIL)));
if (empty($recipients)) {
return false;
}
foreach ($recipients as $recipient) {
$mailer->addRecipient($recipient);
}
// Build subject
$statusLabel = $success ? 'SUCCESS' : 'FAILED';
$mailer->setSubject("[MokoBackup] {$statusLabel}: {$record->description}{$siteName}");
// Build body
$duration = '';
if (!empty($record->backupstart) && !empty($record->backupend)
&& $record->backupend !== '0000-00-00 00:00:00') {
$start = strtotime($record->backupstart);
$end = strtotime($record->backupend);
$seconds = max(0, $end - $start);
$duration = $seconds < 60
? $seconds . ' seconds'
: round($seconds / 60, 1) . ' minutes';
}
$sizeHuman = $record->total_size > 0
? number_format($record->total_size / 1048576, 2) . ' MB'
: 'N/A';
$body = "MokoJoomBackup Notification\n"
. "============================\n\n"
. "Site: {$siteName}\n"
. "URL: {$siteUrl}\n"
. "Status: {$statusLabel}\n"
. "Profile: {$profile->title}\n"
. "Description: {$record->description}\n"
. "Type: {$record->backup_type}\n"
. "Origin: {$record->origin}\n"
. "Archive: {$record->archivename}\n"
. "Size: {$sizeHuman}\n"
. "Duration: {$duration}\n"
. "Started: {$record->backupstart}\n"
. "Ended: {$record->backupend}\n";
if (!empty($record->remote_filename)) {
$body .= "Remote: {$record->remote_filename}\n";
}
if ($record->files_count > 0 || $record->tables_count > 0) {
$body .= "Files: {$record->files_count}\n"
. "Tables: {$record->tables_count}\n";
}
// Add log excerpt on failure (last 30 lines)
if (!$success && !empty($logText)) {
$logLines = explode("\n", $logText);
$excerpt = array_slice($logLines, -30);
$body .= "\n--- Log (last 30 lines) ---\n"
. implode("\n", $excerpt) . "\n";
}
$body .= "\n--\n"
. "MokoJoomBackup — https://mokoconsulting.tech\n";
$mailer->setBody($body);
$mailer->isHtml(false);
return $mailer->Send();
} catch (\Throwable $e) {
// Don't let notification failure break the backup flow
error_log('MokoBackup notification error: ' . $e->getMessage());
return false;
}
}
/**
* Resolve user group IDs to email addresses of group members.
*
* @param string|array $groups Comma-separated group IDs or array
*
* @return array Email addresses
*/
private static function resolveUserGroupEmails(string|array $groups): array
{
if (empty($groups)) {
return [];
}
if (\is_string($groups)) {
$groups = array_filter(array_map('intval', explode(',', $groups)));
}
if (empty($groups)) {
return [];
}
try {
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('DISTINCT ' . $db->quoteName('u.email'))
->from($db->quoteName('#__users', 'u'))
->join('INNER', $db->quoteName('#__user_usergroup_map', 'ugm') . ' ON ugm.user_id = u.id')
->where($db->quoteName('u.block') . ' = 0')
->whereIn($db->quoteName('ugm.group_id'), $groups);
$db->setQuery($query);
return $db->loadColumn() ?: [];
} catch (\Throwable $e) {
return [];
}
}
}