MandrillAdmin::doInstallHook()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 11
c 0
b 0
f 0
nc 4
nop 0
dl 0
loc 19
rs 9.9
1
<?php
2
3
namespace LeKoala\Mandrill;
4
5
use Exception;
6
use Psr\Log\LoggerInterface;
7
use Psr\SimpleCache\CacheInterface;
8
use Psr\SimpleCache\InvalidArgumentException;
9
use SilverStripe\Admin\LeftAndMain;
0 ignored issues
show
Bug introduced by
The type SilverStripe\Admin\LeftAndMain was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use SilverStripe\Control\Director;
11
use SilverStripe\Control\Email\Email;
12
use SilverStripe\Control\Email\SwiftMailer;
0 ignored issues
show
Bug introduced by
The type SilverStripe\Control\Email\SwiftMailer was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
13
use SilverStripe\Control\HTTPResponse;
14
use SilverStripe\Control\Session;
15
use SilverStripe\Forms\CompositeField;
16
use SilverStripe\Forms\DateField;
17
use SilverStripe\Forms\DropdownField;
18
use SilverStripe\Forms\FieldList;
19
use SilverStripe\Forms\Form;
20
use SilverStripe\Forms\FormAction;
21
use SilverStripe\Forms\FormField;
22
use SilverStripe\Forms\GridField\GridField;
23
use SilverStripe\Forms\GridField\GridFieldConfig;
24
use SilverStripe\Forms\GridField\GridFieldDataColumns;
25
use SilverStripe\Forms\GridField\GridFieldDetailForm;
26
use SilverStripe\Forms\GridField\GridFieldFooter;
27
use SilverStripe\Forms\GridField\GridFieldSortableHeader;
28
use SilverStripe\Forms\GridField\GridFieldToolbarHeader;
29
use SilverStripe\Forms\HiddenField;
30
use SilverStripe\Forms\LiteralField;
31
use SilverStripe\Forms\Tab;
32
use SilverStripe\Forms\TabSet;
33
use SilverStripe\Forms\TextField;
34
use SilverStripe\ORM\ArrayLib;
35
use SilverStripe\ORM\ArrayList;
36
use SilverStripe\Security\DefaultAdminService;
37
use SilverStripe\Security\Permission;
38
use SilverStripe\Security\PermissionProvider;
39
use SilverStripe\Security\Security;
40
use SilverStripe\SiteConfig\SiteConfig;
0 ignored issues
show
Bug introduced by
The type SilverStripe\SiteConfig\SiteConfig was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
41
use SilverStripe\View\ArrayData;
42
use Symbiote\GridFieldExtensions\GridFieldTitleHeader;
43
use Symfony\Component\Mailer\MailerInterface;
44
use Symfony\Component\Mailer\Transport\AbstractApiTransport;
45
46
/**
47
 * Mandrill admin section
48
 *
49
 * Allow you to see messages sent through the api key used to send messages
50
 *
51
 * @package Mandrill
52
 * @author LeKoala <[email protected]>
53
 */
54
class MandrillAdmin extends LeftAndMain implements PermissionProvider
55
{
56
57
    const MESSAGE_CACHE_MINUTES = 5;
58
    const WEBHOOK_CACHE_MINUTES = 1440; // 1 day
59
    const SENDINGDOMAIN_CACHE_MINUTES = 1440; // 1 day
60
61
    private static $menu_title = "Mandrill";
62
    private static $url_segment = "mandrill";
63
    private static $menu_icon = "lekoala/silverstripe-mandrill:images/icon.png";
64
    private static $url_rule = '/$Action/$ID/$OtherID';
65
    private static $allowed_actions = array(
66
        "settings",
67
        "ListForm",
68
        "SearchForm",
69
        "doSearch",
70
        "InstallHookForm",
71
        "doInstallHook",
72
        "UninstallHookForm",
73
        "doUninstallHook",
74
        "send_test",
75
    );
76
    private static $cache_enabled = true;
77
78
    /**
79
     * @var Exception
80
     */
81
    protected $lastException;
82
83
    /**
84
     * Inject public dependencies into the controller
85
     *
86
     * @var array
87
     */
88
    private static $dependencies = [
89
        'logger' => '%$Psr\Log\LoggerInterface',
90
        'cache' => '%$Psr\SimpleCache\CacheInterface.mandrill', // see _config/cache.yml
91
    ];
92
93
    /**
94
     * @var LoggerInterface
95
     */
96
    public $logger;
97
98
    /**
99
     * @var CacheInterface
100
     */
101
    public $cache;
102
103
    public function init()
104
    {
105
        parent::init();
106
107
        if (isset($_GET['refresh'])) {
108
            $this->getCache()->clear();
109
        }
110
    }
111
112
    public function settings($request)
113
    {
114
        return parent::index($request);
115
    }
116
117
    public function send_test($request)
118
    {
119
        if (!$this->CanConfigureApi()) {
120
            return $this->httpError(404);
121
        }
122
        $service = DefaultAdminService::create();
123
        $to = $request->getVar('to');
124
        if (!$to) {
125
            $to = $service->findOrCreateDefaultAdmin()->Email;
126
        }
127
        $email = Email::create();
128
        $email->setSubject("Test email");
129
        $email->setBody("Test " . date('Y-m-d H:i:s'));
130
        $email->setTo($to);
131
132
        $result = $email->send();
133
        var_dump($result);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($result) looks like debug code. Are you sure you do not want to remove it?
Loading history...
134
    }
135
136
    /**
137
     * @return Session
138
     */
139
    public function getSession()
140
    {
141
        return $this->getRequest()->getSession();
142
    }
143
144
    /**
145
     * Returns a GridField of messages
146
     *
147
     * @param int $id
148
     * @param FieldList $fields
149
     * @return Form
150
     * @throws InvalidArgumentException
151
     */
152
    public function getEditForm($id = null, $fields = null)
153
    {
154
        if (!$id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
155
            $id = $this->currentPageID();
156
        }
157
158
        $record = $this->getRecord($id);
159
160
        // Check if this record is viewable
161
        if ($record && !$record->canView()) {
162
            $response = Security::permissionFailure($this);
163
            $this->setResponse($response);
164
            return null;
165
        }
166
167
        // Build gridfield
168
        $messageListConfig = GridFieldConfig::create()->addComponents(
169
            new GridFieldSortableHeader(),
170
            new GridFieldDataColumns(),
171
            new GridFieldFooter()
172
        );
173
174
        $messages = $this->Messages();
175
        if (is_string($messages)) {
0 ignored issues
show
introduced by
The condition is_string($messages) is always false.
Loading history...
176
            // The api returned an error
177
            $messagesList = new LiteralField("MessageAlert", $this->MessageHelper($messages, 'bad'));
178
        } else {
179
            $messagesList = GridField::create(
180
                'Messages',
181
                false,
182
                $messages,
183
                $messageListConfig
184
            )->addExtraClass("messages_grid");
185
186
            /** @var GridFieldDataColumns $columns */
187
            $columns = $messageListConfig->getComponentByType(GridFieldDataColumns::class);
188
            $columns->setDisplayFields(array(
189
                'date' => _t('MandrillAdmin.MessageDate', 'Date'),
190
                'state' => _t('MandrillAdmin.MessageStatus', 'Status'),
191
                'sender' => _t('MandrillAdmin.MessageSender', 'Sender'),
192
                'email' => _t('MandrillAdmin.MessageEmail', 'Email'),
193
                'subject' => _t('MandrillAdmin.MessageSubject', 'Subject'),
194
                'opens' => _t('MandrillAdmin.MessageOpens', 'Opens'),
195
                'clicks' => _t('MandrillAdmin.MessageClicks', 'Clicks'),
196
            ));
197
            $columns->setFieldFormatting(array(
198
                'state' => function ($value, &$item) {
199
                    switch ($value) {
200
                        case 'sent':
201
                            $color = 'green';
202
                            break;
203
                        default:
204
                            $color = '#333';
205
                            break;
206
                    }
207
                    return sprintf('<span style="color:%s">%s</span>', $color, $value);
208
                }
209
            ));
210
211
            // Validator setup
212
            $validator = null;
213
            if ($record && method_exists($record, 'getValidator')) {
214
                $validator = $record->getValidator();
215
            }
216
217
            if ($validator) {
218
                /** @var GridFieldDetailForm $detailForm */
219
                $detailForm = $messageListConfig->getComponentByType(GridFieldDetailForm::class);
220
                $detailForm->setValidator($validator);
221
            }
222
        }
223
224
        // Create tabs
225
        $messagesTab = new Tab(
226
            'Messages',
227
            _t('MandrillAdmin.Messages', 'Messages'),
228
            $this->SearchFields(),
229
            $messagesList,
230
            // necessary for tree node selection in LeftAndMain.EditForm.js
231
            new HiddenField('ID', false, 0)
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type SilverStripe\View\ViewableData|null|string expected by parameter $title of SilverStripe\Forms\HiddenField::__construct(). ( Ignorable by Annotation )

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

231
            new HiddenField('ID', /** @scrutinizer ignore-type */ false, 0)
Loading history...
232
        );
233
234
        $fields = new FieldList(
235
            $root = new TabSet('Root', $messagesTab)
236
        );
237
238
        if ($this->CanConfigureApi()) {
239
            $settingsTab = new Tab('Settings', _t('MandrillAdmin.Settings', 'Settings'));
240
241
            $domainTabData = $this->DomainTab();
242
            $settingsTab->push($domainTabData);
243
244
            $webhookTabData = $this->WebhookTab();
245
            $settingsTab->push($webhookTabData);
246
247
            // Add a refresh button
248
            $refreshButton = new LiteralField('RefreshButton', $this->ButtonHelper(
249
                $this->Link() . '?refresh=true',
250
                _t('MandrillAdmin.REFRESH', 'Force data refresh from the API')
251
            ));
252
            $settingsTab->push($refreshButton);
253
254
            $fields->addFieldToTab('Root', $settingsTab);
255
        }
256
257
        // Tab nav in CMS is rendered through separate template
258
        $root->setTemplate('SilverStripe\\Forms\\CMSTabSet');
259
260
        // Manage tabs state
261
        $actionParam = $this->getRequest()->param('Action');
262
        if ($actionParam == 'setting') {
263
            $settingsTab->addExtraClass('ui-state-active');
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $settingsTab does not seem to be defined for all execution paths leading up to this point.
Loading history...
264
        } elseif ($actionParam == 'messages') {
265
            $messagesTab->addExtraClass('ui-state-active');
266
        }
267
268
        // Build replacement form
269
        $form = Form::create(
270
            $this,
271
            'EditForm',
272
            $fields,
273
            new FieldList()
274
        )->setHTMLID('Form_EditForm');
275
        $form->addExtraClass('cms-edit-form fill-height');
276
        $form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
277
        $form->addExtraClass('ss-tabset cms-tabset ' . $this->BaseCSSClasses());
278
        $form->setAttribute('data-pjax-fragment', 'CurrentForm');
279
280
        $this->extend('updateEditForm', $form);
281
282
        return $form;
283
    }
284
285
    /**
286
     * Get logger
287
     *
288
     * @return  LoggerInterface
289
     */
290
    public function getLogger()
291
    {
292
        return $this->logger;
293
    }
294
295
    /**
296
     * Get the cache
297
     *
298
     * @return CacheInterface
299
     */
300
    public function getCache()
301
    {
302
        return $this->cache;
303
    }
304
305
    /**
306
     * @return boolean
307
     */
308
    public function getCacheEnabled()
309
    {
310
        if (isset($_GET['disable_cache'])) {
311
            return false;
312
        }
313
        $v = $this->config()->cache_enabled;
314
        if ($v === null) {
315
            $v = self::$cache_enabled;
316
        }
317
        return $v;
318
    }
319
320
    /**
321
     * A simple cache helper
322
     *
323
     * @param string $method
324
     * @param array $params Params must be set in the right order!
325
     * @param int $expireInSeconds
326
     * @return array
327
     * @throws InvalidArgumentException
328
     */
329
    protected function getCachedData($method, $params, $expireInSeconds = 60)
330
    {
331
        $enabled = $this->getCacheEnabled();
332
        if ($enabled) {
333
            $cache = $this->getCache();
334
            $key = $method . md5(serialize($params));
335
            $cacheResult = $cache->get($key);
336
        }
337
        if ($enabled && $cacheResult) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cacheResult does not seem to be defined for all execution paths leading up to this point.
Loading history...
338
            $data = unserialize($cacheResult);
339
        } else {
340
            try {
341
                $client = MandrillHelper::getClient();
342
                $parts = explode('.', $method);
343
                $service = $parts[0];
344
                $func = $parts[1];
345
                $data = call_user_func_array([$client->$service, $func], $params);
346
            } catch (Exception $ex) {
347
                $this->lastException = $ex;
348
                $this->getLogger()->debug($ex);
349
                $data = false;
350
                $enabled = false;
351
            }
352
353
            //5 minutes cache
354
            if ($enabled) {
355
                $cache->set($key, serialize($data), $expireInSeconds);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cache does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $key does not seem to be defined for all execution paths leading up to this point.
Loading history...
356
            }
357
        }
358
359
        return $data;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $data could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
360
    }
361
362
    public function getParams()
363
    {
364
        $params = $this->config()->default_search_params;
365
        if (!$params) {
366
            $params = [];
367
        }
368
        $data = $this->getSession()->get(__class__ . '.Search');
369
        if (!$data) {
370
            $data = [];
371
        }
372
373
        $params = array_merge($params, $data);
374
375
        // Remove empty params
376
        $params = array_filter($params);
377
378
        return $params;
379
    }
380
381
    public function getParam($name, $default = null)
382
    {
383
        $data = $this->getSession()->get(__class__ . '.Search');
384
        if (!$data) {
385
            return $default;
386
        }
387
        return (isset($data[$name]) && strlen($data[$name])) ? $data[$name] : $default;
388
    }
389
390
    public function SearchFields()
391
    {
392
        $fields = new CompositeField();
393
        $fields->push($from = new DateField('params[date_from]', _t('MandrillAdmin.DATEFROM', 'From'), $this->getParam('date_from', date('Y-m-d', strtotime('-30 days')))));
394
        $fields->push($to = new DateField('params[date_to]', _t('MandrillAdmin.DATETO', 'To'), $to = $this->getParam('date_to')));
395
        $fields->push($queryField = new TextField('params[query]', _t('Mandrill.QUERY', 'Query'), $this->getParam('query')));
396
        $queryField->setAttribute('placeholder', 'full_email:joe@domain.* AND sender:[email protected] OR subject:welcome');
397
        $queryField->setDescription(_t('Mandrill.QUERYDESC', 'For more information about query syntax, please visit <a target="_blank" href="https://mailchimp.com/developer/transactional/docs/activity-reports/#search-outbound-activity">Mandrill Support</a>'));
398
        $fields->push(new DropdownField('params[limit]', _t('MandrillAdmin.PERPAGE', 'Number of results'), array(
399
            100 => 100,
400
            500 => 500,
401
            1000 => 1000,
402
            10000 => 10000,
403
        ), $this->getParam('limit', 100)));
404
405
        foreach ($fields->FieldList() as $field) {
406
            $field->addExtraClass('no-change-track');
407
        }
408
409
        // This is a ugly hack to allow embedding a form into another form
410
        $fields->push($doSearch = new FormAction('doSearch', _t('MandrillAdmin.DOSEARCH', 'Search')));
411
        $doSearch->addExtraClass("btn btn-primary");
412
        $doSearch->setAttribute('onclick', "jQuery('#Form_SearchForm').append(jQuery('#Form_EditForm input,#Form_EditForm select').clone()).submit();");
413
414
        return $fields;
415
    }
416
417
    public function SearchForm()
418
    {
419
        $SearchForm = new Form($this, 'SearchForm', new FieldList(), new FieldList(new FormAction('doSearch')));
420
        $SearchForm->setAttribute('style', 'display:none');
421
        return $SearchForm;
422
    }
423
424
    public function doSearch($data, Form $form)
425
    {
426
        $post = $this->getRequest()->postVar('params');
427
        if (!$post) {
428
            return $this->redirectBack();
429
        }
430
        $params = [];
431
432
        $validFields = [];
433
        foreach ($this->SearchFields()->FieldList()->dataFields() as $field) {
434
            $validFields[] = str_replace(['params[', ']'], '', $field->getName());
435
        }
436
437
        foreach ($post as $k => $v) {
438
            if (in_array($k, $validFields)) {
439
                $params[$k] = $v;
440
            }
441
        }
442
443
        $this->getSession()->set(__class__ . '.Search', $params);
444
        $this->getSession()->save($this->getRequest());
445
446
        return $this->redirectBack();
447
    }
448
449
    /**
450
     * List of messages events
451
     *
452
     * Messages are cached to avoid hammering the api
453
     *
454
     * @link https://mandrillapp.com/api/docs/messages.JSON.html#method=search
455
     * @return ArrayList|string
456
     * @throws InvalidArgumentException
457
     */
458
    public function Messages()
459
    {
460
        $params = $this->getParams();
461
462
        // We need the parameters in order to be sent to the api
463
        $orderedParams = [];
464
        $orderedParams['query'] = isset($params['query']) ? $params['query'] : '*';
465
        $orderedParams['date_from'] = isset($params['date_from']) ? $params['date_from'] : null;
466
        $orderedParams['date_to'] = isset($params['date_to']) ? $params['date_to'] : null;
467
        $orderedParams['tags'] = isset($params['tags']) ? $params['tags'] : null;
468
        $orderedParams['senders'] = isset($params['senders']) ? $params['senders'] : null;
469
        $orderedParams['api_keys'] = isset($params['api_keys']) ? $params['api_keys'] : [MandrillHelper::getAPIKey()];
470
        $orderedParams['limit'] = isset($params['limit']) ? $params['limit'] : null;
471
472
        $messages = $this->getCachedData('messages.search', $orderedParams, 60 * self::MESSAGE_CACHE_MINUTES);
473
474
        if ($messages === false) {
0 ignored issues
show
introduced by
The condition $messages === false is always false.
Loading history...
475
            if ($this->lastException) {
476
                return $this->lastException->getMessage();
477
            }
478
            return _t('MandrillAdmin.NO_MESSAGES', 'No messages');
479
        }
480
481
        $list = new ArrayList();
482
        if ($messages) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $messages of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
483
            foreach ($messages as $message) {
484
                $message['date'] = date('Y-m-d H:i:s', $message['ts']);
485
                $m = new ArrayData($message);
486
                $list->push($m);
487
            }
488
        }
489
490
        return $list;
491
    }
492
493
    /**
494
     * Provides custom permissions to the Security section
495
     *
496
     * @return array
497
     */
498
    public function providePermissions()
499
    {
500
        $title = _t("MandrillAdmin.MENUTITLE", LeftAndMain::menu_title('Mandrill'));
501
        return [
502
            "CMS_ACCESS_Mandrill" => [
503
                'name' => _t('MandrillAdmin.ACCESS', "Access to '{title}' section", ['title' => $title]),
504
                'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'),
505
                'help' => _t(
506
                    'MandrillAdmin.ACCESS_HELP',
507
                    'Allow use of Mandrill admin section'
508
                )
509
            ],
510
        ];
511
    }
512
513
    /**
514
     * Message helper
515
     *
516
     * @param string $message
517
     * @param string $status
518
     * @return string
519
     */
520
    protected function MessageHelper($message, $status = 'info')
521
    {
522
        return '<div class="message ' . $status . '">' . $message . '</div>';
523
    }
524
525
    /**
526
     * Button helper
527
     *
528
     * @param string $link
529
     * @param string $text
530
     * @param boolean $confirm
531
     * @return string
532
     */
533
    protected function ButtonHelper($link, $text, $confirm = false)
534
    {
535
        $link = '<a class="btn btn-primary" href="' . $link . '"';
536
        if ($confirm) {
537
            $link .= ' onclick="return confirm(\'' . _t('MandrillAdmin.CONFIRM_MSG', 'Are you sure?') . '\')"';
538
        }
539
        $link .= '>' . $text . '</a>';
540
        return $link;
541
    }
542
543
    /**
544
     * A template accessor to check the ADMIN permission
545
     *
546
     * @return bool
547
     */
548
    public function IsAdmin()
549
    {
550
        return Permission::check("ADMIN");
551
    }
552
553
    /**
554
     * Check the permission for current user
555
     *
556
     * @return bool
557
     */
558
    public function canView($member = null)
559
    {
560
        $mailer = MandrillHelper::getMailer();
561
        $transport = MandrillHelper::getTransportFromMailer($mailer);
562
        // Another custom mailer has been set
563
        if (!($transport instanceof MandrillApiTransport)) {
564
            return false;
565
        }
566
        return Permission::check("CMS_ACCESS_Mandrill", 'any', $member);
567
    }
568
569
    /**
570
     *
571
     * @return bool
572
     */
573
    public function CanConfigureApi()
574
    {
575
        return Permission::check('ADMIN') || Director::isDev();
576
    }
577
578
    /**
579
     * Check if webhook is installed. Returns the webhook details if installed.
580
     *
581
     * @return bool|array
582
     * @throws InvalidArgumentException
583
     */
584
    public function WebhookInstalled()
585
    {
586
        $list = $this->getCachedData('webhooks.getList', [], 60 * self::WEBHOOK_CACHE_MINUTES);
587
588
        if (empty($list)) {
589
            return false;
590
        }
591
        $url = $this->WebhookUrl();
592
        foreach ($list as $el) {
593
            if (!empty($el['url']) && $el['url'] === $url) {
594
                return $el;
595
            }
596
        }
597
        return false;
598
    }
599
600
    /**
601
     * Hook details for template
602
     * @return ArrayData|null
603
     * @throws InvalidArgumentException
604
     */
605
    public function WebhookDetails()
606
    {
607
        $el = $this->WebhookInstalled();
608
        if ($el) {
609
            return new ArrayData($el);
0 ignored issues
show
Bug introduced by
It seems like $el can also be of type true; however, parameter $value of SilverStripe\View\ArrayData::__construct() does only seem to accept array|object, maybe add an additional type check? ( Ignorable by Annotation )

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

609
            return new ArrayData(/** @scrutinizer ignore-type */ $el);
Loading history...
610
        }
611
        return null;
612
    }
613
614
    /**
615
     * Get content of the tab
616
     *
617
     * @return FormField
618
     * @throws InvalidArgumentException
619
     */
620
    public function WebhookTab()
621
    {
622
        if ($this->WebhookInstalled()) {
623
            return $this->UninstallHookForm();
624
        }
625
        return $this->InstallHookForm();
626
    }
627
628
    /**
629
     * @return string
630
     */
631
    public function WebhookUrl()
632
    {
633
        if (self::config()->webhook_base_url) {
634
            return rtrim(self::config()->webhook_base_url, '/') . '/__mandrill/incoming';
635
        }
636
        if (Director::isLive()) {
637
            return Director::absoluteURL('/__mandrill/incoming');
0 ignored issues
show
Bug Best Practice introduced by
The expression return SilverStripe\Cont...'/__mandrill/incoming') could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
638
        }
639
        $protocol = Director::protocol();
640
        return $protocol . $this->getDomain() . '/__mandrill/incoming';
641
    }
642
643
    /**
644
     * Install hook form
645
     *
646
     * @return FormField
647
     */
648
    public function InstallHookForm()
649
    {
650
        $fields = new CompositeField();
651
        $fields->push(new LiteralField('Info', $this->MessageHelper(
652
            _t('MandrillAdmin.WebhookNotInstalled', 'Webhook is not installed. It should be configured using the following url {url}. This url must be publicly visible to be used as a hook.', ['url' => $this->WebhookUrl()]),
653
            'bad'
654
        )));
655
        $fields->push(new LiteralField('doInstallHook', $this->ButtonHelper(
656
            $this->Link('doInstallHook'),
657
            _t('MandrillAdmin.DOINSTALL_WEBHOOK', 'Install webhook')
658
        )));
659
        return $fields;
660
    }
661
662
    /**
663
     * @return HTTPResponse
664
     * @throws Exception
665
     */
666
    public function doInstallHook()
667
    {
668
        if (!$this->CanConfigureApi()) {
669
            return $this->redirectBack();
670
        }
671
672
        $client = MandrillHelper::getClient();
673
674
        $url = $this->WebhookUrl();
675
        $description = SiteConfig::current_site_config()->Title;
676
677
        try {
678
            $client->webhooks->add($url, $description);
679
            $this->getCache()->clear();
680
        } catch (Exception $ex) {
681
            $this->getLogger()->debug($ex);
682
        }
683
684
        return $this->redirectBack();
685
    }
686
687
    /**
688
     * Uninstall hook form
689
     *
690
     * @return FormField
691
     */
692
    public function UninstallHookForm()
693
    {
694
        $fields = new CompositeField();
695
        $fields->push(new LiteralField('Info', $this->MessageHelper(
696
            _t('MandrillAdmin.WebhookInstalled', 'Webhook is installed and accessible at the following url {url}.', ['url' => $this->WebhookUrl()]),
697
            'good'
698
        )));
699
        $fields->push(new LiteralField('doUninstallHook', $this->ButtonHelper(
700
            $this->Link('doUninstallHook'),
701
            _t('MandrillAdmin.DOUNINSTALL_WEBHOOK', 'Uninstall webhook'),
702
            true
703
        )));
704
        return $fields;
705
    }
706
707
    /**
708
     * @param array $data
709
     * @param Form $form
710
     * @return HTTPResponse
711
     * @throws Exception
712
     * @throws InvalidArgumentException
713
     */
714
    public function doUninstallHook($data, Form $form)
715
    {
716
        if (!$this->CanConfigureApi()) {
717
            return $this->redirectBack();
718
        }
719
720
        $client = MandrillHelper::getClient();
721
722
        try {
723
            $el = $this->WebhookInstalled();
724
            if ($el && !empty($el['id'])) {
725
                $client->webhooks->delete($el['id']);
726
            }
727
            $this->getCache()->clear();
728
        } catch (Exception $ex) {
729
            $this->getLogger()->debug($ex);
730
        }
731
732
        return $this->redirectBack();
733
    }
734
735
    /**
736
     * Check if sending domain is installed
737
     *
738
     * @return array|bool
739
     * @throws InvalidArgumentException
740
     */
741
    public function SendingDomainInstalled()
742
    {
743
        // @todo - Use $client or remove?
744
        $client = MandrillHelper::getClient();
0 ignored issues
show
Unused Code introduced by
The assignment to $client is dead and can be removed.
Loading history...
745
746
        $domains = $this->getCachedData('senders.domains', [$this->getDomain()], 60 * self::SENDINGDOMAIN_CACHE_MINUTES);
747
748
        if (empty($domains)) {
749
            return false;
750
        }
751
752
        // Filter
753
        $host = $this->getDomain();
754
        foreach ($domains as $domain) {
755
            if ($domain['domain'] == $host) {
756
                return $domain;
757
            }
758
        }
759
        return false;
760
    }
761
762
    /**
763
     * Trigger request to check if sending domain is verified
764
     *
765
     * @return array|bool
766
     * @throws Exception
767
     */
768
    public function VerifySendingDomain()
769
    {
770
        $client = MandrillHelper::getClient();
771
772
        $host = $this->getDomain();
773
774
        $verification = $client->senders->verifyDomain($host, 'postmaster@' . $host);
775
776
        if (empty($verification)) {
777
            return false;
778
        }
779
        return $verification;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $verification returns the type struct which is incompatible with the documented return type array|boolean.
Loading history...
780
    }
781
782
    /**
783
     * Get content of the tab
784
     *
785
     * @return FormField
786
     * @throws InvalidArgumentException
787
     */
788
    public function DomainTab()
789
    {
790
        $defaultDomain = $this->getDomain();
791
        $defaultDomainInfos = null;
792
793
        $domains = $this->getCachedData('senders.domains', [], 60 * self::SENDINGDOMAIN_CACHE_MINUTES);
794
        $validDomains = MandrillHelper::listValidDomains();
795
796
        $fields = new CompositeField();
797
798
        // @link https://mandrillapp.com/api/docs/senders.JSON.html#method=domains
799
        $list = new ArrayList();
800
        if ($domains) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $domains of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
801
            foreach ($domains as $domain) {
802
                if (!empty($validDomains) && !in_array($domain['domain'], $validDomains)) {
803
                    continue;
804
                }
805
                $list->push(new ArrayData([
806
                    'Domain' => $domain['domain'],
807
                    'SPF' => $domain['spf']['valid'],
808
                    'DKIM' => $domain['dkim']['valid'],
809
                    'Compliance' => $domain['valid_signing'],
810
                    'Verified' => $domain['verified_at'],
811
                ]));
812
813
                if ($domain['domain'] == $defaultDomain) {
814
                    $defaultDomainInfos = $domain;
815
                }
816
            }
817
        }
818
819
        $config = GridFieldConfig::create();
820
        $config->addComponent(new GridFieldToolbarHeader());
821
        $config->addComponent(new GridFieldTitleHeader());
822
        $config->addComponent($columns = new GridFieldDataColumns());
823
        $columns->setDisplayFields(ArrayLib::valuekey(['Domain', 'SPF', 'DKIM', 'Compliance', 'Verified']));
824
        $domainsList = new GridField('SendingDomains', _t('MandrillAdmin.ALL_SENDING_DOMAINS', 'Configured sending domains'), $list, $config);
825
        $domainsList->addExtraClass('mb-2');
826
        $fields->push($domainsList);
827
828
        if (!$defaultDomainInfos) {
829
            $this->InstallDomainForm($fields);
830
        } else {
831
            $this->UninstallDomainForm($fields);
832
        }
833
834
        return $fields;
835
    }
836
837
    /**
838
     * @return string
839
     */
840
    public function InboundUrl()
841
    {
842
        $subdomain = self::config()->inbound_subdomain;
843
        $domain = $this->getDomain();
844
        if ($domain) {
845
            return $subdomain . '.' . $domain;
0 ignored issues
show
Bug introduced by
Are you sure $domain of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

845
            return $subdomain . '.' . /** @scrutinizer ignore-type */ $domain;
Loading history...
846
        }
847
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
848
    }
849
850
    /**
851
     * Get domain
852
     *
853
     * @return boolean|string
854
     */
855
    public function getDomain()
856
    {
857
        return MandrillHelper::getDomain();
858
    }
859
860
    /**
861
     * Install domain form
862
     *
863
     * @param CompositeField $fields
864
     */
865
    public function InstallDomainForm(CompositeField $fields)
866
    {
867
        $host = $this->getDomain();
868
869
        $fields->push(new LiteralField('Info', $this->MessageHelper(
870
            _t('MandrillAdmin.DomainNotInstalled', 'Default sending domain {domain} is not installed.', ['domain' => $host]),
871
            "bad"
872
        )));
873
        $fields->push(new LiteralField('doInstallDomain', $this->ButtonHelper(
874
            $this->Link('doInstallDomain'),
875
            _t('MandrillAdmin.DOINSTALLDOMAIN', 'Install domain')
876
        )));
877
    }
878
879
    /**
880
     * @return HTTPResponse
881
     * @throws Exception
882
     */
883
    public function doInstallDomain()
884
    {
885
        if (!$this->CanConfigureApi()) {
886
            return $this->redirectBack();
887
        }
888
889
        $client = MandrillHelper::getClient();
890
891
        $domain = $this->getDomain();
892
893
        if (!$domain) {
894
            return $this->redirectBack();
895
        }
896
897
        try {
898
            $client->senders->addDomain($domain);
0 ignored issues
show
Bug introduced by
It seems like $domain can also be of type true; however, parameter $domain of Mandrill_Senders::addDomain() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

898
            $client->senders->addDomain(/** @scrutinizer ignore-type */ $domain);
Loading history...
899
            $this->getCache()->clear();
900
        } catch (Exception $ex) {
901
            $this->getLogger()->debug($ex);
902
        }
903
904
        return $this->redirectBack();
905
    }
906
907
    /**
908
     * Uninstall domain form
909
     *
910
     * @param CompositeField $fields
911
     * @throws InvalidArgumentException
912
     */
913
    public function UninstallDomainForm(CompositeField $fields)
914
    {
915
        $domainInfos = $this->SendingDomainInstalled();
916
917
        $domain = $this->getDomain();
918
919
        if ($domainInfos && $domainInfos['valid_signing']) {
920
            $fields->push(new LiteralField('Info', $this->MessageHelper(
921
                _t('MandrillAdmin.DomainInstalled', 'Default domain {domain} is installed.', ['domain' => $domain]),
922
                'good'
923
            )));
924
        } else {
925
            $fields->push(new LiteralField('Info', $this->MessageHelper(
926
                _t('MandrillAdmin.DomainInstalledBut', 'Default domain {domain} is installed, but is not properly configured.'),
927
                'warning'
928
            )));
929
        }
930
        $fields->push(new LiteralField('doUninstallHook', $this->ButtonHelper(
931
            $this->Link('doUninstallHook'),
932
            _t('MandrillAdmin.DOUNINSTALLDOMAIN', 'Uninstall domain'),
933
            true
934
        )));
935
    }
936
}
937