Total Complexity | 50 |
Total Lines | 509 |
Duplicated Lines | 0 % |
Changes | 5 | ||
Bugs | 0 | Features | 1 |
Complex classes like NotificationType 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 NotificationType, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
33 | class NotificationType extends DataObject |
||
34 | { |
||
35 | private static $table_name = 'Notifications_NotificationType'; |
||
|
|||
36 | |||
37 | /** |
||
38 | * Template used for rendering this notification |
||
39 | * |
||
40 | * @var string |
||
41 | */ |
||
42 | private static $template; |
||
43 | |||
44 | /** |
||
45 | * List of objects that this notification is allowed |
||
46 | * to send to. If null, then all notifications can be sent to all |
||
47 | * registered objects |
||
48 | */ |
||
49 | private static $allowed_objects; |
||
50 | |||
51 | /** |
||
52 | * Alternate fields on an object that can be used for the from |
||
53 | * field on this notification. Provided in the format of |
||
54 | * the classname as the key and an array of field names as the value. |
||
55 | * EG: |
||
56 | * |
||
57 | * App\Model\MyObject: |
||
58 | * - FieldName |
||
59 | * - Relation.FieldName |
||
60 | * |
||
61 | * @var array |
||
62 | */ |
||
63 | private static $alt_from_fields = []; |
||
64 | |||
65 | /** |
||
66 | * Alternate fields on an object that can be used for the recipient |
||
67 | * field on this notification. Provided in the format of |
||
68 | * the classname as the key and an array of field names as the value. |
||
69 | * EG: |
||
70 | * |
||
71 | * App\Model\MyObject: |
||
72 | * - FieldName |
||
73 | * - Relation.FieldName |
||
74 | * |
||
75 | * @var array |
||
76 | */ |
||
77 | private static $alt_recipient_fields = []; |
||
78 | |||
79 | /** |
||
80 | * The current object instance that is notifying |
||
81 | * |
||
82 | * @var DataObject |
||
83 | */ |
||
84 | protected $object; |
||
85 | |||
86 | /** |
||
87 | * Extra vars to be used when rendering |
||
88 | * |
||
89 | * @var array |
||
90 | */ |
||
91 | protected $extra_vars = []; |
||
92 | |||
93 | private static $db = [ |
||
94 | 'From' => 'Varchar', |
||
95 | 'AltFrom' => 'Varchar', |
||
96 | 'Recipient' => 'Varchar', |
||
97 | 'AltRecipient' => 'Varchar', |
||
98 | 'Content' => 'Text' |
||
99 | ]; |
||
100 | |||
101 | private static $has_one = [ |
||
102 | 'Notification' => Notification::class |
||
103 | ]; |
||
104 | |||
105 | private static $casting = [ |
||
106 | 'Type' => 'Varchar', |
||
107 | 'RenderedContent' => 'Text', |
||
108 | 'Summary' => 'Varchar' |
||
109 | ]; |
||
110 | |||
111 | private static $summary_fields = [ |
||
112 | 'Type', |
||
113 | 'From', |
||
114 | 'Recipient' |
||
115 | ]; |
||
116 | |||
117 | private static $field_labels = [ |
||
118 | 'AltFrom' => 'Send notification from Alternate Field', |
||
119 | 'AltRecipient' => 'Send notification to Alternate Field' |
||
120 | ]; |
||
121 | |||
122 | public function getType() |
||
123 | { |
||
124 | return $this->i18n_singular_name(); |
||
125 | } |
||
126 | |||
127 | /** |
||
128 | * Attempt to generate a summary of this rule |
||
129 | * |
||
130 | * @return string |
||
131 | */ |
||
132 | public function getSummary(): string |
||
133 | { |
||
134 | return $this->fieldLabel('Type') . ': ' . $this->Type; |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * Return a rendered version of this notification's content using the |
||
139 | * current object as a base |
||
140 | * |
||
141 | * @return string |
||
142 | */ |
||
143 | public function getRenderedContent(): string |
||
144 | { |
||
145 | return $this->renderString((string) $this->Content); |
||
146 | } |
||
147 | |||
148 | /** |
||
149 | * Get a list of possible alternate fields that can be used for the |
||
150 | * from address |
||
151 | * |
||
152 | * @return array |
||
153 | */ |
||
154 | protected function getAltFromFields(): array |
||
155 | { |
||
156 | $alt_fields = $this->config()->alt_from_fields; |
||
157 | $notification_class = $this->Notification()->BaseClassName; |
||
158 | $return = []; |
||
159 | |||
160 | if (!is_array($alt_fields) || !array_key_exists($notification_class, $alt_fields)) { |
||
161 | return $return; |
||
162 | } |
||
163 | |||
164 | foreach ($alt_fields[$notification_class] as $field) { |
||
165 | $return[$field] = $field; |
||
166 | } |
||
167 | |||
168 | return $return; |
||
169 | } |
||
170 | |||
171 | /** |
||
172 | * Get a list of possible alternate fields that can be used for the |
||
173 | * recipient address |
||
174 | * |
||
175 | * @return array |
||
176 | */ |
||
177 | protected function getAltRecipientFields(): array |
||
178 | { |
||
179 | $alt_fields = $this->config()->alt_recipient_fields; |
||
180 | $notification_class = $this->Notification()->BaseClassName; |
||
181 | $return = []; |
||
182 | |||
183 | if (!is_array($alt_fields) || !array_key_exists($notification_class, $alt_fields)) { |
||
184 | return []; |
||
185 | } |
||
186 | |||
187 | foreach ($alt_fields[$notification_class] as $field) { |
||
188 | $return[$field] = $field; |
||
189 | } |
||
190 | |||
191 | return $return; |
||
192 | } |
||
193 | |||
194 | /** |
||
195 | * Get a list of recipients to recieve this notification. |
||
196 | * If only one is available then will be an array with |
||
197 | * a single item |
||
198 | * |
||
199 | * @return array |
||
200 | */ |
||
201 | protected function getRecipients(): array |
||
202 | { |
||
203 | $object = $this->getObject(); |
||
204 | $recipients = empty($this->Recipient) ? [] : explode(',', $this->Recipient); |
||
205 | |||
206 | // If we arent declaring an alternate recipient |
||
207 | // then return |
||
208 | if (empty($this->AltRecipient)) { |
||
209 | return $recipients; |
||
210 | } |
||
211 | |||
212 | // Try and resolve alternate recipients to a field |
||
213 | // directly |
||
214 | $recipient = $object->relField($this->AltRecipient); |
||
215 | |||
216 | if (!empty($recipient)) { |
||
217 | return array_merge( |
||
218 | $recipients, |
||
219 | explode(',', $recipient) |
||
220 | ); |
||
221 | } |
||
222 | |||
223 | // If alt recipient was null, try and see if it |
||
224 | // resolves to a list |
||
225 | $component = $object; |
||
226 | $fieldName = null; |
||
227 | |||
228 | if (($pos = strrpos($this->AltRecipient, '.')) !== false) { |
||
229 | $relation = substr($this->AltRecipient, 0, $pos); |
||
230 | $fieldName = substr($this->AltRecipient, $pos + 1); |
||
231 | $component = $object->relObject($relation); |
||
232 | } |
||
233 | |||
234 | if (!empty($fieldName) && $component instanceof SS_List) { |
||
235 | $recipients = array_merge( |
||
236 | $recipients, |
||
237 | $component->column($fieldName) |
||
238 | ); |
||
239 | } |
||
240 | |||
241 | return $recipients; |
||
242 | } |
||
243 | |||
244 | /** |
||
245 | * Try and find the correct default sender |
||
246 | * |
||
247 | * @return string |
||
248 | */ |
||
249 | protected function getSender(): string |
||
250 | { |
||
251 | if (!empty($this->AltFrom)) { |
||
252 | $sender = $this->AltFrom; |
||
253 | } else { |
||
254 | $sender = $this->From; |
||
255 | } |
||
256 | |||
257 | return (string)$sender; |
||
258 | } |
||
259 | |||
260 | protected function getFieldsFromClass(string $class): array |
||
261 | { |
||
262 | $result = []; |
||
263 | |||
264 | $fields = [ |
||
265 | 'db' => (array)Config::inst()->get( |
||
266 | $class, |
||
267 | 'db', |
||
268 | Config::UNINHERITED |
||
269 | ), |
||
270 | 'casting' => (array)Config::inst()->get( |
||
271 | $class, |
||
272 | 'casting', |
||
273 | Config::UNINHERITED |
||
274 | ) |
||
275 | ]; |
||
276 | |||
277 | foreach (array_values($fields) as $attrs) { |
||
278 | foreach (array_keys($attrs) as $name) { |
||
279 | $result[] = $name; |
||
280 | } |
||
281 | } |
||
282 | |||
283 | return $result; |
||
284 | } |
||
285 | |||
286 | /** |
||
287 | * Attempt to generate a list possible template |
||
288 | * variables that can be used in the subject and |
||
289 | * content fields. |
||
290 | * |
||
291 | * @return array |
||
292 | */ |
||
293 | protected function compilePossibleTemplateVars(): array |
||
294 | { |
||
295 | $result = []; |
||
296 | $base_class = $this->Notification()->BaseClassName; |
||
297 | |||
298 | if (!class_exists($base_class)) { |
||
299 | return $result; |
||
300 | } |
||
301 | |||
302 | // get all translated static properties as defined in i18nCollectStatics() |
||
303 | $ancestry = ClassInfo::ancestry($base_class); |
||
304 | $ancestry = array_reverse($ancestry); |
||
305 | |||
306 | foreach ($ancestry as $ancestorClass) { |
||
307 | // Finish at ViewableData |
||
308 | if ($ancestorClass === ViewableData::class) { |
||
309 | break; |
||
310 | } |
||
311 | |||
312 | $fields = $this->getFieldsFromClass($ancestorClass); |
||
313 | |||
314 | foreach ($fields as $name) { |
||
315 | $result[$name] = '{$' . $name . '}'; |
||
316 | } |
||
317 | |||
318 | $relations = (array)Config::inst()->get( |
||
319 | $ancestorClass, |
||
320 | 'has_one', |
||
321 | Config::UNINHERITED |
||
322 | ); |
||
323 | |||
324 | foreach ($relations as $name => $related_class) { |
||
325 | $fields = $this->getFieldsFromClass($related_class); |
||
326 | |||
327 | foreach (array_values($fields) as $related_name) { |
||
328 | $result[$name . '.' . $related_name] = '{$' . $name . '.' . $related_name . '}'; |
||
329 | } |
||
330 | } |
||
331 | } |
||
332 | |||
333 | return $result; |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * Generate a string of possible template vars |
||
338 | * |
||
339 | * @return string |
||
340 | */ |
||
341 | protected function getRenderedTemplateVars(): string |
||
342 | { |
||
343 | $vars = $this->compilePossibleTemplateVars(); |
||
344 | return implode('<br/>', array_values($vars)); |
||
345 | } |
||
346 | |||
347 | public function getCMSFields() |
||
348 | { |
||
349 | $this->beforeUpdateCMSFields(function (FieldList $fields) { |
||
350 | $alt_from_fields = $this->getAltFromFields(); |
||
351 | $alt_recipient_fields = $this->getAltRecipientFields(); |
||
352 | |||
353 | if (count($alt_from_fields) > 0) { |
||
354 | $fields->replaceField( |
||
355 | 'AltFrom', |
||
356 | DropdownField::create( |
||
357 | 'AltFrom', |
||
358 | $this->fieldLabel('AltFrom'), |
||
359 | $alt_from_fields |
||
360 | )->setEmptyString(_t( |
||
361 | __CLASS__ . '.AltFromEmptyString', |
||
362 | 'Select a field to send from' |
||
363 | )) |
||
364 | ); |
||
365 | } else { |
||
366 | $fields->removeByName('AltFrom'); |
||
367 | } |
||
368 | |||
369 | if (count($alt_recipient_fields) > 0) { |
||
370 | $fields->replaceField( |
||
371 | 'AltRecipient', |
||
372 | DropdownField::create( |
||
373 | 'AltRecipient', |
||
374 | $this->fieldLabel('AltRecipient'), |
||
375 | $alt_recipient_fields |
||
376 | )->setEmptyString(_t( |
||
377 | __CLASS__ . '.AltRecipientEmptyString', |
||
378 | 'Select a recipient' |
||
379 | )) |
||
380 | ); |
||
381 | } else { |
||
382 | $fields->removeByName('AltRecipient'); |
||
383 | } |
||
384 | |||
385 | // Add a list of possible variables to use in |
||
386 | // subject and content |
||
387 | $vars_field = LiteralField::create( |
||
388 | 'TemplateVars', |
||
389 | HTML::createTag( |
||
390 | 'div', |
||
391 | ['class' => 'px-4 py-2'], |
||
392 | $this->getRenderedTemplateVars() |
||
393 | ) |
||
394 | ); |
||
395 | |||
396 | $fields->addFieldToTab( |
||
397 | 'Root.Main', |
||
398 | ToggleCompositeField::create( |
||
399 | 'TemplateVarsComposite', |
||
400 | _t(__CLASS__ . ".TemplateVars", 'Possible Template Variables'), |
||
401 | $vars_field |
||
402 | ) |
||
403 | ); |
||
404 | }); |
||
405 | |||
406 | return parent::getCMSFields(); |
||
407 | } |
||
408 | |||
409 | /** |
||
410 | * Ensure that a sender and recipient are set |
||
411 | * |
||
412 | * @return ValidationResult |
||
413 | */ |
||
414 | public function validate() |
||
415 | { |
||
416 | $result = ValidationResult::create(); |
||
417 | |||
418 | if (empty($this->From) && empty($this->AltFrom)) { |
||
419 | $result->addError( |
||
420 | _t(__CLASS__ . '.NoSender', 'You have not set a sender') |
||
421 | ); |
||
422 | } |
||
423 | |||
424 | if (empty($this->Recipient) && empty($this->AltRecipient)) { |
||
425 | $result->addError( |
||
426 | _t(__CLASS__ . '.NoRecipient', 'You have not set a recipient') |
||
427 | ); |
||
428 | } |
||
429 | |||
430 | $this->extend('validate', $result); |
||
431 | return $result; |
||
432 | } |
||
433 | |||
434 | public function send(array $custom_recipients = []) |
||
435 | { |
||
436 | throw new LogicException('You must implement your own send method'); |
||
437 | } |
||
438 | |||
439 | /** |
||
440 | * Get the current object instance that is notifying |
||
441 | * |
||
442 | * @return DataObject |
||
443 | */ |
||
444 | public function getObject(): DataObject |
||
445 | { |
||
446 | return $this->object; |
||
447 | } |
||
448 | |||
449 | /** |
||
450 | * Set the current object instance that is notifying |
||
451 | * |
||
452 | * @param DataObject $object |
||
453 | * |
||
454 | * @return self |
||
455 | */ |
||
456 | public function setObject(DataObject $object): self |
||
457 | { |
||
458 | $base_class = $this->Notification()->BaseClassName; |
||
459 | |||
460 | if (!is_a($object, $base_class)) { |
||
461 | throw new LogicException('Object must be of type: ' . $base_class); |
||
462 | } |
||
463 | |||
464 | $this->object = $object; |
||
465 | return $this; |
||
466 | } |
||
467 | |||
468 | /** |
||
469 | * Take the passed string and render it using SSViewer |
||
470 | * |
||
471 | * @param string string |
||
472 | * |
||
473 | * @return string |
||
474 | */ |
||
475 | protected function renderString(string $string): string |
||
476 | { |
||
477 | $object = $this->getObject(); |
||
478 | |||
479 | if (empty($object)) { |
||
480 | throw new LogicException('You must set a base object via setObject'); |
||
481 | } |
||
482 | |||
483 | $viewer = SSViewer::fromString($string); |
||
484 | |||
485 | $vars = array_merge( |
||
486 | [ 'CurrType' => $this ], |
||
487 | $this->getExtraVars() |
||
488 | ); |
||
489 | |||
490 | return $viewer->process( |
||
491 | $object, |
||
492 | $vars |
||
493 | ); |
||
494 | } |
||
495 | |||
496 | /** |
||
497 | * Get extra vars to be used when rendering |
||
498 | * |
||
499 | * @return array |
||
500 | */ |
||
501 | public function getExtraVars(): array |
||
502 | { |
||
503 | return $this->extra_vars; |
||
504 | } |
||
505 | |||
506 | /** |
||
507 | * Set extra vars to be used when rendering |
||
508 | * |
||
509 | * @param array $extra_vars |
||
510 | * |
||
511 | * @return self |
||
512 | */ |
||
513 | public function setExtraVars(array $extra_vars): self |
||
514 | { |
||
515 | $this->extra_vars = $extra_vars; |
||
516 | return $this; |
||
517 | } |
||
518 | |||
519 | /** |
||
520 | * Add a variable to be used when rendering |
||
521 | * |
||
522 | * @return self |
||
523 | */ |
||
524 | public function addExtraVar(string $name, mixed $value): self |
||
528 | } |
||
529 | |||
530 | /** |
||
531 | * Remove a variable to be used when rendering |
||
532 | * |
||
533 | * @return self |
||
534 | */ |
||
535 | public function removeExtraVar(string $name): self |
||
536 | { |
||
537 | if (array_key_exists($name, $this->extra_vars)) { |
||
538 | unset($this->extra_vars[$name]); |
||
539 | } |
||
540 | |||
541 | return $this; |
||
542 | } |
||
543 | } |
||
544 |