Passed
Push — master ( 95b6e2...350bb5 )
by Thomas
13:37
created

SparkPostHelper::hasEnvSendingDisabled()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace LeKoala\SparkPost;
4
5
use Exception;
6
use ReflectionObject;
7
use SilverStripe\Control\Director;
8
use SilverStripe\Core\Environment;
9
use SilverStripe\Core\Config\Config;
10
use Symfony\Component\Mailer\Mailer;
11
use SilverStripe\Control\Email\Email;
12
use SilverStripe\SiteConfig\SiteConfig;
13
use SilverStripe\Core\Injector\Injector;
14
use SilverStripe\Core\Config\Configurable;
15
use LeKoala\SparkPost\Api\SparkPostApiClient;
16
use Symfony\Component\Mailer\MailerInterface;
17
use SilverStripe\Core\Injector\InjectorNotFoundException;
18
19
/**
20
 * This configurable class helps decoupling the api client from SilverStripe
21
 */
22
class SparkPostHelper
23
{
24
    use Configurable;
25
26
    const FROM_SITECONFIG = "SiteConfig";
27
    const FROM_ADMIN = "Admin";
28
    const FROM_DEFAULT = "Default";
29
30
    /**
31
     * Client instance
32
     *
33
     * @var ?\LeKoala\SparkPost\Api\SparkPostApiClient
34
     */
35
    protected static $client;
36
37
    /**
38
     * Get the mailer instance
39
     *
40
     * @return MailerInterface
41
     */
42
    public static function getMailer()
43
    {
44
        return Injector::inst()->get(MailerInterface::class);
45
    }
46
47
    /**
48
     * @param MailerInterface $mailer
49
     * @return \Symfony\Component\Mailer\Transport\AbstractTransport|SparkPostApiTransport
50
     */
51
    public static function getTransportFromMailer($mailer)
52
    {
53
        $r = new ReflectionObject($mailer);
54
        $p = $r->getProperty('transport');
55
        $p->setAccessible(true);
56
        return $p->getValue($mailer);
57
    }
58
59
    /**
60
     * @return string
61
     */
62
    public static function getApiKey()
63
    {
64
        return self::config()->api_key;
65
    }
66
67
    /**
68
     * Get the api client instance
69
     * @return SparkPostApiClient
70
     * @throws Exception
71
     */
72
    public static function getClient()
73
    {
74
        if (!self::$client) {
75
            $key = self::getApiKey();
76
            if (empty($key)) {
77
                throw new \Exception("api_key is not configured for " . __class__);
78
            }
79
            self::$client = new SparkPostApiClient($key);
80
            if (Director::isDev()) {
81
                //@phpstan-ignore-next-line
82
                self::$client->setCurlOption(CURLOPT_VERBOSE, true);
83
            }
84
            if (Environment::getEnv("SPARKPOST_EU")) {
85
                self::$client->setEuEndpoint(true);
86
            }
87
            $subaccountId = self::config()->subaccount_id;
88
            if ($subaccountId) {
89
                self::$client->setSubaccount($subaccountId);
90
            }
91
        }
92
        return self::$client;
93
    }
94
95
    /**
96
     * Get the api client instance
97
     * @return \LeKoala\SparkPost\Api\SparkPostApiClient
98
     * @throws Exception
99
     */
100
    public static function getMasterClient()
101
    {
102
        $masterKey = self::config()->master_api_key;
103
        if (!$masterKey) {
104
            return self::getClient();
105
        }
106
        $client = new SparkPostApiClient($masterKey);
107
        return $client;
108
    }
109
110
    /**
111
     * Get the log folder and create it if necessary
112
     *
113
     * @return string
114
     */
115
    public static function getLogFolder()
116
    {
117
        $logFolder = BASE_PATH . '/' . self::config()->log_folder;
118
        if (!is_dir($logFolder)) {
119
            mkdir($logFolder, 0755, true);
120
        }
121
        return $logFolder;
122
    }
123
124
125
    /**
126
     * Process environment variable to configure this module
127
     *
128
     * @return void
129
     */
130
    public static function init()
131
    {
132
        // Regular api key used for sending emails (including subaccount support)
133
        $api_key = self::getEnvApiKey();
134
        if ($api_key) {
135
            self::config()->api_key = $api_key;
136
        }
137
138
        // Master api key that is used to configure the account. If no api key is defined, the master api key is used
139
        $master_api_key = self::getEnvMasterApiKey();
140
        if ($master_api_key) {
141
            self::config()->master_api_key = $master_api_key;
142
            if (!self::config()->api_key) {
143
                self::config()->api_key = $master_api_key;
144
            }
145
        }
146
147
        $sending_disabled = self::getEnvSendingDisabled();
148
        if ($sending_disabled === false) {
149
            // In dev, if we didn't set a value, disable by default
150
            // This can avoid sending emails by mistake :-) oops!
151
            if (Director::isDev() && !self::hasEnvSendingDisabled()) {
152
                $sending_disabled = true;
153
            }
154
        }
155
        if ($sending_disabled) {
156
            self::config()->disable_sending = $sending_disabled;
157
        }
158
        $enable_logging = self::getEnvEnableLogging();
159
        if ($enable_logging) {
160
            self::config()->enable_logging = $enable_logging;
161
        }
162
        $subaccount_id = self::getEnvSubaccountId();
163
        if ($subaccount_id) {
164
            self::config()->subaccount_id = $subaccount_id;
165
        }
166
167
        // We have a key, we can register the transport
168
        if (self::config()->api_key) {
169
            self::registerTransport();
170
        }
171
    }
172
173
    /**
174
     * @return mixed
175
     */
176
    public static function getEnvApiKey()
177
    {
178
        return Environment::getEnv('SPARKPOST_API_KEY');
179
    }
180
181
    /**
182
     * @return mixed
183
     */
184
    public static function getEnvMasterApiKey()
185
    {
186
        return Environment::getEnv('SPARKPOST_MASTER_API_KEY');
187
    }
188
189
    /**
190
     * @return mixed
191
     */
192
    public static function getEnvSendingDisabled()
193
    {
194
        return Environment::getEnv('SPARKPOST_SENDING_DISABLED');
195
    }
196
197
    /**
198
     * @return bool
199
     */
200
    public static function hasEnvSendingDisabled()
201
    {
202
        return Environment::hasEnv('SPARKPOST_SENDING_DISABLED');
203
    }
204
205
    /**
206
     * @return mixed
207
     */
208
    public static function getEnvEnableLogging()
209
    {
210
        return  Environment::getEnv('SPARKPOST_ENABLE_LOGGING');
211
    }
212
213
    /**
214
     * @return mixed
215
     */
216
    public static function getEnvSubaccountId()
217
    {
218
        return  Environment::getEnv('SPARKPOST_SUBACCOUNT_ID');
219
    }
220
221
    /**
222
     * @return mixed
223
     */
224
    public static function getSubaccountId()
225
    {
226
        return self::config()->subaccount_id;
227
    }
228
229
    /**
230
     * @return mixed
231
     */
232
    public static function getEnvForceSender()
233
    {
234
        return Environment::getEnv('SPARKPOST_FORCE_SENDER');
235
    }
236
237
    /**
238
     * @return mixed
239
     */
240
    public static function getWebhookUsername()
241
    {
242
        return self::config()->webhook_username;
243
    }
244
245
    /**
246
     * @return mixed
247
     */
248
    public static function getWebhookPassword()
249
    {
250
        return self::config()->webhook_password;
251
    }
252
253
    /**
254
     * Register the transport with the client
255
     *
256
     * @return Mailer The updated mailer
257
     * @throws Exception
258
     */
259
    public static function registerTransport()
260
    {
261
        $client = self::getClient();
262
        $transport = new SparkPostApiTransport($client);
263
        $mailer = new Mailer($transport);
264
        Injector::inst()->registerService($mailer, MailerInterface::class);
265
        return $mailer;
266
    }
267
268
    /**
269
     * Update admin email so that we use our config email
270
     *
271
     * @return void
272
     */
273
    public static function forceAdminEmailOverride()
274
    {
275
        Config::modify()->set(Email::class, 'admin_email', self::resolveDefaultFromEmailType());
276
    }
277
278
    /**
279
     * @param string $email
280
     * @return bool
281
     */
282
    public static function isEmailSuppressed($email)
283
    {
284
        $client = self::getClient();
285
286
        $state = $client->getSuppression($email);
287
        if (empty($state)) {
288
            return false;
289
        }
290
        return true;
291
    }
292
293
    /**
294
     * @param string $email
295
     * @return void
296
     */
297
    public static function removeSuppression($email)
298
    {
299
        self::getClient()->deleteSuppression($email);
300
    }
301
302
    /**
303
     * Check if email is ready to send emails
304
     *
305
     * @param string $email
306
     * @return boolean
307
     */
308
    public static function isEmailDomainReady($email)
309
    {
310
        if (!$email) {
311
            return false;
312
        }
313
        $email = EmailUtils::get_email_from_rfc_email($email);
314
        $parts = explode("@", $email);
315
        if (count($parts) != 2) {
316
            return false;
317
        }
318
        $client = SparkPostHelper::getClient();
319
        try {
320
            $domain = $client->getSendingDomain(strtolower($parts[1]));
321
        } catch (Exception $ex) {
322
            return false;
323
        }
324
        if (!$domain) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $domain of type array<mixed,mixed> 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...
325
            return false;
326
        }
327
        if ($domain['status']['dkim_status'] != 'valid') {
328
            return false;
329
        }
330
        if ($domain['status']['compliance_status'] != 'valid') {
331
            return false;
332
        }
333
        if ($domain['status']['ownership_verified'] != true) {
334
            return false;
335
        }
336
        return true;
337
    }
338
339
    /**
340
     * Resolve default send from address
341
     *
342
     * Keep in mind that an email using send() without a from
343
     * will inject the admin_email. Therefore, SiteConfig
344
     * will not be used
345
     * See forceAdminEmailOverride() or use override_admin_email config
346
     *
347
     * @param string $from
348
     * @param bool $createDefault
349
     * @return string|array<string,string>|false
350
     */
351
    public static function resolveDefaultFromEmail($from = null, $createDefault = true)
352
    {
353
        $configEmail = self::getSenderFromSiteConfig();
354
        $original_from = $from;
355
        if (!empty($from)) {
356
            // We have a set email but sending from admin => override if flag is set
357
            if (self::isAdminEmail($from) && $configEmail && self::config()->override_admin_email) {
358
                return $configEmail;
359
            }
360
            // If we have a sender, validate its email
361
            $from = EmailUtils::get_email_from_rfc_email($from);
362
            if (filter_var($from, FILTER_VALIDATE_EMAIL)) {
363
                return $original_from;
364
            }
365
        }
366
        // Look in siteconfig for default sender
367
        if ($configEmail) {
368
            return $configEmail;
369
        }
370
        // Use admin email if set
371
        if ($adminEmail = Email::config()->admin_email) {
372
            if (is_array($adminEmail) && count($adminEmail) > 0) {
373
                $email = array_keys($adminEmail)[0];
374
                return [$email => $adminEmail[$email]];
375
            } elseif (is_string($adminEmail)) {
376
                return $adminEmail;
377
            }
378
        }
379
        // If we still don't have anything, create something based on the domain
380
        if ($createDefault) {
381
            return self::createDefaultEmail();
382
        }
383
        return false;
384
    }
385
386
    /**
387
     * Returns what type of default email is used
388
     *
389
     * @return string
390
     */
391
    public static function resolveDefaultFromEmailType()
392
    {
393
        // Look in siteconfig for default sender
394
        if (self::getSenderFromSiteConfig()) {
395
            return self::FROM_SITECONFIG;
396
        }
397
        // Is admin email set ?
398
        if (Email::config()->admin_email) {
399
            return self::FROM_ADMIN;
400
        }
401
        return self::FROM_DEFAULT;
402
    }
403
404
    /**
405
     * @return string|false
406
     */
407
    public static function getSenderFromSiteConfig()
408
    {
409
        $config = SiteConfig::current_site_config();
410
        $config_field = self::config()->siteconfig_from;
411
        if ($config_field && !empty($config->$config_field)) {
412
            return $config->$config_field;
413
        }
414
        return false;
415
    }
416
417
    /**
418
     * @param string $email
419
     * @return boolean
420
     */
421
    public static function isAdminEmail($email)
422
    {
423
        $admin_email = Email::config()->admin_email;
424
        if (!$admin_email && $email) {
425
            return false;
426
        }
427
        $rfc_email = EmailUtils::get_email_from_rfc_email($email);
428
        $rfc_admin_email = EmailUtils::get_email_from_rfc_email($admin_email);
429
        return $rfc_email == $rfc_admin_email;
430
    }
431
432
    /**
433
     * @param string $email
434
     * @return boolean
435
     */
436
    public static function isDefaultEmail($email)
437
    {
438
        $rfc_email = EmailUtils::get_email_from_rfc_email($email);
439
        return $rfc_email == self::createDefaultEmail();
440
    }
441
442
    /**
443
     * Resolve default send to address
444
     *
445
     * @param string|array<mixed>|null $to
446
     * @return string|array<mixed>|null
447
     */
448
    public static function resolveDefaultToEmail($to = null)
449
    {
450
        // In case of multiple recipients, do not validate anything
451
        if (is_array($to) || strpos($to, ',') !== false) {
0 ignored issues
show
Bug introduced by
It seems like $to can also be of type null; however, parameter $haystack of strpos() 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

451
        if (is_array($to) || strpos(/** @scrutinizer ignore-type */ $to, ',') !== false) {
Loading history...
452
            return $to;
453
        }
454
        $original_to = $to;
455
        if (!empty($to)) {
456
            $to = EmailUtils::get_email_from_rfc_email($to);
457
            if (filter_var($to, FILTER_VALIDATE_EMAIL)) {
458
                return $original_to;
459
            }
460
        }
461
        $config = SiteConfig::current_site_config();
462
        $config_field = self::config()->siteconfig_to;
463
        if ($config_field && !empty($config->$config_field)) {
464
            return $config->$config_field;
465
        }
466
        if ($admin = Email::config()->admin_email) {
467
            return $admin;
468
        }
469
        return null;
470
    }
471
472
    /**
473
     * Create a sensible default address based on domain name
474
     *
475
     * @return string
476
     */
477
    public static function createDefaultEmail()
478
    {
479
        $fulldom = Director::absoluteBaseURL();
480
        $host = parse_url($fulldom, PHP_URL_HOST);
481
        if (!$host) {
482
            $host = 'localhost';
483
        }
484
        $dom = str_replace('www.', '', $host);
485
486
        return 'postmaster@' . $dom;
487
    }
488
489
    /**
490
     * Is logging enabled?
491
     *
492
     * @return bool
493
     */
494
    public static function getLoggingEnabled()
495
    {
496
        if (self::config()->get('enable_logging')) {
497
            return true;
498
        }
499
        return false;
500
    }
501
502
    /**
503
     * Is sending enabled?
504
     *
505
     * @return bool
506
     */
507
    public static function getSendingEnabled()
508
    {
509
        if (self::config()->get('disable_sending')) {
510
            return false;
511
        }
512
        return true;
513
    }
514
}
515