Total Complexity | 69 |
Total Lines | 409 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like EmailProvider often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use EmailProvider, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
53 | class EmailProvider extends AbstractProvider { |
||
54 | /** @var string */ |
||
55 | public const NOTIFICATION_TYPE = 'EMAIL'; |
||
56 | |||
57 | private IMailer $mailer; |
||
58 | |||
59 | public function __construct(IConfig $config, |
||
60 | IMailer $mailer, |
||
61 | LoggerInterface $logger, |
||
62 | L10NFactory $l10nFactory, |
||
63 | IURLGenerator $urlGenerator) { |
||
64 | parent::__construct($logger, $l10nFactory, $urlGenerator, $config); |
||
65 | $this->mailer = $mailer; |
||
66 | } |
||
67 | |||
68 | /** |
||
69 | * Send out notification via email |
||
70 | * |
||
71 | * @param VEvent $vevent |
||
72 | * @param string $calendarDisplayName |
||
73 | * @param string[] $principalEmailAddresses |
||
74 | * @param array $users |
||
75 | * @throws \Exception |
||
76 | */ |
||
77 | public function send(VEvent $vevent, |
||
78 | string $calendarDisplayName, |
||
79 | array $principalEmailAddresses, |
||
80 | array $users = []):void { |
||
81 | $fallbackLanguage = $this->getFallbackLanguage(); |
||
82 | |||
83 | $organizerEmailAddress = null; |
||
84 | if (isset($vevent->ORGANIZER)) { |
||
85 | $organizerEmailAddress = $this->getEMailAddressOfAttendee($vevent->ORGANIZER); |
||
86 | } |
||
87 | |||
88 | $emailAddressesOfSharees = $this->getEMailAddressesOfAllUsersWithWriteAccessToCalendar($users); |
||
89 | $emailAddressesOfAttendees = []; |
||
90 | if (count($principalEmailAddresses) === 0 |
||
91 | || ($organizerEmailAddress && in_array($organizerEmailAddress, $principalEmailAddresses, true)) |
||
92 | ) { |
||
93 | $emailAddressesOfAttendees = $this->getAllEMailAddressesFromEvent($vevent); |
||
94 | } |
||
95 | |||
96 | // Quote from php.net: |
||
97 | // If the input arrays have the same string keys, then the later value for that key will overwrite the previous one. |
||
98 | // => if there are duplicate email addresses, it will always take the system value |
||
99 | $emailAddresses = array_merge( |
||
100 | $emailAddressesOfAttendees, |
||
101 | $emailAddressesOfSharees |
||
102 | ); |
||
103 | |||
104 | $sortedByLanguage = $this->sortEMailAddressesByLanguage($emailAddresses, $fallbackLanguage); |
||
105 | $organizer = $this->getOrganizerEMailAndNameFromEvent($vevent); |
||
106 | |||
107 | foreach ($sortedByLanguage as $lang => $emailAddresses) { |
||
108 | if (!$this->hasL10NForLang($lang)) { |
||
109 | $lang = $fallbackLanguage; |
||
110 | } |
||
111 | $l10n = $this->getL10NForLang($lang); |
||
112 | $fromEMail = \OCP\Util::getDefaultEmailAddress('reminders-noreply'); |
||
113 | |||
114 | $template = $this->mailer->createEMailTemplate('dav.calendarReminder'); |
||
115 | $template->addHeader(); |
||
116 | $this->addSubjectAndHeading($template, $l10n, $vevent); |
||
117 | $this->addBulletList($template, $l10n, $calendarDisplayName, $vevent); |
||
118 | $template->addFooter(); |
||
119 | |||
120 | foreach ($emailAddresses as $emailAddress) { |
||
121 | if (!$this->mailer->validateMailAddress($emailAddress)) { |
||
122 | $this->logger->error('Email address {address} for reminder notification is incorrect', ['app' => 'dav', 'address' => $emailAddress]); |
||
123 | continue; |
||
124 | } |
||
125 | |||
126 | $message = $this->mailer->createMessage(); |
||
127 | $message->setFrom([$fromEMail]); |
||
128 | if ($organizer) { |
||
129 | $message->setReplyTo($organizer); |
||
130 | } |
||
131 | $message->setTo([$emailAddress]); |
||
132 | $message->useTemplate($template); |
||
133 | |||
134 | try { |
||
135 | $failed = $this->mailer->send($message); |
||
136 | if ($failed) { |
||
|
|||
137 | $this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]); |
||
138 | } |
||
139 | } catch (\Exception $ex) { |
||
140 | $this->logger->error($ex->getMessage(), ['app' => 'dav', 'exception' => $ex]); |
||
141 | } |
||
142 | } |
||
143 | } |
||
144 | } |
||
145 | |||
146 | /** |
||
147 | * @param IEMailTemplate $template |
||
148 | * @param IL10N $l10n |
||
149 | * @param VEvent $vevent |
||
150 | */ |
||
151 | private function addSubjectAndHeading(IEMailTemplate $template, IL10N $l10n, VEvent $vevent):void { |
||
152 | $template->setSubject('Notification: ' . $this->getTitleFromVEvent($vevent, $l10n)); |
||
153 | $template->addHeading($this->getTitleFromVEvent($vevent, $l10n)); |
||
154 | } |
||
155 | |||
156 | /** |
||
157 | * @param IEMailTemplate $template |
||
158 | * @param IL10N $l10n |
||
159 | * @param string $calendarDisplayName |
||
160 | * @param array $eventData |
||
161 | */ |
||
162 | private function addBulletList(IEMailTemplate $template, |
||
163 | IL10N $l10n, |
||
164 | string $calendarDisplayName, |
||
165 | VEvent $vevent):void { |
||
166 | $template->addBodyListItem($calendarDisplayName, $l10n->t('Calendar:'), |
||
167 | $this->getAbsoluteImagePath('actions/info.png')); |
||
168 | |||
169 | $template->addBodyListItem($this->generateDateString($l10n, $vevent), $l10n->t('Date:'), |
||
170 | $this->getAbsoluteImagePath('places/calendar.png')); |
||
171 | |||
172 | if (isset($vevent->LOCATION)) { |
||
173 | $template->addBodyListItem((string) $vevent->LOCATION, $l10n->t('Where:'), |
||
174 | $this->getAbsoluteImagePath('actions/address.png')); |
||
175 | } |
||
176 | if (isset($vevent->DESCRIPTION)) { |
||
177 | $template->addBodyListItem((string) $vevent->DESCRIPTION, $l10n->t('Description:'), |
||
178 | $this->getAbsoluteImagePath('actions/more.png')); |
||
179 | } |
||
180 | } |
||
181 | |||
182 | private function getAbsoluteImagePath(string $path):string { |
||
183 | return $this->urlGenerator->getAbsoluteURL( |
||
184 | $this->urlGenerator->imagePath('core', $path) |
||
185 | ); |
||
186 | } |
||
187 | |||
188 | /** |
||
189 | * @param VEvent $vevent |
||
190 | * @return array|null |
||
191 | */ |
||
192 | private function getOrganizerEMailAndNameFromEvent(VEvent $vevent):?array { |
||
193 | if (!$vevent->ORGANIZER) { |
||
194 | return null; |
||
195 | } |
||
196 | |||
197 | $organizer = $vevent->ORGANIZER; |
||
198 | if (strcasecmp($organizer->getValue(), 'mailto:') !== 0) { |
||
199 | return null; |
||
200 | } |
||
201 | |||
202 | $organizerEMail = substr($organizer->getValue(), 7); |
||
203 | |||
204 | if (!$this->mailer->validateMailAddress($organizerEMail)) { |
||
205 | return null; |
||
206 | } |
||
207 | |||
208 | $name = $organizer->offsetGet('CN'); |
||
209 | if ($name instanceof Parameter) { |
||
210 | return [$organizerEMail => $name]; |
||
211 | } |
||
212 | |||
213 | return [$organizerEMail]; |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * @param array<string, array{LANG?: string}> $emails |
||
218 | * @return array<string, string[]> |
||
219 | */ |
||
220 | private function sortEMailAddressesByLanguage(array $emails, |
||
221 | string $defaultLanguage):array { |
||
222 | $sortedByLanguage = []; |
||
223 | |||
224 | foreach ($emails as $emailAddress => $parameters) { |
||
225 | if (isset($parameters['LANG'])) { |
||
226 | $lang = $parameters['LANG']; |
||
227 | } else { |
||
228 | $lang = $defaultLanguage; |
||
229 | } |
||
230 | |||
231 | if (!isset($sortedByLanguage[$lang])) { |
||
232 | $sortedByLanguage[$lang] = []; |
||
233 | } |
||
234 | |||
235 | $sortedByLanguage[$lang][] = $emailAddress; |
||
236 | } |
||
237 | |||
238 | return $sortedByLanguage; |
||
239 | } |
||
240 | |||
241 | /** |
||
242 | * @param VEvent $vevent |
||
243 | * @return array<string, array{LANG?: string}> |
||
244 | */ |
||
245 | private function getAllEMailAddressesFromEvent(VEvent $vevent):array { |
||
246 | $emailAddresses = []; |
||
247 | |||
248 | if (isset($vevent->ATTENDEE)) { |
||
249 | foreach ($vevent->ATTENDEE as $attendee) { |
||
250 | if (!($attendee instanceof VObject\Property)) { |
||
251 | continue; |
||
252 | } |
||
253 | |||
254 | $cuType = $this->getCUTypeOfAttendee($attendee); |
||
255 | if (\in_array($cuType, ['RESOURCE', 'ROOM', 'UNKNOWN'])) { |
||
256 | // Don't send emails to things |
||
257 | continue; |
||
258 | } |
||
259 | |||
260 | $partstat = $this->getPartstatOfAttendee($attendee); |
||
261 | if ($partstat === 'DECLINED') { |
||
262 | // Don't send out emails to people who declined |
||
263 | continue; |
||
264 | } |
||
265 | if ($partstat === 'DELEGATED') { |
||
266 | $delegates = $attendee->offsetGet('DELEGATED-TO'); |
||
267 | if (!($delegates instanceof VObject\Parameter)) { |
||
268 | continue; |
||
269 | } |
||
270 | |||
271 | $emailAddressesOfDelegates = $delegates->getParts(); |
||
272 | foreach ($emailAddressesOfDelegates as $addressesOfDelegate) { |
||
273 | if (strcasecmp($addressesOfDelegate, 'mailto:') === 0) { |
||
274 | $delegateEmail = substr($addressesOfDelegate, 7); |
||
275 | if ($this->mailer->validateMailAddress($delegateEmail)) { |
||
276 | $emailAddresses[$delegateEmail] = []; |
||
277 | } |
||
278 | } |
||
279 | } |
||
280 | |||
281 | continue; |
||
282 | } |
||
283 | |||
284 | $emailAddressOfAttendee = $this->getEMailAddressOfAttendee($attendee); |
||
285 | if ($emailAddressOfAttendee !== null) { |
||
286 | $properties = []; |
||
287 | |||
288 | $langProp = $attendee->offsetGet('LANG'); |
||
289 | if ($langProp instanceof VObject\Parameter && $langProp->getValue() !== null) { |
||
290 | $properties['LANG'] = $langProp->getValue(); |
||
291 | } |
||
292 | |||
293 | $emailAddresses[$emailAddressOfAttendee] = $properties; |
||
294 | } |
||
295 | } |
||
296 | } |
||
297 | |||
298 | if (isset($vevent->ORGANIZER) && $this->hasAttendeeMailURI($vevent->ORGANIZER)) { |
||
299 | $organizerEmailAddress = $this->getEMailAddressOfAttendee($vevent->ORGANIZER); |
||
300 | if ($organizerEmailAddress !== null) { |
||
301 | $emailAddresses[$organizerEmailAddress] = []; |
||
302 | } |
||
303 | } |
||
304 | |||
305 | return $emailAddresses; |
||
306 | } |
||
307 | |||
308 | private function getCUTypeOfAttendee(VObject\Property $attendee):string { |
||
309 | $cuType = $attendee->offsetGet('CUTYPE'); |
||
310 | if ($cuType instanceof VObject\Parameter) { |
||
311 | return strtoupper($cuType->getValue()); |
||
312 | } |
||
313 | |||
314 | return 'INDIVIDUAL'; |
||
315 | } |
||
316 | |||
317 | private function getPartstatOfAttendee(VObject\Property $attendee):string { |
||
318 | $partstat = $attendee->offsetGet('PARTSTAT'); |
||
319 | if ($partstat instanceof VObject\Parameter) { |
||
320 | return strtoupper($partstat->getValue()); |
||
321 | } |
||
322 | |||
323 | return 'NEEDS-ACTION'; |
||
324 | } |
||
325 | |||
326 | private function hasAttendeeMailURI(VObject\Property $attendee): bool { |
||
328 | } |
||
329 | |||
330 | private function getEMailAddressOfAttendee(VObject\Property $attendee): ?string { |
||
331 | if (!$this->hasAttendeeMailURI($attendee)) { |
||
332 | return null; |
||
333 | } |
||
334 | $attendeeEMail = substr($attendee->getValue(), 7); |
||
335 | if (!$this->mailer->validateMailAddress($attendeeEMail)) { |
||
336 | return null; |
||
337 | } |
||
338 | |||
339 | return $attendeeEMail; |
||
340 | } |
||
341 | |||
342 | /** |
||
343 | * @param IUser[] $users |
||
344 | * @return array<string, array{LANG?: string}> |
||
345 | */ |
||
346 | private function getEMailAddressesOfAllUsersWithWriteAccessToCalendar(array $users):array { |
||
364 | } |
||
365 | |||
366 | /** |
||
367 | * @throws \Exception |
||
368 | */ |
||
369 | private function generateDateString(IL10N $l10n, VEvent $vevent): string { |
||
433 | } |
||
434 | |||
435 | private function isDayEqual(DateTime $dtStart, |
||
436 | DateTime $dtEnd):bool { |
||
437 | return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d'); |
||
438 | } |
||
439 | |||
440 | private function getWeekDayName(IL10N $l10n, DateTime $dt):string { |
||
442 | } |
||
443 | |||
444 | private function getDateString(IL10N $l10n, DateTime $dt):string { |
||
445 | return (string)$l10n->l('date', $dt, ['width' => 'medium']); |
||
446 | } |
||
447 | |||
448 | private function getDateTimeString(IL10N $l10n, DateTime $dt):string { |
||
449 | return (string)$l10n->l('datetime', $dt, ['width' => 'medium|short']); |
||
450 | } |
||
451 | |||
452 | private function getTimeString(IL10N $l10n, DateTime $dt):string { |
||
454 | } |
||
455 | |||
456 | private function getTitleFromVEvent(VEvent $vevent, IL10N $l10n):string { |
||
462 | } |
||
463 | } |
||
464 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.