| 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 |