| Current Path : /var/www/cesa.co.za/php/includes/ |
| Current File : /var/www/cesa.co.za/php/includes/MarketingAttendees.php |
<?php
/*
* Contact otto@igotafrica.com for details
*/
include_once($_SERVER['DOCUMENT_ROOT'] . '/php/includes/BaseTableClass.php');
include_once($_SERVER['DOCUMENT_ROOT'] . '/php/includes/MarketingPayments.php');
include_once($_SERVER['DOCUMENT_ROOT'] . '/php/includes/Mailer.php');
include_once($_SERVER['DOCUMENT_ROOT'] . '/php/includes/sqlFunctions.php');
include_once($_SERVER['DOCUMENT_ROOT'] . '/php/includes/ErrorLog.php');
/**
* Control the MarketingAttendees table
*
* @author Otto Saayman
*/
class MarketingAttendees extends BaseTableClass
{
public function findById($attendeeId)
{
// echo "SELECT * FROM MarketingAttendees "
// . "INNER JOIN MarketingEvents ON MarketingAttendees.EventID = MarketingEvents.EventID "
// . "WHERE MarketingAttendees.EventID = $eventId <br />";
return $this->sqlf->getRow("SELECT * FROM MarketingAttendees "
. "INNER JOIN MarketingEvents ON MarketingAttendees.EventID = MarketingEvents.EventID "
. "WHERE MarketingAttendees.AttendeeID = $attendeeId");
}
public function findByEventId($eventId)
{
// echo "SELECT * FROM MarketingAttendees "
// . "INNER JOIN MarketingEvents ON MarketingAttendees.EventID = MarketingEvents.EventID "
// . "WHERE MarketingAttendees.EventID = $eventId <br />";
return $this->sqlf->getAll("SELECT * FROM MarketingAttendees "
. "INNER JOIN MarketingEvents ON MarketingAttendees.EventID = MarketingEvents.EventID "
. "WHERE MarketingAttendees.EventID = $eventId");
}
public function findAllIds()
{
return $this->sqlf->getAll("SELECT AttendeeID FROM MarketingAttendees "
. "WHERE EmailAddress IS NOT NULL AND EmailAddress != '' AND EmailAddress NOT LIKE 'no e-mail address%' "
. "AND Cancelled = 0");
}
public function chooseAttendedPaid($requestVars)
{
$attendeeIds = array();
if (isset($requestVars['AttendeeIDs']) && strlen($requestVars['AttendeeIDs']) > 0) {
$attendeeIds = json_decode($requestVars['AttendeeIDs'], true);
}
if ((isset($requestVars['markAllAsAttendedPaid']) && $requestVars['markAllAsAttendedPaid'] > 0) || (isset($requestVars['markSelectedAsAttendedPaid']) && $requestVars['markSelectedAsAttendedPaid'] > 0)) {
$this->markAllAsAttendedPaid($requestVars['EventID'], true, true, $attendeeIds);
} elseif ((isset($requestVars['markAllAsAttended']) && $requestVars['markAllAsAttended'] > 0) || (isset($requestVars['markSelectedAsAttended']) && $requestVars['markSelectedAsAttended'] > 0)) {
$this->markAllAsAttendedPaid($requestVars['EventID'], true, false, $attendeeIds);
} elseif ((isset($requestVars['markAllAsPaid']) && $requestVars['markAllAsPaid'] > 0) || (isset($requestVars['markSelectedAsPaid']) && $requestVars['markSelectedAsPaid'] > 0)) {
$this->markAllAsAttendedPaid($requestVars['EventID'], false, true, $attendeeIds);
} elseif ((isset($requestVars['markAllAsNotAttendedPaid']) && $requestVars['markAllAsNotAttendedPaid'] > 0) || (isset($requestVars['markSelectedAsNotAttendedPaid']) && $requestVars['markSelectedAsNotAttendedPaid'] > 0)) {
$this->markAllAsNotAttendedPaid($requestVars['EventID'], true, true, $attendeeIds);
} elseif ((isset($requestVars['markAllAsNotAttended']) && $requestVars['markAllAsNotAttended'] > 0) || (isset($requestVars['markSelectedAsNotAttended']) && $requestVars['markSelectedAsNotAttended'] > 0)) {
$this->markAllAsNotAttendedPaid($requestVars['EventID'], true, false, $attendeeIds);
} elseif ((isset($requestVars['markAllAsNotPaid']) && $requestVars['markAllAsNotPaid'] > 0) || (isset($requestVars['markSelectedAsNotPaid']) && $requestVars['markSelectedAsNotPaid'] > 0)) {
$this->markAllAsNotAttendedPaid($requestVars['EventID'], false, true, $attendeeIds);
}
}
public function markAllAsAttendedPaid($eventId, $attended = true, $paid = true, $attendeeIds = array())
{
global $sqlf;
$eventAttendees = $this->findByEventId($eventId);
$marketingPaymentObject = new MarketingPayments($sqlf);
foreach ($eventAttendees as $eventAttendee) {
if (count($attendeeIds) > 0 && !in_array($eventAttendee['AttendeeID'], $attendeeIds)) {
continue;
}
// echo 'found attendee! <br />';
if ($attended) {
$eventAttendee['Attended'] = 'y';
$this->sqlf->autoExecute('MarketingAttendees', $eventAttendee, 'UPDATE', 'AttendeeID = ' . $eventAttendee['AttendeeID']);
}
if ($paid) {
$marketingPaymentObject->markPaidByEventAttendeeId($eventAttendee['AttendeeID'], $eventAttendee['CostPerPerson'], $eventAttendee['EventID']);
}
}
}
public function markAllAsNotAttendedPaid($eventId, $attended = true, $paid = true, $attendeeIds = array())
{
global $sqlf;
$eventAttendees = $this->findByEventId($eventId);
$marketingPaymentObject = new MarketingPayments($sqlf);
foreach ($eventAttendees as $eventAttendee) {
if (count($attendeeIds) > 0 && !in_array($eventAttendee['AttendeeID'], $attendeeIds)) {
continue;
}
// echo 'found attendee! <br />';
if ($attended) {
$eventAttendee['Attended'] = 'n';
// var_dump($eventAttendee);
// exit;
$this->sqlf->autoExecute('MarketingAttendees', $eventAttendee, 'UPDATE', 'AttendeeID = ' . $eventAttendee['AttendeeID']);
}
if ($paid) {
$marketingPaymentObject->markNotPaidByEventAttendeeId($eventAttendee['AttendeeID'], $eventAttendee['CostPerPerson'], $eventAttendee['EventID']);
}
}
}
public function getICSAttachmentFile($marketingAttendee, $options = array())
{
if (!is_array($marketingAttendee) || count($marketingAttendee) <= 0) {
return null;
}
$fullFilePath = sys_get_temp_dir() . '/' . $marketingAttendee['AttendeeID'] . '.ics';
file_put_contents($fullFilePath, Mailer::getICalText($marketingAttendee, $marketingAttendee, $options));
return $fullFilePath;
}
const FIELD_RESEND_INVITES = 'ResendInvites';
const FIELD_CALENDAR_CHANGE_MESSAGE = 'CalendarInviteChangeMessage';
const MAIL_SUBJECT_MAX_LENGTH = 255;
const CALENDAR_RESEND_BATCH_SIZE = 5;
const SESSION_CALENDAR_RESEND_PENDING = 'MarketingEventCalendarResendPending';
const IDENTITY_TYPE_SA_ID = 'SA ID';
const IDENTITY_TYPE_PASSPORT = 'Passport';
const FIELD_IDENTITY_DOCUMENT_TYPE = 'IdentityDocumentType';
public function normalizeIdentityDocumentType($value)
{
$value = trim((string) $value);
if (strcasecmp($value, self::IDENTITY_TYPE_PASSPORT) === 0 || strcasecmp($value, 'Passport') === 0) {
return self::IDENTITY_TYPE_PASSPORT;
}
return self::IDENTITY_TYPE_SA_ID;
}
public function identityNumberFieldLabel($identityDocumentType)
{
if ($this->normalizeIdentityDocumentType($identityDocumentType) === self::IDENTITY_TYPE_PASSPORT) {
return 'Passport number';
}
return 'South African ID number';
}
public function isValidSouthAfricanIdNumber($value)
{
$value = preg_replace('/\s+/', '', trim((string) $value));
if (!preg_match('/^\d{13}$/', $value)) {
return false;
}
$val1 = 0;
$val2 = 0;
for ($i = 0; $i < 13; $i += 2) {
$val1 += (int) $value[$i];
}
for ($i = 1; $i < 12; $i += 2) {
$tmp = (int) $value[$i] * 2;
if ($tmp >= 10) {
$tmp = ($tmp - 10) + 1;
}
$val2 += $tmp;
}
return (($val1 + $val2) % 10) === 0;
}
public function isValidPassportNumber($value)
{
$value = preg_replace('/\s+/', '', trim((string) $value));
if ($value === '') {
return false;
}
return (bool) preg_match('/^[A-Za-z0-9]{6,20}$/', $value);
}
public function validateRegistrationIdentityNumber($identityDocumentType, $idNumber, $attendeeNumber = 0)
{
$attendeeNumber = (int) $attendeeNumber;
$prefix = $attendeeNumber > 0 ? 'Attendee ' . $attendeeNumber . "'s " : '';
$type = $this->normalizeIdentityDocumentType($identityDocumentType);
$idNumber = trim((string) $idNumber);
if ($idNumber === '') {
return $prefix . $this->identityNumberFieldLabel($type) . ' is required.';
}
if ($type === self::IDENTITY_TYPE_PASSPORT) {
if (!$this->isValidPassportNumber($idNumber)) {
return $prefix . 'Passport number is not valid. Please enter 6 to 20 letters and numbers.';
}
return null;
}
if (!$this->isValidSouthAfricanIdNumber($idNumber)) {
return $prefix . 'South African ID number is not valid. Please enter a valid 13-digit ID number.';
}
return null;
}
public function extractDateOfBirthFromSouthAfricanId($idNumber)
{
$idNumber = preg_replace('/\s+/', '', trim((string) $idNumber));
if (!$this->isValidSouthAfricanIdNumber($idNumber)) {
return null;
}
$year = (int) substr($idNumber, 0, 2);
$month = (int) substr($idNumber, 2, 2);
$day = (int) substr($idNumber, 4, 2);
$fullYear = 2000 + $year;
if ($fullYear > (int) date('Y')) {
$fullYear -= 100;
}
if (!checkdate($month, $day, $fullYear)) {
return null;
}
return sprintf('%04d-%02d-%02d', $fullYear, $month, $day);
}
public function normalizeRegistrationDateOfBirth($value)
{
$value = trim((string) $value);
if ($value === '') {
return '';
}
$dt = \DateTime::createFromFormat('Y-m-d', $value);
if ($dt instanceof \DateTime && $dt->format('Y-m-d') === $value) {
return $value;
}
foreach (array('d/m/Y', 'd-m-Y', 'Y/m/d') as $format) {
$dt = \DateTime::createFromFormat($format, $value);
if ($dt instanceof \DateTime && $dt->format($format) === $value) {
return $dt->format('Y-m-d');
}
}
$timestamp = strtotime($value);
if ($timestamp === false) {
return null;
}
return date('Y-m-d', $timestamp);
}
public function validateRegistrationDateOfBirth($dateOfBirth, $attendeeNumber = 0)
{
$attendeeNumber = (int) $attendeeNumber;
$prefix = $attendeeNumber > 0 ? 'Attendee ' . $attendeeNumber . "'s " : '';
$normalized = $this->normalizeRegistrationDateOfBirth($dateOfBirth);
if ($normalized === '' || $normalized === null) {
return $prefix . 'date of birth is required. Please enter a valid date (YYYY-MM-DD).';
}
if ($normalized > date('Y-m-d')) {
return $prefix . 'date of birth cannot be in the future.';
}
$minYear = (int) date('Y') - 120;
if ((int) substr($normalized, 0, 4) < $minYear) {
return $prefix . 'date of birth is not valid.';
}
return null;
}
public function formatRegistrationDateOfBirthForDisplay($value)
{
$normalized = $this->normalizeRegistrationDateOfBirth($value);
if ($normalized === '' || $normalized === null) {
return '';
}
return $normalized;
}
/**
* SQL literal for MarketingAttendees.DateOfBirth (DATE column).
* Empty / invalid values become NULL — never '' (MySQL rejects '' for DATE).
*
* @param string|null $value
* @return string NULL or quoted Y-m-d
*/
public function sqlDateOfBirthLiteral($value)
{
$normalized = $this->normalizeRegistrationDateOfBirth($value);
if ($normalized === '' || $normalized === null) {
return 'NULL';
}
return "'" . addslashes($normalized) . "'";
}
/**
* Resolve DOB for registration insert: posted value, else SA ID extraction.
*
* @param string $postedDob
* @param string $identityDocumentType
* @param string $idNumber
* @return string Y-m-d or ''
*/
public function resolveRegistrationDateOfBirthForSave($postedDob, $identityDocumentType, $idNumber)
{
$dob = $this->formatRegistrationDateOfBirthForDisplay($postedDob);
if ($dob !== '') {
return $dob;
}
if ($this->normalizeIdentityDocumentType($identityDocumentType) === self::IDENTITY_TYPE_SA_ID) {
$extracted = $this->extractDateOfBirthFromSouthAfricanId($idNumber);
if (is_string($extracted) && $extracted !== '') {
return $extracted;
}
}
return '';
}
/**
* @param array{submittedCount?:int,duplicateCount?:int,insertFailureCount?:int,insertFailures?:array<int,array<string,mixed>>} $context
* @return array{heading:string,detail:string}
*/
public function buildNoAttendeesRegisteredSummary(array $context)
{
$submittedCount = (int) ($context['submittedCount'] ?? 0);
$duplicateCount = (int) ($context['duplicateCount'] ?? 0);
$insertFailureCount = (int) ($context['insertFailureCount'] ?? 0);
$insertFailures = is_array($context['insertFailures'] ?? null) ? $context['insertFailures'] : array();
if ($submittedCount <= 0) {
return array(
'heading' => 'No attendees were registered.',
'detail' => 'No attendee details were submitted. Please complete at least one attendee and submit the form again.',
);
}
if ($duplicateCount > 0 && $insertFailureCount === 0 && $duplicateCount >= $submittedCount) {
return array(
'heading' => 'No new attendees were registered.',
'detail' => 'All ' . $duplicateCount . ' attendee(s) you submitted are already registered for this event (see details below).',
);
}
if ($duplicateCount > 0 && $insertFailureCount === 0) {
return array(
'heading' => 'No new attendees were registered.',
'detail' => $duplicateCount . ' of the ' . $submittedCount . ' submitted attendee(s) are already registered for this event (see details below).',
);
}
if ($insertFailureCount > 0 && $duplicateCount === 0) {
$detail = 'Your registration could not be saved due to a system error. Please review the error details below, correct the problem if you can, and submit again. If the problem continues, please contact the event organisers.';
if (count($insertFailures) === 1 && !empty($insertFailures[0]['displayError'])) {
$detail .= ' Error: ' . $insertFailures[0]['displayError'];
}
return array(
'heading' => 'No attendees were registered.',
'detail' => $detail,
);
}
if ($insertFailureCount > 0 && $duplicateCount > 0) {
return array(
'heading' => 'No new attendees were registered.',
'detail' => $insertFailureCount . ' attendee(s) could not be saved and ' . $duplicateCount . ' duplicate(s) were found (see details below).',
);
}
return array(
'heading' => 'No attendees were registered.',
'detail' => 'Please check your attendee details and submit the form again.',
);
}
public function sanitizeRegistrationDatabaseErrorForDisplay($mysqlError)
{
$message = trim((string) $mysqlError);
if ($message === '') {
return 'The registration could not be saved.';
}
$message = preg_replace('/`[^`]+`\.`[^`]+`\.`([^`]+)`/', "'$1'", $message);
$message = preg_replace('/`([^`]+)`/', "'$1'", $message);
return $message;
}
/**
* @param array{firstName?:string,surname?:string,email?:string} $attendeeContext
* @return array{attendeeNumber:int,firstName:string,surname:string,email:string,displayError:string}
*/
public function handleRegistrationInsertFailure($eventId, $attendeeNumber, array $attendeeContext, $mysqlError, $sql = '')
{
$attendeeNumber = (int) $attendeeNumber;
$eventId = (int) $eventId;
$displayError = $this->sanitizeRegistrationDatabaseErrorForDisplay($mysqlError);
$firstName = trim((string) ($attendeeContext['firstName'] ?? ''));
$surname = trim((string) ($attendeeContext['surname'] ?? ''));
$email = trim((string) ($attendeeContext['email'] ?? ''));
$logMessage = "Marketing event registration INSERT failed\n"
. 'EventID: ' . $eventId . "\n"
. 'Attendee slot: ' . $attendeeNumber . "\n"
. 'Name: ' . $firstName . ' ' . $surname . "\n"
. 'Email: ' . $email . "\n"
. 'MySQL error: ' . trim((string) $mysqlError) . "\n"
. 'SQL: ' . substr((string) $sql, 0, 4000);
ErrorLog::logError($logMessage);
if (function_exists('maill')) {
maill(
'cesa-marketing@igotafrica.com',
'Marketing event registration insert failed (Event ' . $eventId . ')',
$logMessage,
null,
[
'from-name' => 'CESA Events',
'from-address' => 'events@cesa.co.za',
'reply-to-address' => 'events@cesa.co.za',
]
);
}
return array(
'attendeeNumber' => $attendeeNumber,
'firstName' => $firstName,
'surname' => $surname,
'email' => $email,
'displayError' => $displayError,
);
}
public function findCalendarInviteRecipientsByEventId($eventId)
{
$eventId = intval($eventId);
if ($eventId <= 0) {
return array();
}
return $this->sqlf->getAll(
"SELECT MarketingAttendees.*, MarketingEvents.*
FROM MarketingAttendees
INNER JOIN MarketingEvents ON MarketingAttendees.EventID = MarketingEvents.EventID
WHERE MarketingAttendees.EventID = " . $eventId . "
AND IFNULL(MarketingAttendees.Cancelled, 0) = 0
AND MarketingAttendees.EmailAddress IS NOT NULL
AND MarketingAttendees.EmailAddress != ''
AND MarketingAttendees.EmailAddress NOT LIKE 'no e-mail address%'
ORDER BY MarketingAttendees.AttendeeID ASC"
);
}
public function getEventCalendarResendRow($eventId)
{
$eventId = intval($eventId);
if ($eventId <= 0) {
return null;
}
return $this->sqlf->getRow(
'SELECT EventID, ResendInvites FROM MarketingEvents WHERE EventID = ' . $eventId . ' LIMIT 1'
);
}
public function shouldResendCalendarInvitesFromEventRow($eventRow)
{
if (!is_array($eventRow)) {
return false;
}
$value = $eventRow[self::FIELD_RESEND_INVITES] ?? 0;
return intval($value) === 1 || $value === true || $value === '1';
}
public function calendarChangeMessageFromEventRow($eventRow)
{
if (!is_array($eventRow)) {
return '';
}
if (isset($eventRow[self::FIELD_CALENDAR_CHANGE_MESSAGE])) {
return trim((string) $eventRow[self::FIELD_CALENDAR_CHANGE_MESSAGE]);
}
return '';
}
public function clearResendInvitesFlag($eventId)
{
$eventId = intval($eventId);
if ($eventId <= 0) {
return false;
}
return $this->sqlf->execute(
'UPDATE MarketingEvents SET ' . self::FIELD_RESEND_INVITES . ' = 0 WHERE EventID = ' . $eventId
);
}
public function getCalendarResendRecipientCount($eventId)
{
return count($this->findCalendarInviteRecipientsByEventId($eventId));
}
public function sendCalendarInvitesBatch($eventId, $changeMessage = '', $offset = 0, $batchSize = null)
{
$eventId = intval($eventId);
$offset = max(0, intval($offset));
$batchSize = $batchSize === null ? self::CALENDAR_RESEND_BATCH_SIZE : max(1, intval($batchSize));
$result = array(
'eventId' => $eventId,
'total' => 0,
'offset' => $offset,
'processed' => 0,
'sent' => 0,
'skipped' => 0,
'failed' => 0,
'complete' => false,
'nextOffset' => $offset,
'errors' => array(),
);
if ($eventId <= 0) {
$result['errors'][] = 'Invalid event ID.';
$result['complete'] = true;
return $result;
}
$attendees = $this->findCalendarInviteRecipientsByEventId($eventId);
$result['total'] = count($attendees);
if ($result['total'] <= 0) {
$this->clearResendInvitesFlag($eventId);
$result['complete'] = true;
return $result;
}
if ($offset >= $result['total']) {
$result['complete'] = true;
$result['nextOffset'] = $result['total'];
return $result;
}
$batch = array_slice($attendees, $offset, $batchSize);
foreach ($batch as $attendeeRow) {
$sendResult = $this->sendCalendarInviteToAttendeeViaMaill($attendeeRow, $changeMessage);
$result['processed']++;
if ($sendResult === true) {
$result['sent']++;
} elseif ($sendResult === null) {
$result['skipped']++;
} else {
$result['failed']++;
if (is_string($sendResult) && $sendResult !== '') {
$result['errors'][] = $sendResult;
}
}
}
$result['nextOffset'] = $offset + count($batch);
$result['complete'] = $result['nextOffset'] >= $result['total'];
if ($result['complete'] && $result['failed'] === 0) {
$this->clearResendInvitesFlag($eventId);
}
return $result;
}
/**
* @return bool|null|string true sent, null skipped, string error message on failure
*/
public function sendCalendarInviteToAttendeeViaMaill($attendeeRow, $changeMessage = '')
{
if (!is_array($attendeeRow) || count($attendeeRow) <= 0) {
return null;
}
$recipientEmails = $this->buildCalendarInviteRecipientEmails($attendeeRow);
if (count($recipientEmails) <= 0) {
return null;
}
include_once($_SERVER['DOCUMENT_ROOT'] . '/php/includes/maill.php');
$eventName = isset($attendeeRow['EventName']) ? $attendeeRow['EventName'] : 'CESA Event';
$subject = $this->truncateForMailColumn('Updated: ' . $eventName, self::MAIL_SUBJECT_MAX_LENGTH);
$body = $this->buildCalendarUpdateEmailHtml($attendeeRow, $changeMessage);
$icsOptions = array(
'isUpdate' => true,
'changeMessage' => $changeMessage,
'sequence' => time(),
);
$mailOptions = $this->buildDirectCalendarResendMailOptions($attendeeRow, $icsOptions);
try {
//TEST FIRST
$recipientEmails = ['cesa-event-test@igotafrica.com'];
maill(implode(',', $recipientEmails), $subject, $body, null, $mailOptions);
} catch (\Throwable $e) {
return 'Attendee ' . intval($attendeeRow['AttendeeID']) . ': ' . $e->getMessage();
}
return true;
}
protected function buildDirectCalendarResendMailOptions($attendeeRow, array $icsOptions)
{
return array(
'isHTML' => true,
'content-type' => 'text/html; charset=UTF-8',
'from-address' => 'bonolo@cesa.co.za',
'from-name' => 'CESA Events',
'reply-to-address' => 'events@cesa.co.za',
'i-cal' => Mailer::getICalText($attendeeRow, $attendeeRow, $icsOptions),
);
}
public function clearStaleCalendarResendSessionOnEditPageLoad()
{
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST') {
return;
}
unset($_SESSION[self::SESSION_CALENDAR_RESEND_PENDING]);
}
public function prepareCalendarResendSession($eventId, $changeMessage = '')
{
$eventId = intval($eventId);
if ($eventId <= 0) {
return false;
}
$_SESSION[self::SESSION_CALENDAR_RESEND_PENDING] = array(
'eventId' => $eventId,
'changeMessage' => trim((string) $changeMessage),
);
return true;
}
public function consumeCalendarResendSession($eventId)
{
$eventId = intval($eventId);
if ($eventId <= 0 || !isset($_SESSION[self::SESSION_CALENDAR_RESEND_PENDING])) {
return null;
}
$pending = $_SESSION[self::SESSION_CALENDAR_RESEND_PENDING];
if (!is_array($pending) || intval($pending['eventId'] ?? 0) !== $eventId) {
return null;
}
unset($_SESSION[self::SESSION_CALENDAR_RESEND_PENDING]);
return array(
'eventId' => $eventId,
'changeMessage' => trim((string) ($pending['changeMessage'] ?? '')),
);
}
public function hasCalendarResendPendingRedirect()
{
return isset($_SESSION[self::SESSION_CALENDAR_RESEND_PENDING])
&& is_array($_SESSION[self::SESSION_CALENDAR_RESEND_PENDING])
&& intval($_SESSION[self::SESSION_CALENDAR_RESEND_PENDING]['eventId'] ?? 0) > 0;
}
public function getCalendarResendWaitPageUrl($eventId)
{
$eventId = intval($eventId);
if ($eventId <= 0) {
return '';
}
return '/echasl23/MarketingEventCalendarResendWait.php?EventID=' . $eventId;
}
public function truncateForMailColumn($text, $maxLength)
{
$text = (string) $text;
$maxLength = intval($maxLength);
if ($maxLength <= 0 || strlen($text) <= $maxLength) {
return $text;
}
if ($maxLength <= 3) {
return substr($text, 0, $maxLength);
}
return substr($text, 0, $maxLength - 3) . '...';
}
public function buildCalendarInviteRecipientEmails($attendeeRow)
{
$emails = array();
if (!empty($attendeeRow['EmailAddress']) && $this->isUsableCalendarEmail($attendeeRow['EmailAddress'])) {
$emails[] = trim($attendeeRow['EmailAddress']);
}
if (!empty($attendeeRow['BookingEmail']) && $this->isUsableCalendarEmail($attendeeRow['BookingEmail'])) {
$emails[] = trim($attendeeRow['BookingEmail']);
}
return array_values(array_unique($emails));
}
public function buildCalendarInviteRecipientList($attendeeRow)
{
return implode(',', $this->buildCalendarInviteRecipientEmails($attendeeRow));
}
public function isUsableCalendarEmail($email)
{
$email = trim((string) $email);
if ($email === '') {
return false;
}
$lower = strtolower($email);
if ($lower === 'not applicable' || $lower === 'n/a' || $lower === 'na') {
return false;
}
if (stripos($email, 'no e-mail address') === 0) {
return false;
}
return (bool) filter_var($email, FILTER_VALIDATE_EMAIL);
}
public function buildCalendarUpdateEmailHtml($attendeeRow, $changeMessage = '')
{
$firstName = isset($attendeeRow['ContactFirstName']) ? $attendeeRow['ContactFirstName'] : '';
$lastName = isset($attendeeRow['ContactLastName']) ? $attendeeRow['ContactLastName'] : '';
$safeName = htmlspecialchars(trim($firstName . ' ' . $lastName), ENT_QUOTES, 'UTF-8');
$html = '<html><head><style>body, td { font-family: Arial, Verdana, sans-serif; font-size: 11pt; }</style></head><body>';
$html .= '<p>Dear ' . $safeName . ',</p>';
$html .= '<p><strong>This event has been updated.</strong> Please find the updated calendar invitation attached.</p>';
$changeMessage = trim((string) $changeMessage);
if ($changeMessage !== '') {
$html .= '<p>' . nl2br(htmlspecialchars($changeMessage, ENT_QUOTES, 'UTF-8')) . '</p>';
}
$html .= $this->buildEventSummaryHtml($attendeeRow);
$html .= '<p>With kind regards,<br>CESA Events</p>';
$html .= '</body></html>';
return $html;
}
public function buildEventSummaryHtml($eventRow)
{
$eventName = isset($eventRow['EventName']) ? htmlspecialchars($eventRow['EventName'], ENT_QUOTES, 'UTF-8') : '';
$html = '<p><strong>' . $eventName . '</strong>';
if (!empty($eventRow['StartDate'])) {
$html .= ' will be held on ' . date('D j F Y', strtotime($eventRow['StartDate']));
if (!empty($eventRow['EndDate']) && $eventRow['EndDate'] !== $eventRow['StartDate']) {
$html .= ' to ' . date('D j F Y', strtotime($eventRow['EndDate']));
}
}
if (!empty($eventRow['StartTime'])) {
$html .= ' at ' . date('H:i', strtotime($eventRow['StartTime']));
if (!empty($eventRow['EndTime'])) {
$html .= '-' . date('H:i', strtotime($eventRow['EndTime']));
}
}
if (!empty($eventRow['Venue'])) {
if ($eventRow['Venue'] === 'MS Teams') {
$html .= ', on ' . htmlspecialchars($eventRow['Venue'], ENT_QUOTES, 'UTF-8');
} else {
$html .= ', at ' . htmlspecialchars($eventRow['Venue'], ENT_QUOTES, 'UTF-8');
}
}
$html .= '.</p>';
return $html;
}
public function formatCalendarResendResultMessage(array $result)
{
$sent = isset($result['sent']) ? intval($result['sent']) : 0;
$skipped = isset($result['skipped']) ? intval($result['skipped']) : 0;
$failed = isset($result['failed']) ? intval($result['failed']) : 0;
$message = 'Calendar invites sent: ' . $sent;
if ($skipped > 0) {
$message .= ', ' . $skipped . ' skipped';
}
if ($failed > 0) {
$message .= ', ' . $failed . ' failed (ResendInvites left set)';
}
$message .= '.';
if (!empty($result['errors']) && is_array($result['errors'])) {
$message .= ' ' . implode(' ', array_slice($result['errors'], 0, 3));
}
return $message;
}
public function resolveAttendeeDateOfBirth(array $attendee)
{
$stored = trim((string) ($attendee['DateOfBirth'] ?? ''));
if ($stored !== '') {
$normalized = $this->normalizeRegistrationDateOfBirth($stored);
if ($normalized !== '' && $normalized !== null) {
return $normalized;
}
}
$idNumber = trim((string) ($attendee['IDNumber'] ?? ''));
if ($idNumber === '') {
return null;
}
return $this->extractDateOfBirthFromSouthAfricanId($idNumber);
}
public function getYoungProfessionalYesNo($attendeeID)
{
if (empty($attendeeID)) {
return '-';
}
$attendee = $this->findById($attendeeID);
if (empty($attendee)) {
return '-';
}
$dateOfBirth = $this->resolveAttendeeDateOfBirth($attendee);
if ($dateOfBirth === null) {
return '-';
}
$birthDate = DateTime::createFromFormat('Y-m-d', $dateOfBirth);
if (!$birthDate instanceof DateTime) {
return '-';
}
$today = new DateTime('today');
$age = $today->diff($birthDate)->y;
if ($age <= 36) {
return 'Yes';
}
return 'No';
}
}
function marketingEventRowUpdatedResendCalendarInvites(&$rsold, &$rsnew)
{
global $sqlf;
$marketingAttendees = new MarketingAttendees($sqlf);
$eventId = 0;
if (is_array($rsnew) && isset($rsnew['EventID'])) {
$eventId = intval($rsnew['EventID']);
} elseif (is_array($rsold) && isset($rsold['EventID'])) {
$eventId = intval($rsold['EventID']);
}
$shouldResend = $marketingAttendees->shouldResendCalendarInvitesFromEventRow($rsnew);
if (!$shouldResend) {
return;
}
$changeMessage = $marketingAttendees->calendarChangeMessageFromEventRow($rsnew);
$marketingAttendees->prepareCalendarResendSession($eventId, $changeMessage);
}
function marketingEventEditPageLoadClearStaleCalendarResend()
{
global $sqlf;
$marketingAttendees = new MarketingAttendees($sqlf);
$marketingAttendees->clearStaleCalendarResendSessionOnEditPageLoad();
}
function marketingEventEditPageRedirectingCalendarResend(&$url)
{
global $sqlf;
$marketingAttendees = new MarketingAttendees($sqlf);
if (!$marketingAttendees->hasCalendarResendPendingRedirect()) {
return;
}
$pending = $_SESSION[MarketingAttendees::SESSION_CALENDAR_RESEND_PENDING];
$eventId = intval($pending['eventId'] ?? 0);
if ($eventId <= 0) {
return;
}
$url = $marketingAttendees->getCalendarResendWaitPageUrl($eventId);
}
function marketingAttendeeListOptionsLoad(&$objThis) {
// $objThis->ListOptions->Add("registration");
// $objThis->ListOptions->Items["registration"]->CssStyle = "white-space: nowrap;";
// $objThis->ListOptions->Items["registration"]->OnLeft = FALSE;
// $objThis->ListOptions->Items["registration"]->Header="Registration Form";
// $objThis->ListOptions->MoveItem("registration",count($objThis->ListOptions->Items)-1);
$objThis->ListOptions->Add("YoungProfessional");
$objThis->ListOptions->Items["YoungProfessional"]->CssStyle = "white-space: nowrap;";
$objThis->ListOptions->Items["YoungProfessional"]->OnLeft = FALSE;
$objThis->ListOptions->Items["YoungProfessional"]->Header="Young Pro";
$objThis->ListOptions->MoveItem("YoungProfessional", 0);
}
function marketingAttendeeListOptionsRendered(&$objThis, &$marketingAttendeesTable) {
global $sqlf;
$attendeeId = $marketingAttendeesTable->AttendeeID->CurrentValue;
$marketingAttendeesService = new MarketingAttendees($sqlf);
$objThis->ListOptions->Items["YoungProfessional"]->Body = $marketingAttendeesService->getYoungProfessionalYesNo($attendeeId);
}
function marketingCertificatesListMenu() {
if (isset($_SESSION['EventID']) && strlen($_SESSION['EventID']) > 0) {
$_SESSION[EW_SESSION_MESSAGE] .= '<br><SPAN class=phpmaker>'
. '<A href="MarketingCertificateEmail.php?EventID=' . $_SESSION['EventID'] . '"><IMG title="" border=0 alt="" src="images/exportemail.gif" width=16 height=16> E-mail All Certificates</A> '
. '<a href="MarketingCertificateEmail.php?EventID=' . $_SESSION['EventID'] . '&markAllAsAttendedPaid=1">Mark All as Attended and Paid</a> '
. '<a href="MarketingCertificateEmail.php?EventID=' . $_SESSION['EventID'] . '&markAllAsAttended=1">Mark All as Attended</a> '
. '<a href="MarketingCertificateEmail.php?EventID=' . $_SESSION['EventID'] . '&markAllAsPaid=1">Mark All as Paid</a> '
// . '<a href="CertificateEmail.php?EventID=' . $_SESSION['EventID'] . '&markAllAsUnpaid=1">Mark All as Unpaid</a>'
. '<br /><br />'
. '<a href="MarketingCertificateEmail.php?EventID=' . $_SESSION['EventID'] . '&markSelectedEMail=1" class="selected-e-mail"><IMG title="" border=0 alt="" src="images/exportemail.gif" width=16 height=16> E-mail Selected Certificates</a> '
. '<a href="MarketingCertificateEmail.php?EventID=' . $_SESSION['EventID'] . '&markSelectedAsAttendedPaid=1" class="selected-attended-paid">Mark Selected as Attended and Paid</a> '
. '<a href="MarketingCertificateEmail.php?EventID=' . $_SESSION['EventID'] . '&markSelectedAsAttended=1" class="selected-attended">Mark Selected as Attended</a> '
. '<a href="MarketingCertificateEmail.php?EventID=' . $_SESSION['EventID'] . '&markSelectedAsPaid=1" class="selected-paid">Mark Selected as Paid</a><br /><br />'
. '<a href="MarketingCertificateEmail.php?EventID='.$_SESSION['EventID'].'&markAllAsNotAttendedPaid=1">Mark All as NOT Attended and NOT Paid</a> '
. '<a href="CertificateEmail.php?EventID='.$_SESSION['EventID'].'&markAllAsNotAttended=1">Mark All NOT NOT Attended</a> '
. '<a href="CertificateEmail.php?EventID='.$_SESSION['EventID'].'&markAllAsNotPaid=1">Mark All as NOT Paid</a><br /><br />'
. '<a href="CertificateEmail.php?EventID='.$_SESSION['EventID'].'&markSelectedAsNotAttendedPaid=1" class="selected-attended-paid">Mark Selected as NOT Attended and Paid</a> '
. '<a href="CertificateEmail.php?EventID='.$_SESSION['EventID'].'&markSelectedAsNotAttended=1" class="selected-attended">Mark Selected as NOT Attended</a> '
. '<a href="CertificateEmail.php?EventID='.$_SESSION['EventID'].'&markSelectedAsNotPaid=1" class="selected-paid">Mark Selected as NOT Paid</a><br /><br />'
. '</SPAN>';
}
}