Passed
Push — master ( c65031...5c5fd2 )
by Thomas
03:11
created

SparkPostHelper::getWebhookPassword()   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
nc 1
nop 0
dl 0
loc 3
rs 10
c 1
b 0
f 0
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
use Swift_Mailer;
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 SparkPostApiClient
34
     */
35
    protected static $client;
36
37
    /**
38
     * Get the mailer instance
39
     *
40
     * @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...
41
     */
42
    public static function getMailer()
43
    {
44
        return Injector::inst()->get(Mailer::class);
45
    }
46
47
    /**
48
     * @return string
49
     */
50
    public static function getApiKey()
51
    {
52
        return self::config()->api_key;
53
    }
54
55
    /**
56
     * Get the api client instance
57
     * @return SparkPostApiClient
58
     * @throws Exception
59
     */
60
    public static function getClient()
61
    {
62
        if (!self::$client) {
63
            $key = self::getApiKey();
64
            if (empty($key)) {
65
                throw new \Exception("api_key is not configured for " . __class__);
66
            }
67
            self::$client = new SparkPostApiClient($key);
68
            if (Director::isDev()) {
69
                self::$client->setCurlOption(CURLOPT_VERBOSE, true);
70
            }
71
            if (Environment::getEnv("SPARKPOST_EU")) {
72
                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

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

371
        if (is_array($to) || strpos(/** @scrutinizer ignore-type */ $to, ',') !== false) {
Loading history...
372
            return $to;
373
        }
374
        $original_to = $to;
375
        if (!empty($to)) {
376
            $to = EmailUtils::get_email_from_rfc_email($to);
377
            if (filter_var($to, FILTER_VALIDATE_EMAIL)) {
378
                return $original_to;
379
            }
380
        }
381
        $config = SiteConfig::current_site_config();
382
        $config_field = self::config()->siteconfig_to;
383
        if ($config_field && !empty($config->$config_field)) {
384
            return $config->$config_field;
385
        }
386
        if ($admin = Email::config()->admin_email) {
387
            return $admin;
388
        }
389
        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...
390
    }
391
392
    /**
393
     * Create a sensible default address based on domain name
394
     *
395
     * @return string
396
     */
397
    public static function createDefaultEmail()
398
    {
399
        $fulldom = Director::absoluteBaseURL();
400
        $host = parse_url($fulldom, PHP_URL_HOST);
401
        if (!$host) {
402
            $host = 'localhost';
403
        }
404
        $dom = str_replace('www.', '', $host);
405
406
        return 'postmaster@' . $dom;
407
    }
408
}
409