PluginProbe
The Innovative Form Builder – IvyForms / 0.8
The Innovative Form Builder – IvyForms v0.8
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 1.2 1.3
ivyforms / backend / src / Services / Notification / NotificationService.php

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

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