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

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