NotificationType::getExtraVars()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 1
c 1
b 0
f 1
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
namespace ilateral\SilverStripe\Notifier\Types;
4
5
use LogicException;
6
use SilverStripe\ORM\SS_List;
7
use SilverStripe\View\SSViewer;
8
use SilverStripe\Core\ClassInfo;
9
use SilverStripe\ORM\DataObject;
10
use SilverStripe\Forms\FieldList;
11
use SilverStripe\View\ViewableData;
12
use SilverStripe\Core\Config\Config;
13
use SilverStripe\Forms\DropdownField;
14
use SilverStripe\ORM\ValidationResult;
15
use SilverStripe\Forms\ToggleCompositeField;
16
use ilateral\SilverStripe\Notifier\Model\Notification;
17
use SilverStripe\Forms\LiteralField;
18
use SilverStripe\View\HTML;
19
20
/**
21
 * Base Object for sending notifications
22
 *
23
 * @property string From
24
 * @property string AltFrom
25
 * @property string Recipient
26
 * @property string AltRecipient
27
 * @property string Content
28
 * @property string Type
29
 * @property string RenderedContent
30
 * 
31
 * @method Notification Notification
32
 */
33
class NotificationType extends DataObject
34
{
35
    private static $table_name = 'Notifications_NotificationType';
0 ignored issues
show
introduced by
The private property $table_name is not used, and could be removed.
Loading history...
36
37
    /**
38
     * Template used for rendering this notification
39
     *
40
     * @var string
41
     */
42
    private static $template;
0 ignored issues
show
introduced by
The private property $template is not used, and could be removed.
Loading history...
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;
0 ignored issues
show
introduced by
The private property $allowed_objects is not used, and could be removed.
Loading history...
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 = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
94
        'From' => 'Varchar',
95
        'AltFrom' => 'Varchar',
96
        'Recipient' => 'Varchar',
97
        'AltRecipient' => 'Varchar',
98
        'Content' => 'Text'
99
    ];
100
101
    private static $has_one = [
0 ignored issues
show
introduced by
The private property $has_one is not used, and could be removed.
Loading history...
102
        'Notification' => Notification::class
103
    ];
104
105
    private static $casting = [
0 ignored issues
show
introduced by
The private property $casting is not used, and could be removed.
Loading history...
106
        'Type' => 'Varchar',
107
        'RenderedContent' => 'Text',
108
        'Summary' => 'Varchar'
109
    ];
110
111
    private static $summary_fields = [
0 ignored issues
show
introduced by
The private property $summary_fields is not used, and could be removed.
Loading history...
112
        'Type',
113
        'From',
114
        'Recipient'
115
    ];
116
117
    private static $field_labels = [
0 ignored issues
show
introduced by
The private property $field_labels is not used, and could be removed.
Loading history...
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 = [])
0 ignored issues
show
Unused Code introduced by
The parameter $custom_recipients is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

434
    public function send(/** @scrutinizer ignore-unused */ array $custom_recipients = [])

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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
525
    {
526
        $this->extra_vars[$name] = $value;
527
        return $this;
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