Passed
Push — master ( 2d41ee...7be4fb )
by Thomas
23:35
created

SparkPostHelper::getSendingEnabled()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 6
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
18
/**
19
 * This configurable class helps decoupling the api client from SilverStripe
20
 */
21
class SparkPostHelper
22
{
23
    use Configurable;
24
25
    const FROM_SITECONFIG = "SiteConfig";
26
    const FROM_ADMIN = "Admin";
27
    const FROM_DEFAULT = "Default";
28
29
    /**
30
     * Client instance
31
     *
32
     * @var SparkPostApiClient
33
     */
34
    protected static $client;
35
36
    /**
37
     * Get the mailer instance
38
     *
39
     * @return MailerInterface
40
     */
41
    public static function getMailer()
42
    {
43
        return Injector::inst()->get(MailerInterface::class);
44
    }
45
46
    /**
47
     * Useful when dealing with legacy code
48
     *
49
     * @param Email $Email
50
     * @return \Symfony\Component\Mime\Header\Headers|Swift_Mime_SimpleHeaderSet
0 ignored issues
show
Bug introduced by
The type LeKoala\SparkPost\Swift_Mime_SimpleHeaderSet 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...
51
     */
52
    public static function getHeaders($Email)
53
    {
54
        return method_exists($Email, 'getSwiftMessage') ? $Email->getSwiftMessage()->getHeaders() : $Email->getHeaders();
55
    }
56
57
    /**
58
     * @param MailerInterface $mailer
59
     * @return AbstractTransport|SparkpostApiTransport
0 ignored issues
show
Bug introduced by
The type LeKoala\SparkPost\AbstractTransport 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...
60
     */
61
    public static function getTransportFromMailer($mailer)
62
    {
63
        $r = new ReflectionObject($mailer);
64
        $p = $r->getProperty('transport');
65
        $p->setAccessible(true);
66
        return $p->getValue($mailer);
67
    }
68
69
    /**
70
     * @return string
71
     */
72
    public static function getApiKey()
73
    {
74
        return self::config()->api_key;
75
    }
76
77
    /**
78
     * Get the api client instance
79
     * @return SparkPostApiClient
80
     * @throws Exception
81
     */
82
    public static function getClient()
83
    {
84
        if (!self::$client) {
85
            $key = self::getApiKey();
86
            if (empty($key)) {
87
                throw new \Exception("api_key is not configured for " . __class__);
88
            }
89
            self::$client = new SparkPostApiClient($key);
90
            if (Director::isDev()) {
91
                self::$client->setCurlOption(CURLOPT_VERBOSE, true);
92
            }
93
            if (Environment::getEnv("SPARKPOST_EU")) {
94
                self::$client->setEuEndpoint(true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $euEndpoint of LeKoala\SparkPost\Api\Sp...Client::setEuEndpoint(). ( Ignorable by Annotation )

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

94
                self::$client->setEuEndpoint(/** @scrutinizer ignore-type */ true);
Loading history...
95
            }
96
            $subaccountId = self::config()->subaccount_id;
97
            if ($subaccountId) {
98
                self::$client->setSubaccount($subaccountId);
99
            }
100
        }
101
        return self::$client;
102
    }
103
104
    /**
105
     * Get the api client instance
106
     * @return LeKoala\SparkPost\Api\SparkPostApiClient
0 ignored issues
show
Bug introduced by
The type LeKoala\SparkPost\LeKoal...\Api\SparkPostApiClient was not found. Did you mean LeKoala\SparkPost\Api\SparkPostApiClient? If so, make sure to prefix the type with \.
Loading history...
107
     *
108
     * @throws Exception
109
     */
110
    public static function getMasterClient()
111
    {
112
        $masterKey = self::config()->master_api_key;
113
        if (!$masterKey) {
114
            return self::getClient();
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::getClient() returns the type LeKoala\SparkPost\Api\SparkPostApiClient which is incompatible with the documented return type LeKoala\SparkPost\LeKoal...\Api\SparkPostApiClient.
Loading history...
115
        }
116
        $client = new SparkPostApiClient($masterKey);
117
        return $client;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $client returns the type LeKoala\SparkPost\Api\SparkPostApiClient which is incompatible with the documented return type LeKoala\SparkPost\LeKoal...\Api\SparkPostApiClient.
Loading history...
118
    }
119
120
    /**
121
     * Get the log folder and create it if necessary
122
     *
123
     * @return string
124
     */
125
    public static function getLogFolder()
126
    {
127
        $logFolder = BASE_PATH . '/' . self::config()->log_folder;
128
        if (!is_dir($logFolder)) {
129
            mkdir($logFolder, 0755, true);
130
        }
131
        return $logFolder;
132
    }
133
134
135
    /**
136
     * Process environment variable to configure this module
137
     *
138
     * @return void
139
     */
140
    public static function init()
141
    {
142
        // Regular api key used for sending emails (including subaccount support)
143
        $api_key = self::getEnvApiKey();
144
        if ($api_key) {
145
            self::config()->api_key = $api_key;
146
        }
147
148
        // Master api key that is used to configure the account. If no api key is defined, the master api key is used
149
        $master_api_key = self::getEnvMasterApiKey();
150
        if ($master_api_key) {
151
            self::config()->master_api_key = $master_api_key;
152
            if (!self::config()->api_key) {
153
                self::config()->api_key = $master_api_key;
154
            }
155
        }
156
157
        $sending_disabled = self::getEnvSendingDisabled();
158
        if ($sending_disabled) {
159
            self::config()->disable_sending = $sending_disabled;
160
        }
161
        $enable_logging = self::getEnvEnableLogging();
162
        if ($enable_logging) {
163
            self::config()->enable_logging = $enable_logging;
164
        }
165
        $subaccount_id = self::getEnvSubaccountId();
166
        if ($subaccount_id) {
167
            self::config()->subaccount_id = $subaccount_id;
168
        }
169
170
        // We have a key, we can register the transport
171
        if (self::config()->api_key) {
172
            self::registerTransport();
173
        }
174
    }
175
176
    public static function getEnvApiKey()
177
    {
178
        return Environment::getEnv('SPARKPOST_API_KEY');
179
    }
180
181
    public static function getEnvMasterApiKey()
182
    {
183
        return Environment::getEnv('SPARKPOST_MASTER_API_KEY');
184
    }
185
186
    public static function getEnvSendingDisabled()
187
    {
188
        return Environment::getEnv('SPARKPOST_SENDING_DISABLED');
189
    }
190
191
    public static function getEnvEnableLogging()
192
    {
193
        return  Environment::getEnv('SPARKPOST_ENABLE_LOGGING');
194
    }
195
196
    public static function getEnvSubaccountId()
197
    {
198
        return  Environment::getEnv('SPARKPOST_SUBACCOUNT_ID');
199
    }
200
201
    public static function getSubaccountId()
202
    {
203
        return self::config()->subaccount_id;
204
    }
205
206
    public static function getEnvForceSender()
207
    {
208
        return Environment::getEnv('SPARKPOST_FORCE_SENDER');
209
    }
210
211
    public static function getWebhookUsername()
212
    {
213
        return self::config()->webhook_username;
214
    }
215
216
    public static function getWebhookPassword()
217
    {
218
        return self::config()->webhook_password;
219
    }
220
221
    /**
222
     * Register the transport with the client
223
     *
224
     * @return SparkPostApiTransport The updated mailer
225
     * @throws Exception
226
     */
227
    public static function registerTransport()
228
    {
229
        $client = self::getClient();
230
        $mailer = self::getMailer();
0 ignored issues
show
Unused Code introduced by
The assignment to $mailer is dead and can be removed.
Loading history...
231
        $transport = new SparkPostApiTransport($client);
232
        $mailer = new Mailer($transport);
233
        Injector::inst()->registerService($mailer, MailerInterface::class);
234
        return $mailer;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $mailer returns the type Symfony\Component\Mailer\Mailer which is incompatible with the documented return type LeKoala\SparkPost\SparkPostApiTransport.
Loading history...
235
    }
236
237
    /**
238
     * Update admin email so that we use our config email
239
     *
240
     * @return void
241
     */
242
    public static function forceAdminEmailOverride()
243
    {
244
        Config::modify()->set(Email::class, 'admin_email', self::resolveDefaultFromEmailType());
245
    }
246
247
    /**
248
     * Check if email is ready to send emails
249
     *
250
     * @param string $email
251
     * @return boolean
252
     */
253
    public static function isEmailDomainReady($email)
254
    {
255
        if (!$email) {
256
            return false;
257
        }
258
        $parts = explode("@", $email);
259
        if (count($parts) != 2) {
260
            return false;
261
        }
262
        $client = SparkPostHelper::getClient();
263
        try {
264
            $domain = $client->getSendingDomain(strtolower($parts[1]));
265
        } catch (Exception $ex) {
266
            return false;
267
        }
268
        if (!$domain) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $domain 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...
269
            return false;
270
        }
271
        if ($domain['status']['dkim_status'] != 'valid') {
272
            return false;
273
        }
274
        if ($domain['status']['compliance_status'] != 'valid') {
275
            return false;
276
        }
277
        if ($domain['status']['ownership_verified'] != true) {
278
            return false;
279
        }
280
        return true;
281
    }
282
283
    /**
284
     * Resolve default send from address
285
     *
286
     * Keep in mind that an email using send() without a from
287
     * will inject the admin_email. Therefore, SiteConfig
288
     * will not be used
289
     * See forceAdminEmailOverride() or use override_admin_email config
290
     *
291
     * @param string $from
292
     * @param bool $createDefault
293
     * @return string
294
     */
295
    public static function resolveDefaultFromEmail($from = null, $createDefault = true)
296
    {
297
        $configEmail = self::getSenderFromSiteConfig();
298
        $original_from = $from;
299
        if (!empty($from)) {
300
            // We have a set email but sending from admin => override if flag is set
301
            if (self::isAdminEmail($from) && $configEmail && self::config()->override_admin_email) {
302
                return $configEmail;
303
            }
304
            // If we have a sender, validate its email
305
            $from = EmailUtils::get_email_from_rfc_email($from);
306
            if (filter_var($from, FILTER_VALIDATE_EMAIL)) {
307
                return $original_from;
308
            }
309
        }
310
        // Look in siteconfig for default sender
311
        if ($configEmail) {
312
            return $configEmail;
313
        }
314
        // Use admin email if set
315
        if ($admin = Email::config()->admin_email) {
316
            return $admin;
317
        }
318
        // If we still don't have anything, create something based on the domain
319
        if ($createDefault) {
320
            return self::createDefaultEmail();
321
        }
322
        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...
323
    }
324
325
    /**
326
     * Returns what type of default email is used
327
     *
328
     * @return string
329
     */
330
    public static function resolveDefaultFromEmailType()
331
    {
332
        // Look in siteconfig for default sender
333
        if (self::getSenderFromSiteConfig()) {
334
            return self::FROM_SITECONFIG;
335
        }
336
        // Is admin email set ?
337
        if (Email::config()->admin_email) {
338
            return self::FROM_ADMIN;
339
        }
340
        return self::FROM_DEFAULT;
341
    }
342
343
    /**
344
     * @return string|false
345
     */
346
    public static function getSenderFromSiteConfig()
347
    {
348
        $config = SiteConfig::current_site_config();
349
        $config_field = self::config()->siteconfig_from;
350
        if ($config_field && !empty($config->$config_field)) {
351
            return $config->$config_field;
352
        }
353
        return false;
354
    }
355
356
    /**
357
     * @param string $email
358
     * @return boolean
359
     */
360
    public static function isAdminEmail($email)
361
    {
362
        $admin_email = Email::config()->admin_email;
363
        if (!$admin_email && $email) {
364
            return false;
365
        }
366
        $rfc_email = EmailUtils::get_email_from_rfc_email($email);
367
        $rfc_admin_email = EmailUtils::get_email_from_rfc_email($admin_email);
368
        return $rfc_email == $rfc_admin_email;
369
    }
370
371
    /**
372
     * @param string $email
373
     * @return boolean
374
     */
375
    public static function isDefaultEmail($email)
376
    {
377
        $rfc_email = EmailUtils::get_email_from_rfc_email($email);
378
        return $rfc_email == self::createDefaultEmail();
379
    }
380
381
    /**
382
     * Resolve default send to address
383
     *
384
     * @param string $to
385
     * @return string
386
     */
387
    public static function resolveDefaultToEmail($to = null)
388
    {
389
        // In case of multiple recipients, do not validate anything
390
        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

390
        if (is_array($to) || strpos(/** @scrutinizer ignore-type */ $to, ',') !== false) {
Loading history...
391
            return $to;
392
        }
393
        $original_to = $to;
394
        if (!empty($to)) {
395
            $to = EmailUtils::get_email_from_rfc_email($to);
396
            if (filter_var($to, FILTER_VALIDATE_EMAIL)) {
397
                return $original_to;
398
            }
399
        }
400
        $config = SiteConfig::current_site_config();
401
        $config_field = self::config()->siteconfig_to;
402
        if ($config_field && !empty($config->$config_field)) {
403
            return $config->$config_field;
404
        }
405
        if ($admin = Email::config()->admin_email) {
406
            return $admin;
407
        }
408
        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...
409
    }
410
411
    /**
412
     * Create a sensible default address based on domain name
413
     *
414
     * @return string
415
     */
416
    public static function createDefaultEmail()
417
    {
418
        $fulldom = Director::absoluteBaseURL();
419
        $host = parse_url($fulldom, PHP_URL_HOST);
420
        if (!$host) {
421
            $host = 'localhost';
422
        }
423
        $dom = str_replace('www.', '', $host);
424
425
        return 'postmaster@' . $dom;
426
    }
427
428
    /**
429
     * Is logging enabled?
430
     *
431
     * @return bool
432
     */
433
    public static function getLoggingEnabled()
434
    {
435
        if (self::config()->get('enable_logging')) {
436
            return true;
437
        }
438
        return false;
439
    }
440
441
    /**
442
     * Is sending enabled?
443
     *
444
     * @return bool
445
     */
446
    public static function getSendingEnabled()
447
    {
448
        if (self::config()->get('disable_sending')) {
449
            return false;
450
        }
451
        return true;
452
    }
453
}
454