Linux cesa-www-main 6.1.0-49-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64
Apache/2.4.68 (Debian)
Server IP : 10.218.0.2 & Your IP : 216.73.216.28
Domains :
Cant Read [ /etc/named.conf ]
User : www-data
Terminal
Auto Root
Create File
Create Folder
Localroot Suggester
Backdoor Destroyer
Readme
/
var /
www /
cesa.co.za /
php /
includes /
Delete
Unzip
Name
Size
Permission
Date
Action
error_handling
[ DIR ]
drwxr-sr-x
2025-07-07 15:54
mail
[ DIR ]
drwxr-sr-x
2025-06-14 11:17
ADQAnswers.php
3.16
KB
-rw-r--r--
2026-06-03 05:55
ADQAudit.php
6.51
KB
-rw-r--r--
2026-06-03 05:55
ADQAuditAnswers.php
1.21
KB
-rw-r--r--
2026-06-03 05:55
ADQFirms.php
18.41
KB
-rw-r--r--
2025-12-31 10:56
ADQuestionnairesAnswers.php
1.05
KB
-rw-r--r--
2026-06-03 05:55
ADQuestionnairesAudit.php
2.01
KB
-rw-r--r--
2026-06-03 05:55
ADecQuestionnaires.php
3.31
KB
-rw-r--r--
2026-06-03 05:55
ADecQuestions.php
541
B
-rw-r--r--
2026-06-03 05:55
ADec_Contacts.php
1.61
KB
-rw-r--r--
2026-06-03 05:55
ADec_OfficeContacts.php
7.43
KB
-rw-r--r--
2026-06-03 05:55
ADec_PIUnderwriters.php
742
B
-rw-r--r--
2026-06-03 05:55
ADec_Transformation.php
817
B
-rw-r--r--
2026-06-03 05:55
ADec_tblMemberFirmOffices.php
1.49
KB
-rw-r--r--
2026-06-03 05:55
ADec_tblMemberFirmSkills.php
2.38
KB
-rw-r--r--
2026-06-03 05:55
ADec_tblMemberFirms.php
19.37
KB
-rw-r--r--
2026-06-03 05:55
AnonRespondees.php
4.25
KB
-rw-r--r--
2026-06-05 11:41
AwardsEntries.php
2.4
KB
-rw-r--r--
2026-06-06 10:48
BaseTableClass.php
950
B
-rw-r--r--
2026-04-26 08:24
BatchJob.php
4.46
KB
-rw-r--r--
2026-06-03 05:55
BceAssignmentFeedbackFormsReportService.php
6.77
KB
-rw-r--r--
2026-05-25 13:06
CCEAssignment.php
12.44
KB
-rw-r--r--
2026-05-26 14:26
CCEAssignmentMarking.php
26.26
KB
-rw-r--r--
2026-08-14 12:06
CCEBooks.php
524
B
-rw-r--r--
2026-06-03 05:55
CCEConvenorAuth.php
3.06
KB
-rw-r--r--
2026-08-19 11:18
CCECourseSchedules.php
579
B
-rw-r--r--
2026-06-03 05:55
CCEDocCategories.php
529
B
-rw-r--r--
2026-06-03 05:55
CCEDocuments.php
8.15
KB
-rw-r--r--
2026-08-20 12:16
CCEExam.php
15.87
KB
-rw-r--r--
2026-06-03 06:35
CCEExamQuestion.php
1.76
KB
-rw-r--r--
2026-06-03 05:55
CCEFacilitatorUser.php
931
B
-rw-r--r--
2026-06-03 05:55
CCEMessageLog.php
951
B
-rw-r--r--
2026-06-03 05:55
CCEMessages.php
8.88
KB
-rw-r--r--
2026-07-01 13:20
CCEModuleMarksService.php
7.16
KB
-rw-r--r--
2026-05-26 10:30
CCEModules.php
1.16
KB
-rw-r--r--
2026-06-03 05:55
CCEResourceGroups.php
591
B
-rw-r--r--
2026-06-03 05:55
CCEResources.php
896
B
-rw-r--r--
2026-06-03 05:55
CCESetup.php
256
B
-rw-r--r--
2026-02-09 16:20
CCEStAssigExtension.php
2.07
KB
-rw-r--r--
2026-06-03 05:55
CCEStAssigExtensionRequest.php
6.65
KB
-rw-r--r--
2026-06-03 05:55
CCEStudentAssignment.php
16.61
KB
-rw-r--r--
2026-07-15 14:44
CCEStudentCourses.php
611
B
-rw-r--r--
2026-06-03 05:55
CCEStudentDisplayOrder.php
4.96
KB
-rw-r--r--
2026-05-26 09:32
CCEStudents.php
8.4
KB
-rw-r--r--
2026-08-19 11:18
CCETemplates.php
896
B
-rw-r--r--
2026-06-03 05:55
CCEWeeklySessions.php
2.27
KB
-rw-r--r--
2026-02-09 16:25
CPDSPReview.php
624
B
-rw-r--r--
2026-06-03 05:55
CPDServiceProvider.php
4.57
KB
-rw-r--r--
2026-06-12 11:48
CPDTrainingMaterial.php
3.96
KB
-rw-r--r--
2026-06-12 11:48
CceUploadedFileService.php
34.9
KB
-rw-r--r--
2026-06-24 12:03
Certificates.php
8.93
KB
-rw-r--r--
2026-08-18 15:42
Contact.php
690
B
-rw-r--r--
2026-06-03 05:55
ErrorLog.php
1.22
KB
-rw-r--r--
2026-08-24 10:12
EventSchedules.php
5.39
KB
-rw-r--r--
2025-12-20 10:03
FileUploads.php
2.1
KB
-rw-r--r--
2026-06-03 05:55
HTMLHelper.php
17.2
KB
-rw-r--r--
2026-06-03 05:55
KeyValue.php
4.9
KB
-rw-r--r--
2026-04-25 09:34
Mailer.php
12.78
KB
-rw-r--r--
2026-07-15 09:50
MarketingAttendees.php
36.87
KB
-rw-r--r--
2026-08-14 12:06
MarketingPayments.php
2.52
KB
-rw-r--r--
2026-06-03 05:55
MemberContact.php
1.04
KB
-rw-r--r--
2026-06-03 05:55
MemberFirm.php
17.71
KB
-rw-r--r--
2026-06-03 05:55
MemberFirmOffices.php
752
B
-rw-r--r--
2026-06-03 05:55
MemberFirmSkill.php
18.04
KB
-rw-r--r--
2026-06-03 05:55
MemberPortalTransformationProxy.php
4.44
KB
-rw-r--r--
2026-05-20 09:08
MemberPortalUrls.php
1.82
KB
-rw-r--r--
2026-05-20 09:53
MsgAttachFileService.php
4.92
KB
-rw-r--r--
2026-06-22 08:37
OfficeContact.php
1.01
KB
-rw-r--r--
2026-06-03 05:55
Picklists.php
539
B
-rw-r--r--
2026-06-03 05:55
Questionnaires.php
849
B
-rw-r--r--
2026-01-23 12:39
SchoolAttendeeBulkMailerService.php
34.26
KB
-rw-r--r--
2026-08-11 10:45
SchoolAttendees.php
73.21
KB
-rw-r--r--
2026-08-11 10:45
SchoolCourses.php
7.98
KB
-rw-r--r--
2026-08-19 11:59
SchoolEvents.php
45.54
KB
-rw-r--r--
2026-08-18 15:42
SchoolPayments.php
4.48
KB
-rw-r--r--
2026-04-13 09:33
SchoolPresenters.php
1.86
KB
-rw-r--r--
2026-01-23 12:43
SchoolWeeksCoursesDashboardProxy.php
10.69
KB
-rw-r--r--
2026-08-14 12:32
TrainingProvider.php
8.06
KB
-rw-r--r--
2026-01-23 12:43
TrainingProviderDoc.php
554
B
-rw-r--r--
2026-06-03 05:55
Transformation.php
10.87
KB
-rw-r--r--
2026-05-13 09:36
UnempPractitioners.php
1.43
KB
-rw-r--r--
2026-06-03 05:55
Utils.php
13.72
KB
-rw-r--r--
2026-07-23 07:28
WeeklySessionsParser.php
7.87
KB
-rw-r--r--
2026-02-09 16:20
cceMessageAttachmentUpload.inc.php
5.12
KB
-rw-r--r--
2026-06-03 05:55
cceMessageEditor.inc.php
9.26
KB
-rw-r--r--
2026-07-01 13:20
cce_file_download_endpoint.php
1.75
KB
-rw-r--r--
2026-06-24 08:38
cstUnreadMessages.php
2.1
KB
-rw-r--r--
2026-07-01 13:20
cstWeeksCourses.php
1.84
KB
-rw-r--r--
2026-08-14 12:06
load_phpmailer.php
742
B
-rw-r--r--
2026-07-17 14:57
maill.php
19.45
KB
-rw-r--r--
2026-08-19 12:23
require_ewmysql.php
1.2
KB
-rw-r--r--
2026-08-24 10:12
sqlFunctions.php
10.48
KB
-rw-r--r--
2026-08-24 10:12
Save
Rename
<?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>'; } }