Total Complexity | 60 |
Total Lines | 455 |
Duplicated Lines | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 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 |
||
47 | class EmailProvider extends AbstractProvider { |
||
48 | |||
49 | /** @var string */ |
||
50 | public const NOTIFICATION_TYPE = 'EMAIL'; |
||
51 | |||
52 | /** @var IMailer */ |
||
53 | private $mailer; |
||
54 | |||
55 | /** |
||
56 | * @param IConfig $config |
||
57 | * @param IMailer $mailer |
||
58 | * @param ILogger $logger |
||
59 | * @param L10NFactory $l10nFactory |
||
60 | * @param IUrlGenerator $urlGenerator |
||
61 | */ |
||
62 | public function __construct(IConfig $config, |
||
63 | IMailer $mailer, |
||
64 | ILogger $logger, |
||
65 | L10NFactory $l10nFactory, |
||
66 | IURLGenerator $urlGenerator) { |
||
67 | parent::__construct($logger, $l10nFactory, $urlGenerator, $config); |
||
68 | $this->mailer = $mailer; |
||
69 | } |
||
70 | |||
71 | /** |
||
72 | * Send out notification via email |
||
73 | * |
||
74 | * @param VEvent $vevent |
||
75 | * @param string $calendarDisplayName |
||
76 | * @param array $users |
||
77 | * @throws \Exception |
||
78 | */ |
||
79 | public function send(VEvent $vevent, |
||
80 | string $calendarDisplayName, |
||
81 | array $users=[]):void { |
||
82 | $fallbackLanguage = $this->getFallbackLanguage(); |
||
83 | |||
84 | $emailAddressesOfSharees = $this->getEMailAddressesOfAllUsersWithWriteAccessToCalendar($users); |
||
85 | $emailAddressesOfAttendees = $this->getAllEMailAddressesFromEvent($vevent); |
||
86 | |||
87 | // Quote from php.net: |
||
88 | // If the input arrays have the same string keys, then the later value for that key will overwrite the previous one. |
||
89 | // => if there are duplicate email addresses, it will always take the system value |
||
90 | $emailAddresses = array_merge( |
||
91 | $emailAddressesOfAttendees, |
||
92 | $emailAddressesOfSharees |
||
93 | ); |
||
94 | |||
95 | $sortedByLanguage = $this->sortEMailAddressesByLanguage($emailAddresses, $fallbackLanguage); |
||
96 | $organizer = $this->getOrganizerEMailAndNameFromEvent($vevent); |
||
97 | |||
98 | foreach($sortedByLanguage as $lang => $emailAddresses) { |
||
99 | if (!$this->hasL10NForLang($lang)) { |
||
100 | $lang = $fallbackLanguage; |
||
101 | } |
||
102 | $l10n = $this->getL10NForLang($lang); |
||
103 | $fromEMail = \OCP\Util::getDefaultEmailAddress('reminders-noreply'); |
||
104 | |||
105 | $template = $this->mailer->createEMailTemplate('dav.calendarReminder'); |
||
106 | $template->addHeader(); |
||
107 | $this->addSubjectAndHeading($template, $l10n, $vevent); |
||
108 | $this->addBulletList($template, $l10n, $calendarDisplayName, $vevent); |
||
109 | $template->addFooter(); |
||
110 | |||
111 | foreach ($emailAddresses as $emailAddress) { |
||
112 | $message = $this->mailer->createMessage(); |
||
113 | $message->setFrom([$fromEMail]); |
||
114 | if ($organizer) { |
||
115 | $message->setReplyTo($organizer); |
||
116 | } |
||
117 | $message->setTo([$emailAddress]); |
||
118 | $message->useTemplate($template); |
||
119 | |||
120 | try { |
||
121 | $failed = $this->mailer->send($message); |
||
122 | if ($failed) { |
||
|
|||
123 | $this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]); |
||
124 | } |
||
125 | } catch (\Exception $ex) { |
||
126 | $this->logger->logException($ex, ['app' => 'dav']); |
||
127 | } |
||
128 | } |
||
129 | } |
||
130 | } |
||
131 | |||
132 | /** |
||
133 | * @param IEMailTemplate $template |
||
134 | * @param IL10N $l10n |
||
135 | * @param VEvent $vevent |
||
136 | */ |
||
137 | private function addSubjectAndHeading(IEMailTemplate $template, IL10N $l10n, VEvent $vevent):void { |
||
138 | $template->setSubject('Notification: ' . $this->getTitleFromVEvent($vevent, $l10n)); |
||
139 | $template->addHeading($this->getTitleFromVEvent($vevent, $l10n)); |
||
140 | } |
||
141 | |||
142 | /** |
||
143 | * @param IEMailTemplate $template |
||
144 | * @param IL10N $l10n |
||
145 | * @param string $calendarDisplayName |
||
146 | * @param array $eventData |
||
147 | */ |
||
148 | private function addBulletList(IEMailTemplate $template, |
||
149 | IL10N $l10n, |
||
150 | string $calendarDisplayName, |
||
151 | VEvent $vevent):void { |
||
152 | $template->addBodyListItem($calendarDisplayName, $l10n->t('Calendar:'), |
||
153 | $this->getAbsoluteImagePath('actions/info.svg')); |
||
154 | |||
155 | $template->addBodyListItem($this->generateDateString($l10n, $vevent), $l10n->t('Date:'), |
||
156 | $this->getAbsoluteImagePath('places/calendar.svg')); |
||
157 | |||
158 | if (isset($vevent->LOCATION)) { |
||
159 | $template->addBodyListItem((string) $vevent->LOCATION, $l10n->t('Where:'), |
||
160 | $this->getAbsoluteImagePath('actions/address.svg')); |
||
161 | } |
||
162 | if (isset($vevent->DESCRIPTION)) { |
||
163 | $template->addBodyListItem((string) $vevent->DESCRIPTION, $l10n->t('Description:'), |
||
164 | $this->getAbsoluteImagePath('actions/more.svg')); |
||
165 | } |
||
166 | } |
||
167 | |||
168 | /** |
||
169 | * @param string $path |
||
170 | * @return string |
||
171 | */ |
||
172 | private function getAbsoluteImagePath(string $path):string { |
||
173 | return $this->urlGenerator->getAbsoluteURL( |
||
174 | $this->urlGenerator->imagePath('core', $path) |
||
175 | ); |
||
176 | } |
||
177 | |||
178 | /** |
||
179 | * @param VEvent $vevent |
||
180 | * @return array|null |
||
181 | */ |
||
182 | private function getOrganizerEMailAndNameFromEvent(VEvent $vevent):?array { |
||
183 | if (!$vevent->ORGANIZER) { |
||
184 | return null; |
||
185 | } |
||
186 | |||
187 | $organizer = $vevent->ORGANIZER; |
||
188 | if (strcasecmp($organizer->getValue(), 'mailto:') !== 0) { |
||
189 | return null; |
||
190 | } |
||
191 | |||
192 | $organizerEMail = substr($organizer->getValue(), 7); |
||
193 | |||
194 | $name = $organizer->offsetGet('CN'); |
||
195 | if ($name instanceof Parameter) { |
||
196 | return [$organizerEMail => $name]; |
||
197 | } |
||
198 | |||
199 | return [$organizerEMail]; |
||
200 | } |
||
201 | |||
202 | /** |
||
203 | * @param array $emails |
||
204 | * @param string $defaultLanguage |
||
205 | * @return array |
||
206 | */ |
||
207 | private function sortEMailAddressesByLanguage(array $emails, |
||
208 | string $defaultLanguage):array { |
||
209 | $sortedByLanguage = []; |
||
210 | |||
211 | foreach($emails as $emailAddress => $parameters) { |
||
212 | if (isset($parameters['LANG'])) { |
||
213 | $lang = $parameters['LANG']; |
||
214 | } else { |
||
215 | $lang = $defaultLanguage; |
||
216 | } |
||
217 | |||
218 | if (!isset($sortedByLanguage[$lang])) { |
||
219 | $sortedByLanguage[$lang] = []; |
||
220 | } |
||
221 | |||
222 | $sortedByLanguage[$lang][] = $emailAddress; |
||
223 | } |
||
224 | |||
225 | return $sortedByLanguage; |
||
226 | } |
||
227 | |||
228 | /** |
||
229 | * @param VEvent $vevent |
||
230 | * @return array |
||
231 | */ |
||
232 | private function getAllEMailAddressesFromEvent(VEvent $vevent):array { |
||
233 | $emailAddresses = []; |
||
234 | |||
235 | if (isset($vevent->ATTENDEE)) { |
||
236 | foreach ($vevent->ATTENDEE as $attendee) { |
||
237 | if (!($attendee instanceof VObject\Property)) { |
||
238 | continue; |
||
239 | } |
||
240 | |||
241 | $cuType = $this->getCUTypeOfAttendee($attendee); |
||
242 | if (\in_array($cuType, ['RESOURCE', 'ROOM', 'UNKNOWN'])) { |
||
243 | // Don't send emails to things |
||
244 | continue; |
||
245 | } |
||
246 | |||
247 | $partstat = $this->getPartstatOfAttendee($attendee); |
||
248 | if ($partstat === 'DECLINED') { |
||
249 | // Don't send out emails to people who declined |
||
250 | continue; |
||
251 | } |
||
252 | if ($partstat === 'DELEGATED') { |
||
253 | $delegates = $attendee->offsetGet('DELEGATED-TO'); |
||
254 | if (!($delegates instanceof VObject\Parameter)) { |
||
255 | continue; |
||
256 | } |
||
257 | |||
258 | $emailAddressesOfDelegates = $delegates->getParts(); |
||
259 | foreach($emailAddressesOfDelegates as $addressesOfDelegate) { |
||
260 | if (strcasecmp($addressesOfDelegate, 'mailto:') === 0) { |
||
261 | $emailAddresses[substr($addressesOfDelegate, 7)] = []; |
||
262 | } |
||
263 | } |
||
264 | |||
265 | continue; |
||
266 | } |
||
267 | |||
268 | $emailAddressOfAttendee = $this->getEMailAddressOfAttendee($attendee); |
||
269 | if ($emailAddressOfAttendee !== null) { |
||
270 | $properties = []; |
||
271 | |||
272 | $langProp = $attendee->offsetGet('LANG'); |
||
273 | if ($langProp instanceof VObject\Parameter) { |
||
274 | $properties['LANG'] = $langProp->getValue(); |
||
275 | } |
||
276 | |||
277 | $emailAddresses[$emailAddressOfAttendee] = $properties; |
||
278 | } |
||
279 | } |
||
280 | } |
||
281 | |||
282 | if (isset($vevent->ORGANIZER) && $this->hasAttendeeMailURI($vevent->ORGANIZER)) { |
||
283 | $emailAddresses[$this->getEMailAddressOfAttendee($vevent->ORGANIZER)] = []; |
||
284 | } |
||
285 | |||
286 | return $emailAddresses; |
||
287 | } |
||
288 | |||
289 | |||
290 | |||
291 | /** |
||
292 | * @param VObject\Property $attendee |
||
293 | * @return string |
||
294 | */ |
||
295 | private function getCUTypeOfAttendee(VObject\Property $attendee):string { |
||
296 | $cuType = $attendee->offsetGet('CUTYPE'); |
||
297 | if ($cuType instanceof VObject\Parameter) { |
||
298 | return strtoupper($cuType->getValue()); |
||
299 | } |
||
300 | |||
301 | return 'INDIVIDUAL'; |
||
302 | } |
||
303 | |||
304 | /** |
||
305 | * @param VObject\Property $attendee |
||
306 | * @return string |
||
307 | */ |
||
308 | private function getPartstatOfAttendee(VObject\Property $attendee):string { |
||
309 | $partstat = $attendee->offsetGet('PARTSTAT'); |
||
310 | if ($partstat instanceof VObject\Parameter) { |
||
311 | return strtoupper($partstat->getValue()); |
||
312 | } |
||
313 | |||
314 | return 'NEEDS-ACTION'; |
||
315 | } |
||
316 | |||
317 | /** |
||
318 | * @param VObject\Property $attendee |
||
319 | * @return bool |
||
320 | */ |
||
321 | private function hasAttendeeMailURI(VObject\Property $attendee):bool { |
||
323 | } |
||
324 | |||
325 | /** |
||
326 | * @param VObject\Property $attendee |
||
327 | * @return string|null |
||
328 | */ |
||
329 | private function getEMailAddressOfAttendee(VObject\Property $attendee):?string { |
||
330 | if (!$this->hasAttendeeMailURI($attendee)) { |
||
331 | return null; |
||
332 | } |
||
333 | |||
334 | return substr($attendee->getValue(), 7); |
||
335 | } |
||
336 | |||
337 | /** |
||
338 | * @param array $users |
||
339 | * @return array |
||
340 | */ |
||
341 | private function getEMailAddressesOfAllUsersWithWriteAccessToCalendar(array $users):array { |
||
342 | $emailAddresses = []; |
||
343 | |||
344 | foreach($users as $user) { |
||
345 | $emailAddress = $user->getEMailAddress(); |
||
346 | if ($emailAddress) { |
||
347 | $lang = $this->getLangForUser($user); |
||
348 | if ($lang) { |
||
349 | $emailAddresses[$emailAddress] = [ |
||
350 | 'LANG' => $lang, |
||
351 | ]; |
||
352 | } else { |
||
353 | $emailAddresses[$emailAddress] = []; |
||
354 | } |
||
355 | } |
||
356 | } |
||
357 | |||
358 | return $emailAddresses; |
||
359 | } |
||
360 | |||
361 | /** |
||
362 | * @param IUser $user |
||
363 | * @return string |
||
364 | */ |
||
365 | private function getLangForUser(IUser $user): ?string { |
||
366 | return $this->config->getUserValue($user->getUID(), 'core', 'lang', null); |
||
367 | } |
||
368 | |||
369 | /** |
||
370 | * @param IL10N $l10n |
||
371 | * @param VEvent $vevent |
||
372 | * @return string |
||
373 | * @throws \Exception |
||
374 | */ |
||
375 | private function generateDateString(IL10N $l10n, VEvent $vevent):string { |
||
443 | } |
||
444 | |||
445 | /** |
||
446 | * @param DateTime $dtStart |
||
447 | * @param DateTime $dtEnd |
||
448 | * @return bool |
||
449 | */ |
||
450 | private function isDayEqual(DateTime $dtStart, |
||
451 | DateTime $dtEnd):bool { |
||
452 | return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d'); |
||
453 | } |
||
454 | |||
455 | /** |
||
456 | * @param IL10N $l10n |
||
457 | * @param DateTime $dt |
||
458 | * @return string |
||
459 | */ |
||
460 | private function getWeekDayName(IL10N $l10n, DateTime $dt):string { |
||
462 | } |
||
463 | |||
464 | /** |
||
465 | * @param IL10N $l10n |
||
466 | * @param DateTime $dt |
||
467 | * @return string |
||
468 | */ |
||
469 | private function getDateString(IL10N $l10n, DateTime $dt):string { |
||
470 | return $l10n->l('date', $dt, ['width' => 'medium']); |
||
471 | } |
||
472 | |||
473 | /** |
||
474 | * @param IL10N $l10n |
||
475 | * @param DateTime $dt |
||
476 | * @return string |
||
477 | */ |
||
478 | private function getDateTimeString(IL10N $l10n, DateTime $dt):string { |
||
479 | return $l10n->l('datetime', $dt, ['width' => 'medium|short']); |
||
480 | } |
||
481 | |||
482 | /** |
||
483 | * @param IL10N $l10n |
||
484 | * @param DateTime $dt |
||
485 | * @return string |
||
486 | */ |
||
487 | private function getTimeString(IL10N $l10n, DateTime $dt):string { |
||
489 | } |
||
490 | |||
491 | /** |
||
492 | * @param VEvent $vevent |
||
493 | * @param IL10N $l10n |
||
494 | * @return string |
||
495 | */ |
||
496 | private function getTitleFromVEvent(VEvent $vevent, IL10N $l10n):string { |
||
502 | } |
||
503 | } |
||
504 |
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.