• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Freegle / iznik-server / aec5fbfc-2ad7-4040-8ff2-d8bdbef64ecb

02 Jan 2025 12:59PM UTC coverage: 92.32% (-0.1%) from 92.423%
aec5fbfc-2ad7-4040-8ff2-d8bdbef64ecb

push

circleci

edwh
Ignore auto-expired outcomes in stats.

5 of 5 new or added lines in 1 file covered. (100.0%)

6 existing lines in 1 file now uncovered.

25496 of 27617 relevant lines covered (92.32%)

31.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.49
/include/misc/Mail.php
1
<?php
2
namespace Freegle\Iznik;
3

4
# This holds info about the different kinds of mails we send.
5
class Mail {
6
    const DIGEST = 1;
7
    const CHAT = 2;
8
    const REMOVED = 4;
9
    const THANK_DONATION = 5;
10
    const INVITATION = 6;
11
    const ASK_DONATION = 7;
12
    const DONATE_IPN = 8;
13
    const CHAT_CHASEUP_MODS = 9;
14
    const ADMIN = 10;
15
    const ALERT = 11;
16
    const NEARBY = 12;
17
    const DIGEST_OFF = 13;
18
    const EVENTS = 14;
19
    const EVENTS_OFF = 15;
20
    const NEWSLETTER = 16;
21
    const NEWSLETTER_OFF = 17;
22
    const RELEVANT = 18;
23
    const RELEVANT_OFF = 19;
24
    const VOLUNTEERING = 20;
25
    const VOLUNTEERING_OFF = 21;
26
    const VOLUNTEERING_RENEW = 22;
27
    const NEWSFEED = 23;
28
    const NEWSFEED_OFF = 24;
29
    const NEWSFEED_MODNOTIF = 25;
30
    const NOTIFICATIONS = 26;
31
    const NOTIFICATIONS_OFF = 27;
32
    const REQUEST = 28;
33
    const REQUEST_COMPLETED = 29;
34
    const STORY = 30;
35
    const STORY_OFF = 31;
36
    const STORY_ASK = 32;
37
    const WELCOME = 33;
38
    const FORGOT_PASSWORD = 34;
39
    const VERIFY_EMAIL = 35;
40
    const BAD_SMS = 36;
41
    const SPAM_WARNING = 37;
42
    const NOTICEBOARD = 38;
43
    const MERGE = 39;
44
    const UNSUBSCRIBE = 40;
45
    const MISSING = 41;
46
    const NOTICEBOARD_CHASEUP_OWNER = 42;
47
    const DONATE_EXTERNAL = 43;
48
    const REFER_TO_SUPPORT = 44;
49
    const NOT_A_MEMBER = 46;
50
    const MODMAIL = 47;
51
    const AUTOREPOST = 48;
52
    const CHASEUP = 49;
53
    const REPORTED_NEWSFEED = 50;
54
    const CALENDAR = 51;
55
    const FBL_OFF = 52;
56
    const PLEDGE_SIGNUP = 53;
57

58
    const DESCRIPTIONS = [
59
        Mail::DIGEST => 'Digest',
60
        Mail::CHAT => 'Chat',
61
        Mail::REMOVED => 'Removed',
62
        Mail::THANK_DONATION => 'ThankDonation',
63
        Mail::INVITATION => 'Invitation',
64
        Mail::ASK_DONATION => 'AskDonation',
65
        Mail::DONATE_IPN => 'DonateIPN',
66
        Mail::CHAT_CHASEUP_MODS => 'ChatChaseupMods',
67
        Mail::ADMIN => 'Admin',
68
        Mail::ALERT => 'Alert',
69
        Mail::DIGEST_OFF => 'MailOff',
70
        Mail::EVENTS => 'Events',
71
        Mail::EVENTS_OFF => 'EventsOff',
72
        Mail::NEWSLETTER => 'Newsletter',
73
        Mail::NEWSLETTER_OFF => 'NewsletterOff',
74
        Mail::RELEVANT => 'Relevant',
75
        Mail::RELEVANT_OFF => 'RelevantOff',
76
        Mail::VOLUNTEERING => 'Volunteering',
77
        Mail::VOLUNTEERING_OFF => 'VolunteeringOff',
78
        Mail::VOLUNTEERING_RENEW => 'VolunteeringRenew',
79
        Mail::NEWSFEED => 'Newsfeed',
80
        Mail::NEWSFEED_OFF => 'NewsfeedOff',
81
        Mail::NEWSFEED_MODNOTIF => 'NewsfeedModNotif',
82
        Mail::NEARBY => 'Nearby',
83
        Mail::NOTIFICATIONS => 'Notifications',
84
        Mail::NOTIFICATIONS_OFF => 'NotificationsOff',
85
        Mail::REQUEST => 'Request',
86
        Mail::REQUEST_COMPLETED => 'RequestCompleted',
87
        Mail::STORY => 'Story',
88
        Mail::STORY_OFF => 'StoryOff',
89
        Mail::STORY_ASK => 'StoryOff',
90
        Mail::WELCOME => 'Welcome',
91
        Mail::FORGOT_PASSWORD => 'ForgotPassword',
92
        Mail::VERIFY_EMAIL => 'VerifyEmail',
93
        Mail::BAD_SMS => 'BadSMS',
94
        Mail::SPAM_WARNING => 'SpamWarning',
95
        Mail::NOTICEBOARD => 'Noticeboard',
96
        Mail::UNSUBSCRIBE => 'Unsubscribe',
97
        Mail::MISSING => 'Missing',
98
        Mail::NOTICEBOARD_CHASEUP_OWNER => 'NoticeboardChaseupOwner',
99
        Mail::REFER_TO_SUPPORT => 'ReferToSupport',
100
        Mail::MERGE => 'Merge',
101
        Mail::DONATE_EXTERNAL => 'DonateExternal',
102
        Mail::NOT_A_MEMBER => 'NotAMember',
103
        Mail::MODMAIL => 'ModMail',
104
        Mail::AUTOREPOST => 'AutoRepost',
105
        Mail::CHASEUP => 'Chaseup',
106
        Mail::REPORTED_NEWSFEED => 'ReportedNewsfeed',
107
        Mail::CALENDAR => 'Calendar',
108
        Mail::FBL_OFF => 'FBLOff'
109
    ];
110

111
    private static $mailers = [];
112

113
    public static function getDescription($type) {
114
        return(Mail::DESCRIPTIONS[$type]);
120✔
115
    }
116

117
    public static function matchingId($type, $qualifier) {
118
        # Return Path is picky about the format - needs to be alphabetic then number.
119
        #
120
        # It also applies a limit per month so use something which will only change every week.  That way all our
121
        # mails of a particular type and qualifier will be grouped.
122
        $matchingid = 'freegle' . $type. str_pad($qualifier < 0 ? (100 + $qualifier) : $qualifier, 3, '0') . date("Ymd000000", strtotime('Last Sunday'));
×
UNCOV
123
        return($matchingid);
×
124
    }
125

126
    public static function addHeaders($dbhr, $dbhm, $msg, $type, $userid = 0, $qualifier = 0) {
127
        $headers = $msg->getHeaders();
120✔
128

129
        # We add a header of our own.  TN uses this.
130
        $headers->addTextHeader('X-Freegle-Mail-Type', Mail::getDescription($type));
120✔
131

132
        # Google feedback loop uses Feedback-ID as per
133
        # https://support.google.com/mail/answer/6254652?hl=en&ref_topic=7279058
134
        $headers->addTextHeader('Feedback-ID', "$qualifier:$userid:" . Mail::getDescription($type) . ':freegle');
120✔
135

136
        # Add one-click unsubscribe.
137
        $u = new User($dbhr, $dbhm);
120✔
138
        $headers->addTextHeader('List-Unsubscribe', $u->listUnsubscribe($userid, Mail::getDescription($type)));
120✔
139
        $headers->addTextHeader('List-Unsubscribe-Post', 'List-Unsubscribe=One-Click');
120✔
140
    }
141

142
    public static function getSeeds($dbhr, $dbhm) {
143
        $users = $dbhr->preQuery("SELECT * FROM returnpath_seedlist WHERE active = 1 AND userid IS NOT NULL;");
1✔
144

145
        foreach ($users as $user) {
1✔
146
            $ret[] = $user['userid'];
1✔
147

148
            if ($user['oneshot']) {
1✔
149
                $dbhm->preExec("UPDATE returnpath_seedlist SET active = 0 WHERE id = ?;", [
1✔
150
                    $user['id']
1✔
151
                ]);
1✔
152
            }
153
        }
154

155
        return($users);
1✔
156
    }
157

158
    public static function getMailer($host = 'localhost', $spoolname = '/spool') {
159
        $key = $host . $spoolname;
103✔
160

161
        if (!array_key_exists($key, self::$mailers)) {
103✔
162
            if (!file_exists(IZNIK_BASE . $spoolname)) {
7✔
163
                @mkdir(IZNIK_BASE . $spoolname);
7✔
164
                @chmod(IZNIK_BASE . $spoolname, 755);
7✔
165
                @chgrp(IZNIK_BASE . $spoolname, 'www-data');
7✔
166
                @chown(IZNIK_BASE . $spoolname, 'www-data');
7✔
167
            }
168

169
            $spool = new \Swift_FileSpool(IZNIK_BASE . $spoolname);
7✔
170
            $spooltrans = \Swift_SpoolTransport::newInstance($spool);
7✔
171
            $smtptrans = \Swift_SmtpTransport::newInstance($host);
7✔
172
            $transport = \Swift_FailoverTransport::newInstance([
7✔
173
                                                                   $smtptrans,
7✔
174
                                                                   $spooltrans
7✔
175
                                                               ]);
7✔
176
            $mailer = \Swift_Mailer::newInstance($transport);
7✔
177
            self::$mailers[$key] = [$transport, $mailer];
7✔
178
        }
179

180
        return self::$mailers[$key];
103✔
181
    }
182

183
    public static function realEmail($email) {
184
        # TODO What's the right way to spot a 'real' address?
185
        return(
2✔
186
            stripos($email, USER_DOMAIN) === FALSE &&
2✔
187
            stripos($email, 'fbuser') === FALSE &&
2✔
188
            stripos($email, 'trashnothing.com') === FALSE &&
2✔
189
            stripos($email, '@ilovefreegle.org') === FALSE &&
2✔
190
            stripos($email, 'modtools.org') === FALSE
2✔
191
        );
2✔
192
    }
193

194
    public static function ourDomain($email) {
195
        $ourdomains = explode(',', OURDOMAINS);
331✔
196

197
        $ours = FALSE;
331✔
198
        foreach ($ourdomains as $domain) {
331✔
199
            if (stripos($email, '@' . $domain) !== FALSE) {
331✔
200
                $ours = TRUE;
85✔
201
                break;
85✔
202
            }
203
        }
204

205
        return($ours);
331✔
206
    }
207

208
    public static function checkSpamhaus($url) {
209
        $ret = FALSE;
11✔
210

211
        if (strpos($url, 'https://goo.gl') === 0) {
11✔
212
            # Google's shortening service.  Fetch.
213
            try {
214
                $exp = file_get_contents('https://www.googleapis.com/urlshortener/v1/url?key=' . GOOGLE_VISION_KEY . '&shortUrl=' . $url);
1✔
215

216
                if ($exp) {
1✔
UNCOV
217
                    $exp = json_decode($exp, TRUE);
×
218

219
                    if ($exp) {
×
UNCOV
220
                        if (Utils::pres('longUrl', $exp)) {
×
221
                            $url = $exp['longUrl'];
1✔
222
                        }
223
                    }
224
                }
UNCOV
225
            } catch (\Exception $e) {}
×
226
        }
227

228
        $parsed = parse_url( $url );
11✔
229

230
        if (isset($parsed['host'])) {
11✔
231
            // Remove www. from domain (but not from www.com)
232
            $parsed['host'] = preg_replace('/^www\.(.+\.)/i', '$1', $parsed['host']);
11✔
233

234
            $blacklists = array(
11✔
235
                'dbl.spamhaus.org',
11✔
236
            );
11✔
237

238
            foreach ($blacklists as $blacklist) {
11✔
239
                $domain = $parsed['host'] . '.' . $blacklist . '.';
11✔
240
                try {
241
                    $record = dns_get_record($domain, DNS_A);
11✔
242

243
                    if ($record != NULL && count($record) > 0) {
11✔
244
                        foreach ($record as $entry) {
11✔
245
                            if (array_key_exists('ip', $entry) && strpos($entry['ip'], '127.0.1') === 0) {
10✔
246
                                error_log("Spamhaus blocked $url");
×
UNCOV
247
                                $ret = TRUE;
×
248
                            }
249
                        }
250
                    }
251
                } catch (\Exception $e) {
×
UNCOV
252
                    error_log("dns_get_record for $domain failed " . $e->getMessage());
×
253
                }
254
            }
255
        }
256

257
        return $ret;
11✔
258
    }
259
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc