PluginProbe
The Innovative Form Builder – IvyForms / 1.1
The Innovative Form Builder – IvyForms v1.1
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.1, at backend/src/Services/Notification/NotificationService.php

427 lines 13.2 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' => [
246 'enabled' => false,
247 'match' => 'any',
248 'rules' => [],
249 ],
250 ];
251 $notification = NotificationFactory::create($defaultNotification);
252 $notificationId = $this->createNotification($notification);
253 if (!$notificationId) {
254 throw new QueryExecutionException(
255 BackendStrings::getSettingsFormBuilderStrings()['failed_to_create_default_notification']
256 );
257 }
258 $notification->setId($notificationId);
259 }
260
261 /**
262 * Process notifications for the form.
263 *
264 * @param int $formId
265 * @param array<mixed> $submissionData
266 * @param array<mixed> $formFields
267 * @param array<string, mixed> $fieldData
268 * @param array<string, mixed> $generalData
269 * @param MailerService $mailerService
270 * @param array<string, string> $fieldLabels
271 * @return bool
272 */
273 public function processNotifications(
274 int $formId,
275 array $submissionData,
276 array $formFields,
277 array $fieldData,
278 array $generalData,
279 MailerService $mailerService,
280 array $fieldLabels = []
281 ): bool {
282 $activeNotifications = $this->getActiveNotificationsByFormId($formId);
283 $logicFormFields = $formFields;
284 $exceptionStrings = BackendStrings::getExceptionStrings();
285 foreach ($activeNotifications as $notification) {
286 if (empty($notification->getNotificationContact()->getReceiver())) {
287 continue;
288 }
289
290 $shouldSend = apply_filters(
291 'ivyforms/notification/should_send',
292 true,
293 $notification->getSmartLogic(),
294 $submissionData,
295 $logicFormFields,
296 $notification,
297 $formId
298 );
299
300 if (!$shouldSend) {
301 continue;
302 }
303
304 // Resolve sender and receiver
305 $sender = $this->resolveAddress(
306 $notification->getNotificationContact()->getSender(),
307 $fieldData,
308 $generalData,
309 $fieldLabels
310 );
311
312 $receiver = $this->resolveAddress(
313 $notification->getNotificationContact()->getReceiver(),
314 $fieldData,
315 $generalData,
316 $fieldLabels
317 );
318
319 // Build subject and sanitized message
320 $subject = PlaceholderService::replacePlaceholders(
321 $notification->getSubject(),
322 $fieldData,
323 $generalData,
324 $fieldLabels,
325 $notification
326 );
327
328 $safeMessage = $this->buildSafeMessage(
329 $notification->getMessage(),
330 $fieldData,
331 $generalData,
332 $fieldLabels,
333 $notification
334 );
335
336 // Resolve replyTo and validate; fallback to sender if empty
337 $replyTo = $this->resolveAddress(
338 $notification->getNotificationContact()->getReplyTo(),
339 $fieldData,
340 $generalData,
341 $fieldLabels
342 );
343 if (empty($replyTo) && !empty($sender)) {
344 $replyTo = $sender;
345 }
346
347 if (empty($sender) || empty($receiver)) {
348 error_log(
349 $exceptionStrings['error_log_invalid_email'] . $sender . ' ' . $receiver
350 );
351 return false;
352 }
353
354 // Apply resolved values back to the notification
355 $notification->getNotificationContact()->setSender($sender);
356 $notification->getNotificationContact()->setReceiver($receiver);
357 $notification->getNotificationContact()->setReplyTo($replyTo);
358 $notification->setSubject($subject);
359 $notification->setMessage($safeMessage);
360
361 // Send notification
362 $sent = $mailerService->sendEmail($notification, $submissionData);
363 if (!$sent) {
364 error_log($exceptionStrings['error_log_failed_send'] . ' ' . $formId);
365 }
366 }
367
368 return true;
369 }
370
371 /**
372 * Resolve an email address template that may contain placeholders
373 *
374 * Returns the original replaced string when valid, or empty string when invalid.
375 *
376 * @param string $template
377 * @param array<string,mixed> $fieldData
378 * @param array<string,mixed> $generalData
379 * @param array<string,string> $fieldLabels
380 * @return string
381 */
382 private function resolveAddress(string $template, array $fieldData, array $generalData, array $fieldLabels): string
383 {
384 $rawReplaced = PlaceholderService::replacePlaceholders(
385 $template,
386 $fieldData,
387 $generalData,
388 $fieldLabels
389 );
390
391 $emailCandidate = $rawReplaced;
392 if (preg_match('/<([^>]+)>/', $rawReplaced, $match)) {
393 $emailCandidate = trim($match[1]);
394 }
395
396 return Sanitizer::isValidEmail($emailCandidate) ? $rawReplaced : '';
397 }
398
399 /**
400 * Build a sanitized HTML message from a template with placeholders.
401 *
402 * @param string $template
403 * @param array<string,mixed> $fieldData
404 * @param array<string,mixed> $generalData
405 * @param array<string,string> $fieldLabels
406 * @param Notification $notification
407 * @return string
408 */
409 private function buildSafeMessage(
410 string $template,
411 array $fieldData,
412 array $generalData,
413 array $fieldLabels,
414 Notification $notification
415 ): string {
416 $message = PlaceholderService::replacePlaceholders(
417 $template,
418 $fieldData,
419 $generalData,
420 $fieldLabels,
421 $notification
422 );
423
424 return Sanitizer::sanitizeHtmlContent($message);
425 }
426 }
427