PluginProbe
The Innovative Form Builder – IvyForms / 1.0
The Innovative Form Builder – IvyForms v1.0
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / backend / src / Services / Notification / NotificationService.php

NotificationService.php in The Innovative Form Builder – IvyForms 1.0, at backend/src/Services/Notification/NotificationService.php

406 lines 12.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace IvyForms\Services\Notification;
4
5 use IvyForms\Common\Exceptions\InvalidArgumentException;
6 use IvyForms\Common\Exceptions\NotFoundException;
7 use IvyForms\Common\Exceptions\QueryExecutionException;
8 use IvyForms\Common\Sanitizer\Sanitizer;
9 use IvyForms\Entity\Notification\Notification;
10 use IvyForms\Factory\Notification\NotificationFactory;
11 use IvyForms\Repository\Notification\NotificationRepositoryInterface;
12 use IvyForms\Services\Placeholder\PlaceholderService;
13 use IvyForms\Services\Media\ImageService;
14 use IvyForms\Services\Translations\BackendStrings;
15 use IvyForms\Services\Mailer\MailerService;
16
17 class NotificationService
18 {
19 private NotificationRepositoryInterface $notificationRepository;
20 private ImageService $imageService;
21
22 // Constructor injection for the NotificationRepository via PHP-DI
23 public function __construct(
24 NotificationRepositoryInterface $notificationRepository,
25 ImageService $imageService
26 ) {
27 $this->notificationRepository = $notificationRepository;
28 $this->imageService = $imageService;
29 }
30
31 /**
32 * Get all notifications from the repository.
33 *
34 * @return array<mixed>|null
35 */
36 public function getAllNotifications(): ?array
37 {
38 return $this->notificationRepository->getAll();
39 }
40
41 /**
42 * Create a new notification.
43 *
44 * @param Notification $notificationData
45 *
46 * @return int Notification ID
47 */
48 public function createNotification(Notification $notificationData): int
49 {
50 // Process images in message content before saving
51 $message = $notificationData->getMessage();
52 if (!empty($message)) {
53 $processedMessage = $this->imageService->processImagesInContent($message, 'notification');
54 $notificationData->setMessage($processedMessage);
55 }
56
57 // Create notification via repository and return new notification ID
58 return $this->notificationRepository->add($notificationData);
59 }
60
61 /**
62 * Get a specific notification by its ID.
63 *
64 * @param int $notificationId
65 *
66 * @return object NotificationEntity
67 *
68 * @throws NotFoundException If the notification is not found
69 */
70 public function getNotificationById(int $notificationId): object
71 {
72 $notification = $this->notificationRepository->getById($notificationId);
73
74 if (!$notification) {
75 throw new NotFoundException(
76 BackendStrings::getExceptionStrings()['notification_not_found']
77 );
78 }
79
80 return $notification;
81 }
82
83 /**
84 * Update an existing notification.
85 *
86 * @param int $notificationId
87 * @param Notification $notificationData
88 *
89 * @return bool
90 */
91 public function updateNotification(int $notificationId, Notification $notificationData): bool
92 {
93 // Process images in message content before saving
94 $message = $notificationData->getMessage();
95 if (!empty($message)) {
96 $processedMessage = $this->imageService->processImagesInContent($message, 'notification');
97 $notificationData->setMessage($processedMessage);
98 }
99
100 // Validate notification data before updating
101
102 return $this->notificationRepository->update($notificationId, $notificationData);
103 }
104
105 /**
106 * Delete a notification by its ID.
107 *
108 * @param int $notificationId
109 *
110 * @return int
111 */
112 public function deleteNotification(int $notificationId): int
113 {
114 return $this->notificationRepository->delete($notificationId);
115 }
116
117 /**
118 * Delete multiple notifications by their IDs.
119 *
120 * @param array<int> $notificationIds
121 *
122 * @return int Number of deleted notifications
123 */
124 public function deleteNotifications(array $notificationIds): int
125 {
126 return $this->notificationRepository->deleteMany($notificationIds);
127 }
128
129 /**
130 * Get notifications by form ID.
131 *
132 * @param int $formId
133 *
134 * @return array<Notification>
135 *
136 * @throws NotFoundException If no notifications are found
137 */
138 public function getNotificationsByFormId(int $formId): array
139 {
140 $notification = $this->notificationRepository->getAllById($formId);
141
142 if (!$notification) {
143 throw new NotFoundException(
144 BackendStrings::getExceptionStrings()['notification_not_found']
145 );
146 }
147
148 return $notification;
149 }
150
151 /**
152 * Get active notifications by form ID.
153 *
154 * @param int $formId
155 *
156 * @return array<Notification>
157 *
158 */
159 public function getActiveNotificationsByFormId(int $formId): array
160 {
161 $notification = $this->notificationRepository->getAllActiveById($formId);
162
163 // TODO Implement handling no notifications found, but do not return an error to the user
164 // For now, log the error if WP_DEBUG is enabled
165 if (!$notification && defined('WP_DEBUG') && WP_DEBUG) {
166 error_log(BackendStrings::getExceptionStrings()['active_notif_not_found']);
167 }
168
169 return $notification;
170 }
171
172 /**
173 * Delete notifications by multiple form IDs.
174 *
175 * @param array<int> $formIds
176 *
177 * @return int Number of deleted notifications
178 * @throws InvalidArgumentException If no form IDs are provided
179 */
180 public function deleteNotificationsByFormIds(array $formIds): int
181 {
182 if (empty($formIds)) {
183 throw new InvalidArgumentException(
184 BackendStrings::getExceptionStrings()['no_form_ids_provided']
185 );
186 }
187
188 return $this->notificationRepository->deleteManyByForeignKeyValues($formIds);
189 }
190
191 /**
192 * Duplicate notifications from one form to another.
193 *
194 * @param int $originalFormId
195 * @param int $newFormId
196 *
197 * @return void
198 * @throws NotFoundException
199 * @throws InvalidArgumentException
200 */
201 public function duplicateNotifications(int $originalFormId, int $newFormId): void
202 {
203 $notifications = $this->getNotificationsByFormId($originalFormId);
204 foreach ($notifications as $notification) {
205 $notificationData = $notification->toArray();
206 unset($notificationData['id']);
207 $notificationData['formId'] = $newFormId;
208
209 $this->createNotification(NotificationFactory::create($notificationData));
210 }
211 }
212
213 /**
214 * Search notifications with pagination and sorting
215 *
216 * @param array<string, mixed> $params
217 *
218 * @return array<string, mixed>
219 */
220 public function searchNotifications(array $params): array
221 {
222 return $this->notificationRepository->search($params);
223 }
224
225 /**
226 * Create a default notification for a form.
227 *
228 * @param int $formId
229 * @param string $formName
230 * @param string $adminEmail
231 *
232 * @return void
233 * @throws InvalidArgumentException|QueryExecutionException
234 */
235 public function createDefaultNotificationForForm(int $formId, string $formName, string $adminEmail): void
236 {
237 $defaultNotification = [
238 'formId' => $formId,
239 'name' => BackendStrings::getAllFormsStrings()['admin_notification_email'],
240 'sender' => $adminEmail,
241 'receiver' => $adminEmail,
242 'enabled' => 1,
243 'subject' => $formName,
244 'message' => '{{all_data}}',
245 'smartLogic' => 0,
246 ];
247 $notification = NotificationFactory::create($defaultNotification);
248 $notificationId = $this->createNotification($notification);
249 if (!$notificationId) {
250 throw new QueryExecutionException(
251 BackendStrings::getSettingsFormBuilderStrings()['failed_to_create_default_notification']
252 );
253 }
254 $notification->setId($notificationId);
255 }
256
257 /**
258 * Process notifications for the form.
259 *
260 * @param int $formId
261 * @param array<mixed> $submissionData
262 * @param array<string, mixed> $fieldData
263 * @param array<string, mixed> $generalData
264 * @param MailerService $mailerService
265 * @param array<string, string> $fieldLabels
266 * @return bool
267 */
268 public function processNotifications(
269 int $formId,
270 array $submissionData,
271 array $fieldData,
272 array $generalData,
273 MailerService $mailerService,
274 array $fieldLabels = []
275 ): bool {
276 $activeNotifications = $this->getActiveNotificationsByFormId($formId);
277 $exceptionStrings = BackendStrings::getExceptionStrings();
278 foreach ($activeNotifications as $notification) {
279 if (empty($notification->getNotificationContact()->getReceiver())) {
280 continue;
281 }
282
283 // Resolve sender and receiver
284 $sender = $this->resolveAddress(
285 $notification->getNotificationContact()->getSender(),
286 $fieldData,
287 $generalData,
288 $fieldLabels
289 );
290
291 $receiver = $this->resolveAddress(
292 $notification->getNotificationContact()->getReceiver(),
293 $fieldData,
294 $generalData,
295 $fieldLabels
296 );
297
298 // Build subject and sanitized message
299 $subject = PlaceholderService::replacePlaceholders(
300 $notification->getSubject(),
301 $fieldData,
302 $generalData,
303 $fieldLabels,
304 $notification
305 );
306
307 $safeMessage = $this->buildSafeMessage(
308 $notification->getMessage(),
309 $fieldData,
310 $generalData,
311 $fieldLabels,
312 $notification
313 );
314
315 // Resolve replyTo and validate; fallback to sender if empty
316 $replyTo = $this->resolveAddress(
317 $notification->getNotificationContact()->getReplyTo(),
318 $fieldData,
319 $generalData,
320 $fieldLabels
321 );
322 if (empty($replyTo) && !empty($sender)) {
323 $replyTo = $sender;
324 }
325
326 if (empty($sender) || empty($receiver)) {
327 error_log(
328 $exceptionStrings['error_log_invalid_email'] . $sender . ' ' . $receiver
329 );
330 return false;
331 }
332
333 // Apply resolved values back to the notification
334 $notification->getNotificationContact()->setSender($sender);
335 $notification->getNotificationContact()->setReceiver($receiver);
336 $notification->getNotificationContact()->setReplyTo($replyTo);
337 $notification->setSubject($subject);
338 $notification->setMessage($safeMessage);
339
340 // Send notification
341 $sent = $mailerService->sendEmail($notification, $submissionData);
342 if (!$sent) {
343 error_log($exceptionStrings['error_log_failed_send'] . ' ' . $formId);
344 }
345 }
346
347 return true;
348 }
349
350 /**
351 * Resolve an email address template that may contain placeholders
352 *
353 * Returns the original replaced string when valid, or empty string when invalid.
354 *
355 * @param string $template
356 * @param array<string,mixed> $fieldData
357 * @param array<string,mixed> $generalData
358 * @param array<string,string> $fieldLabels
359 * @return string
360 */
361 private function resolveAddress(string $template, array $fieldData, array $generalData, array $fieldLabels): string
362 {
363 $rawReplaced = PlaceholderService::replacePlaceholders(
364 $template,
365 $fieldData,
366 $generalData,
367 $fieldLabels
368 );
369
370 $emailCandidate = $rawReplaced;
371 if (preg_match('/<([^>]+)>/', $rawReplaced, $match)) {
372 $emailCandidate = trim($match[1]);
373 }
374
375 return Sanitizer::isValidEmail($emailCandidate) ? $rawReplaced : '';
376 }
377
378 /**
379 * Build a sanitized HTML message from a template with placeholders.
380 *
381 * @param string $template
382 * @param array<string,mixed> $fieldData
383 * @param array<string,mixed> $generalData
384 * @param array<string,string> $fieldLabels
385 * @param Notification $notification
386 * @return string
387 */
388 private function buildSafeMessage(
389 string $template,
390 array $fieldData,
391 array $generalData,
392 array $fieldLabels,
393 Notification $notification
394 ): string {
395 $message = PlaceholderService::replacePlaceholders(
396 $template,
397 $fieldData,
398 $generalData,
399 $fieldLabels,
400 $notification
401 );
402
403 return Sanitizer::sanitizeHtmlContent($message);
404 }
405 }
406