Passed
Push — master ( 6e0d84...78080d )
by Thomas
15:02
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 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
     * @param MailerInterface $mailer
48
     * @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...
49
     */
50
    public static function getTransportFromMailer($mailer)
51
    {
52
        $r = new ReflectionObject($mailer);
53
        $p = $r->getProperty('transport');
54
        $p->setAccessible(true);
55
        return $p->getValue($mailer);
56
    }
57
58
    /**
59
     * @return string
60
     */
61
    public static function getApiKey()
62
    {
63
        return self::config()->api_key;
64
    }
65
66
    /**
67
     * Get the api client instance
68
     * @return SparkPostApiClient
69
     * @throws Exception
70
     */
71
    public static function getClient()
72
    {
73
        if (!self::$client) {
74
            $key = self::getApiKey();
75
            if (empty($key)) {
76
                throw new \Exception("api_key is not configured for " . __class__);
77
            }
78
            self::$client = new SparkPostApiClient($key);
79
            if (Director::isDev()) {
80
                self::$client->setCurlOption(CURLOPT_VERBOSE, true);
81
            }
82
            if (Environment::getEnv("SPARKPOST_EU")) {
83
                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

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

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