Passed
Push — master ( f3166b...4707af )
by Thomas
02:33
created

SparkPostHelper::isEmailDomainReady()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 28
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 8
eloc 19
c 3
b 1
f 0
nc 8
nop 1
dl 0
loc 28
rs 8.4444
1
<?php
2
3
namespace LeKoala\SparkPost;
4
5
use Exception;
6
use SilverStripe\Core\Environment;
7
use SilverStripe\Control\Director;
8
use SilverStripe\Control\Email\Email;
9
use SilverStripe\Control\Email\Mailer;
10
use SilverStripe\SiteConfig\SiteConfig;
11
use SilverStripe\Core\Injector\Injector;
12
use SilverStripe\Core\Config\Configurable;
13
use SilverStripe\Control\Email\SwiftMailer;
14
use LeKoala\SparkPost\Api\SparkPostApiClient;
15
use LeKoala\SparkPost\SparkPostSwiftTransport;
16
use SilverStripe\Core\Config\Config;
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 SilverStripe\Control\Email\SwiftMailer
0 ignored issues
show
Bug introduced by
The type LeKoala\SparkPost\Silver...ntrol\Email\SwiftMailer was not found. Did you mean SilverStripe\Control\Email\SwiftMailer? If so, make sure to prefix the type with \.
Loading history...
40
     */
41
    public static function getMailer()
42
    {
43
        return Injector::inst()->get(Mailer::class);
44
    }
45
46
    /**
47
     * @return string
48
     */
49
    public static function getApiKey()
50
    {
51
        return self::config()->api_key;
52
    }
53
54
    /**
55
     * Get the api client instance
56
     * @return SparkPostApiClient
57
     * @throws Exception
58
     */
59
    public static function getClient()
60
    {
61
        if (!self::$client) {
62
            $key = self::getApiKey();
63
            if (empty($key)) {
64
                throw new \Exception("api_key is not configured for " . __class__);
65
            }
66
            self::$client = new SparkPostApiClient($key);
67
            if (Director::isDev()) {
68
                self::$client->setCurlOption(CURLOPT_VERBOSE, true);
69
            }
70
            if (Environment::getEnv("SPARKPOST_EU")) {
71
                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

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

355
        if (is_array($to) || strpos(/** @scrutinizer ignore-type */ $to, ',') !== false) {
Loading history...
356
            return $to;
357
        }
358
        $original_to = $to;
359
        if (!empty($to)) {
360
            $to = EmailUtils::get_email_from_rfc_email($to);
361
            if (filter_var($to, FILTER_VALIDATE_EMAIL)) {
362
                return $original_to;
363
            }
364
        }
365
        $config = SiteConfig::current_site_config();
366
        $config_field = self::config()->siteconfig_to;
367
        if ($config_field && !empty($config->$config_field)) {
368
            return $config->$config_field;
369
        }
370
        if ($admin = Email::config()->admin_email) {
371
            return $admin;
372
        }
373
        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...
374
    }
375
376
    /**
377
     * Create a sensible default address based on domain name
378
     *
379
     * @return string
380
     */
381
    public static function createDefaultEmail()
382
    {
383
        $fulldom = Director::absoluteBaseURL();
384
        $host = parse_url($fulldom, PHP_URL_HOST);
385
        if (!$host) {
386
            $host = 'localhost';
387
        }
388
        $dom = str_replace('www.', '', $host);
389
390
        return 'postmaster@' . $dom;
391
    }
392
}
393