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; |
||||
10 | use SilverStripe\Control\Director; |
||||
11 | use SilverStripe\Control\Email\Email; |
||||
12 | use SilverStripe\Control\Email\SwiftMailer; |
||||
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; |
||||
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
![]() |
|||||
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
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 For 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
![]() |
|||||
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
|
|||||
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
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
![]() |
|||||
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
|
|||||
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
|
|||||
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
Comprehensibility
Best Practice
introduced
by
|
|||||
356 | } |
||||
357 | } |
||||
358 | |||||
359 | return $data; |
||||
0 ignored issues
–
show
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. ![]() |
|||||
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
|
|||||
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
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 ![]() |
|||||
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
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
![]() |
|||||
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
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. ![]() |
|||||
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
|
|||||
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
|
|||||
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
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 ![]() |
|||||
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
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
![]() |
|||||
846 | } |
||||
847 | return false; |
||||
0 ignored issues
–
show
|
|||||
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
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
![]() |
|||||
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 |