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

Freegle / iznik-server / ec7a2b18-1f1b-46ff-8c5b-967e83bf65ff

09 Jun 2024 09:37AM UTC coverage: 94.863%. Remained the same
ec7a2b18-1f1b-46ff-8c5b-967e83bf65ff

push

circleci

edwh
Test fixes.

25430 of 26807 relevant lines covered (94.86%)

31.53 hits per line

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

92.36
/include/group/Group.php
1
<?php
2
namespace Freegle\Iznik;
3

4
class Group extends Entity
5
{
6
    # We have a cache of groups, because we access groups a _lot_, and this can speed things up significantly by avoiding
7
    # hitting the DB.  This is only preserved within this process.
8
    static $processCache = [];
9
    static $processCacheDeleted = [];
10
    const PROCESS_CACHE_SIZE = 100;
11

12
    /** @var  $dbhm LoggedPDO */
13
    var $publicatts = array('id', 'nameshort', 'namefull', 'nameabbr', 'namedisplay', 'settings', 'type', 'region', 'logo', 'publish',
14
        'onhere', 'ontn', 'membercount', 'modcount', 'lat', 'lng',
15
        'profile', 'cover', 'onmap', 'tagline', 'legacyid', 'external', 'welcomemail', 'description',
16
        'contactmail', 'fundingtarget', 'affiliationconfirmed', 'affiliationconfirmedby', 'mentored', 'privategroup', 'defaultlocation',
17
        'moderationstatus', 'maxagetoshow', 'nearbygroups', 'microvolunteering', 'microvolunteeringoptions', 'autofunctionoverride', 'overridemoderation', 'precovidmoderated', 'onlovejunk');
18

19
    const GROUP_REUSE = 'Reuse';
20
    const GROUP_FREEGLE = 'Freegle';
21
    const GROUP_OTHER = 'Other';
22
    const GROUP_UT = 'UnitTest';
23

24
    const POSTING_MODERATED = 'MODERATED';
25
    const POSTING_PROHIBITED = 'PROHIBITED';
26
    const POSTING_DEFAULT = 'DEFAULT';
27
    const POSTING_UNMODERATED = 'UNMODERATED';
28

29
    const OVERRIDE_MODERATION_NONE = 'None';
30
    const OVERRIDE_MODERATION_ALL = 'ModerateAll';
31

32
    const FILTER_NONE = 0;
33
    const FILTER_WITHCOMMENTS = 1;
34
    const FILTER_MODERATORS = 2;
35
    const FILTER_BOUNCING = 3;
36
    const FILTER_MOSTACTIVE = 4;
37
    const FILTER_BANNED = 5;
38

39
    /** @var  $log Log */
40
    private $log;
41

42
    public $defaultSettings;
43

44
    function __construct(LoggedPDO $dbhr, LoggedPDO $dbhm, $id = NULL, $atts = NULL)
45
    {
46
        # We need special SQL to pick up postvisibility in text.
47
        $sql = "SELECT `groups`.*, ST_AsText(postvisibility) AS postvisibility FROM `groups` WHERE id = ?";
491✔
48

49
        if ($atts) {
491✔
50
            # We've been passed all the atts we need to construct the group
51
            $this->fetch($dbhr, $dbhm, $id, 'groups', 'group', $this->publicatts, $atts, FALSE, $sql);
123✔
52
        } else {
53
            $this->fetch($dbhr, $dbhm, $id, 'groups', 'group', $this->publicatts, NULL, FALSE, $sql);
491✔
54

55
            if ($id && !$this->id) {
491✔
56
                # We were passed an id, but didn't find the group.  See if the id is a legacyid.
57
                #
58
                # This assumes that the legacy and current ids don't clash.  Which they don't.  So that's a good assumption.
59
                $groups = $this->dbhr->preQuery("SELECT id FROM `groups` WHERE legacyid = ?;", [ $id ]);
3✔
60
                foreach ($groups as $group) {
3✔
61
                    $this->fetch($dbhr, $dbhm, $group['id'], 'groups', 'group', $this->publicatts, NULL, FALSE, $sql);
×
62
                }
63
            }
64
        }
65

66
        $this->setDefaults();
491✔
67
    }
68

69
    public function setDefaults() {
70
        $this->defaultSettings = [
491✔
71
            'showchat' => 1,
491✔
72
            'communityevents' => 1,
491✔
73
            'volunteering' => 1,
491✔
74
            'stories' => 1,
491✔
75
            'includearea' => 1,
491✔
76
            'includepc' => 1,
491✔
77
            'moderated' => 0,
491✔
78
            'allowedits' => [
491✔
79
                'moderated' => 1,
491✔
80
                'group' => 1
491✔
81
            ],
491✔
82
            'autoapprove' => [
491✔
83
                'members' => 0,
491✔
84
                'messages' => 0
491✔
85
            ], 'duplicates' => [
491✔
86
                'check' => 1,
491✔
87
                'offer' => 14,
491✔
88
                'taken' => 14,
491✔
89
                'wanted' => 14,
491✔
90
                'received' => 14
491✔
91
            ], 'spammers' => [
491✔
92
                'chatreview' => 1,
491✔
93
                'messagereview' => 1
491✔
94
            ], 'joiners' => [
491✔
95
                'check' => 1,
491✔
96
                'threshold' => 5
491✔
97
            ], 'keywords' => [
491✔
98
                'OFFER' => 'OFFER',
491✔
99
                'TAKEN' => 'TAKEN',
491✔
100
                'WANTED' => 'WANTED',
491✔
101
                'RECEIVED' => 'RECEIVED'
491✔
102
            ], 'reposts' => [
491✔
103
                'offer' => 3,
491✔
104
                'wanted' => 7,
491✔
105
                'max' => 5,
491✔
106
                'chaseups' => 5
491✔
107
            ],
491✔
108
            'relevant' => 1,
491✔
109
            'newsfeed' => 1,
491✔
110
            'newsletter' => 1,
491✔
111
            'businesscards' => 1,
491✔
112
            'autoadmins' => 1,
491✔
113
            'mentored' => 0,
491✔
114
            'nearbygroups' => 5,
491✔
115
            'showjoin' => 0,
491✔
116
            'engagement' => 1
491✔
117
        ];
491✔
118

119
        if ($this->id) {
491✔
120
            if (!$this->group['settings'] || strlen($this->group['settings']) == 0) {
457✔
121
                $this->group['settings'] = json_encode($this->defaultSettings);
456✔
122
            }
123
        }
124

125
        $this->log = new Log($this->dbhr, $this->dbhm);
491✔
126
    }
127

128
    public static function get(LoggedPDO $dbhr, LoggedPDO $dbhm, $id = NULL, $gsecache = TRUE) {
129
        if ($id) {
480✔
130
            # We cache the constructed group.
131
            if ($gsecache && array_key_exists($id, Group::$processCache) && Group::$processCache[$id]->getId() == $id) {
461✔
132
                # We found it.
133
                #error_log("Found $id in cache");
134

135
                # @var Group
136
                $g = Group::$processCache[$id];
148✔
137

138
                if (!Group::$processCacheDeleted[$id]) {
148✔
139
                    # And it's not zapped - so we can use it.
140
                    #error_log("Not zapped");
141
                    return ($g);
147✔
142
                } else {
143
                    # It's zapped - so refetch.
144
                    #error_log("Zapped, refetch " . $id);
145
                    $g->fetch($g->dbhr, $g->dbhm, $id, 'groups', 'group', $g->publicatts, NULL, FALSE);
7✔
146

147
                    if (!$g->group['settings'] || strlen($g->group['settings']) == 0) {
7✔
148
                        $g->group['settings'] = json_encode($g->defaultSettings);
6✔
149
                    }
150

151
                    Group::$processCache[$id] = $g;
7✔
152
                    Group::$processCacheDeleted[$id] = FALSE;
7✔
153
                    return($g);
7✔
154
                }
155
            }
156
        }
157

158
        #error_log("$id not in cache");
159
        $g = new Group($dbhr, $dbhm, $id);
479✔
160

161
        if ($id && count(Group::$processCache) < Group::PROCESS_CACHE_SIZE) {
479✔
162
            # Store for next time in this process.
163
            #error_log("store $id in cache");
164
            Group::$processCache[$id] = $g;
148✔
165
            Group::$processCacheDeleted[$id] = FALSE;
148✔
166
        }
167

168
        return($g);
479✔
169
    }
170

171
    public static function clearCache($id = NULL) {
172
        # Remove this group from our process cache.
173
        #error_log("Clear $id from cache");
174
        if ($id) {
246✔
175
            Group::$processCacheDeleted[$id] = TRUE;
246✔
176
        } else {
177
            Group::$processCache = [];
3✔
178
            Group::$processCacheDeleted = [];
3✔
179
        }
180
    }
181

182
    /**
183
     * @param LoggedPDO $dbhm
184
     */
185
    public function setDbhm($dbhm)
186
    {
187
        $this->dbhm = $dbhm;
1✔
188
    }
189

190
    public function getDefaults() {
191
        return($this->defaultSettings);
153✔
192
    }
193

194
    public function setPrivate($att, $val) {
195
        $ret = TRUE;
240✔
196

197
        if ($att == 'postvisibility') {
240✔
198
            # Check validity of spatial data
199
            $ret = FALSE;
1✔
200

201
            try {
202
                $valid = $this->dbhm->preQuery($this->dbhr->isV8() ? "SELECT ST_IsValid(ST_GeomFromText(?, {$this->dbhr->SRID()})) AS valid;" : "SELECT ST_IsValid(ST_GeomFromText(?)) AS valid;", [
1✔
203
                    $val
1✔
204
                ]);
1✔
205

206
                foreach ($valid as $v) {
1✔
207
                    if ($v['valid']) {
1✔
208
                        $this->dbhm->preExec("UPDATE `groups` SET postvisibility = ST_GeomFromText(?, {$this->dbhr->SRID()}) WHERE id = ?;", [
1✔
209
                            $val,
1✔
210
                            $this->id
1✔
211
                        ]);
1✔
212

213
                        $ret = TRUE;
1✔
214
                    }
215
                }
216
            } catch(\Exception $e) {
1✔
217
                # Drop through with ret false.
218
            }
219
        } else  if ($att == 'poly' || $att == 'polyofficial') {
239✔
220
            # Check validity of spatial data
221
            $ret = FALSE;
26✔
222
            try {
223
                $valid = !$val ? [ [ 'valid' => TRUE ] ] :  $this->dbhm->preQuery($this->dbhr->isV8() ? "SELECT ST_IsValid(ST_Simplify(ST_GeomFromText(?, {$this->dbhr->SRID()}), ?)) AS valid;" : "SELECT ST_IsValid(ST_Simplify(ST_GeomFromText(?), ?)) AS valid;", [
26✔
224
                    $val,
26✔
225
                    LoggedPDO::SIMPLIFY
26✔
226
                ]);
26✔
227

228
                foreach ($valid as $v) {
26✔
229
                    if ($v['valid']) {
26✔
230
                        # We can get very large geometries - so simplify it when we set it.  Party to avoid any MySQL woes, and partly because
231
                        # manual editing of very granular polygons is a right old faff.
232
                        $this->dbhm->preExec("UPDATE `groups` SET $att = CASE WHEN ? IS NULL THEN NULL ELSE ST_AsText(ST_Simplify(ST_GeomFromText(?, {$this->dbhr->SRID()}), ?)) END WHERE id = ?;", [
26✔
233
                            $val,
26✔
234
                            $val,
26✔
235
                            LoggedPDO::SIMPLIFY,
26✔
236
                            $this->id
26✔
237
                        ]);
26✔
238

239
                        # Get value back.
240
                        $vals = $this->dbhm->preQuery("SELECT $att FROM `groups` WHERE id = ?;", [
26✔
241
                            $this->id
26✔
242
                        ]);
26✔
243

244
                        foreach ($vals as $newval) {
26✔
245
                            $this->group[$att] = $newval[$att];
26✔
246
                        }
247

248
                        $this->dbhm->preExec("UPDATE `groups` SET polyindex = ST_GeomFromText(COALESCE(poly, polyofficial, 'POINT(0 0)'), {$this->dbhr->SRID()}) WHERE id = ?;", [
26✔
249
                            $this->id
26✔
250
                        ]);
26✔
251

252
                        $ret = TRUE;
26✔
253
                    }
254
                }
255
            } catch(\Exception $e) {
26✔
256
                # Drop through with ret false.
257
                #error_log("Failed to set $att " . $e->getMessage());
258
            }
259
        } else {
260
            parent::setPrivate($att, $val);
233✔
261
        }
262

263
        Group::clearCache($this->id);
240✔
264

265
        return $ret;
240✔
266
    }
267

268
    public function create($shortname, $type) {
269
        try {
270
            # Check for duplicate.  Might still occur in a timing window but in that rare case we'll get an exception
271
            # and catch that, failing the call.
272
            $groups = $this->dbhm->preQuery("SELECT id FROM `groups` WHERE nameshort = ?;", [ $shortname ]);
467✔
273
            foreach ($groups as $group) {
467✔
274
                return(NULL);
3✔
275
            }
276

277
            $rc = $this->dbhm->preExec("INSERT INTO `groups` (nameshort, type, founded, licenserequired, polyindex) VALUES (?, ?, NOW(),?,ST_GeomFromText('POINT(0 0)', {$this->dbhr->SRID()}))", [
467✔
278
                $shortname,
467✔
279
                $type,
467✔
280
                $type != Group::GROUP_FREEGLE ? 0 : 1
467✔
281
            ]);
467✔
282

283
            $id = $this->dbhm->lastInsertId();
467✔
284

285
            if ($type == Group::GROUP_FREEGLE) {
467✔
286
                # Also create a shortlink.
287
                $linkname = str_ireplace('Freegle', '', $shortname);
316✔
288
                $linkname = str_replace('-', '', $linkname);
316✔
289
                $linkname = str_replace('_', '', $linkname);
316✔
290
                $s = new Shortlink($this->dbhr, $this->dbhm);
316✔
291
                $sid = $s->create($linkname, Shortlink::TYPE_GROUP, $id);
316✔
292

293
                # And a group chat.
294
                $r = new ChatRoom($this->dbhr, $this->dbhm);
316✔
295
                if ($r->createGroupChat("$shortname Volunteers", $id, TRUE, TRUE)) {
316✔
296
                    $r->setPrivate('description', "$shortname Volunteers");
467✔
297
                }
298
            }
299
        } catch (\Exception $e) {
1✔
300
            error_log("Create group exception " . $e->getMessage());
1✔
301
            $id = NULL;
1✔
302
            $rc = 0;
1✔
303
        }
304

305
        if ($rc && $id) {
467✔
306
            $this->fetch($this->dbhm, $this->dbhm, $id, 'groups', 'group', $this->publicatts);
467✔
307
            $this->log->log([
467✔
308
                'type' => Log::TYPE_GROUP,
467✔
309
                'subtype' => Log::SUBTYPE_CREATED,
467✔
310
                'groupid' => $id,
467✔
311
                'text' => $shortname
467✔
312
            ]);
467✔
313

314
            return($id);
467✔
315
        } else {
316
            return(NULL);
1✔
317
        }
318
    }
319

320
    public function getMods($types = [ User::ROLE_MODERATOR, User::ROLE_OWNER ]) {
321
        $sql = "SELECT users.id FROM users INNER JOIN memberships ON users.id = memberships.userid AND memberships.groupid = ? AND role IN ('" . implode("','", $types) . "');";
22✔
322
        $mods = $this->dbhr->preQuery($sql, [ $this->id ]);
22✔
323
        $ret = [];
22✔
324
        foreach ($mods as $mod) {
22✔
325
            $ret[] = $mod['id'];
16✔
326
        }
327
        return($ret);
22✔
328
    }
329

330
    public function getModsEmail() {
331
        # This is an address used when we are sending to volunteers, or in response to an action by a volunteer.
332
        if (Utils::pres('contactmail', $this->group)) {
214✔
333
            $ret = $this->group['contactmail'];
1✔
334
        } else {
335
            $ret = $this->group['nameshort'] . "-volunteers@" . GROUP_DOMAIN;
213✔
336
        }
337

338
        return($ret);
214✔
339
    }
340

341
    public function getAutoEmail() {
342
        # This is an address used when we are sending automatic emails for a group.
343
        if ($this->group['contactmail']) {
214✔
344
            $ret = $this->group['contactmail'];
1✔
345
        } else {
346
            $ret = $this->group['nameshort'] . "-auto@" . GROUP_DOMAIN;
213✔
347
        }
348

349
        return($ret);
214✔
350
    }
351

352
    public function getGroupEmail() {
353
        $ret = $this->group['nameshort'] . '@' . GROUP_DOMAIN;
217✔
354
        return($ret);
217✔
355
    }
356

357
    public function delete() {
358
        $rc = $this->dbhm->preExec("DELETE FROM `groups` WHERE id = ?;", [$this->id]);
8✔
359
        if ($rc) {
8✔
360
            $this->log->log([
8✔
361
                'type' => Log::TYPE_GROUP,
8✔
362
                'subtype' => Log::SUBTYPE_DELETED,
8✔
363
                'groupid' => $this->id
8✔
364
            ]);
8✔
365
        }
366

367
        return($rc);
8✔
368
    }
369

370
    public function findByShortName($name) {
371
        $groups = $this->dbhr->preQuery("SELECT id FROM `groups` WHERE nameshort LIKE ?;",
184✔
372
            [
184✔
373
                trim($name)
184✔
374
            ]);
184✔
375

376
        foreach ($groups as $group) {
184✔
377
            return($group['id']);
180✔
378
        }
379

380
        return(NULL);
6✔
381
    }
382

383
    public function getWorkCounts($mysettings, $groupids) {
384
        $ret = [];
28✔
385
        $me = Session::whoAmI($this->dbhr, $this->dbhm);
28✔
386

387
        if ($groupids) {
28✔
388
            $groupq = "(" . implode(',', $groupids) . ")";
21✔
389

390
            $earliestmsg = date("Y-m-d", strtotime(MessageCollection::RECENTPOSTS));
21✔
391
            $eventsqltime = date("Y-m-d H:i:s", time());
21✔
392

393
            # Exclude messages routed to system, for which there must be a good reason.
394
            #
395
            # See also MessageCollection.
396
            $pendingspamcounts = $this->dbhr->preQuery("SELECT messages_groups.groupid, COUNT(*) AS count, messages_groups.collection, messages.heldby IS NOT NULL AS held FROM messages 
21✔
397
    INNER JOIN messages_groups ON messages.id = messages_groups.msgid AND messages_groups.groupid IN $groupq AND messages_groups.collection IN (?) AND messages_groups.deleted = 0 AND messages.deleted IS NULL AND messages.fromuser IS NOT NULL AND messages_groups.arrival >= '$earliestmsg' AND (messages.lastroute IS NULL OR messages.lastroute != ?) 
21✔
398
    GROUP BY messages_groups.groupid, messages_groups.collection, held;", [
21✔
399
                MessageCollection::PENDING,
21✔
400
                MailRouter::TO_SYSTEM
21✔
401
            ]);
21✔
402

403
            # No need to check spam_users as those will be auto-removed by the check_spammers job (in earlier times
404
            # this wasn't the case for all groups).
405
            $sql = "SELECT memberships.groupid, COUNT(*) AS count, memberships.heldby IS NOT NULL AS held FROM memberships
21✔
406
                    WHERE (reviewrequestedat IS NOT NULL AND (reviewedat IS NULL OR DATE(reviewedat) < DATE_SUB(NOW(), INTERVAL 31 DAY))) AND groupid IN $groupq
21✔
407
                    GROUP BY memberships.groupid, held;";
21✔
408
            $spammembercounts = $this->dbhr->preQuery($sql, []);
21✔
409

410
            $pendingeventcounts = $this->dbhr->preQuery("SELECT groupid, COUNT(DISTINCT communityevents.id) AS count FROM communityevents INNER JOIN communityevents_dates ON communityevents_dates.eventid = communityevents.id INNER JOIN communityevents_groups ON communityevents.id = communityevents_groups.eventid WHERE communityevents_groups.groupid IN $groupq AND communityevents.pending = 1 AND communityevents.deleted = 0 AND end >= ? GROUP BY groupid;", [
21✔
411
                $eventsqltime
21✔
412
            ]);
21✔
413

414
            $pendingvolunteercounts = $this->dbhr->preQuery("SELECT groupid, COUNT(DISTINCT volunteering.id) AS count FROM volunteering LEFT JOIN volunteering_dates ON volunteering_dates.volunteeringid = volunteering.id INNER JOIN volunteering_groups ON volunteering.id = volunteering_groups.volunteeringid WHERE volunteering_groups.groupid IN $groupq AND volunteering.pending = 1 AND volunteering.deleted = 0 AND volunteering.expired = 0 AND (applyby IS NULL OR applyby >= ?) AND (end IS NULL OR end >= ?) GROUP BY groupid;", [
21✔
415
                $eventsqltime,
21✔
416
                $eventsqltime
21✔
417
            ]);
21✔
418

419
            $pendingadmins = $this->dbhr->preQuery("SELECT groupid, COUNT(DISTINCT admins.id) AS count FROM admins WHERE admins.groupid IN $groupq AND admins.complete IS NULL AND admins.pending = 1 AND heldby IS NULL AND admins.created >= '$earliestmsg' GROUP BY groupid;");
21✔
420

421
            # Related members.
422
            #
423
            # Complex query for speed.
424
            $relatedsql = "SELECT COUNT(*) AS count, groupid FROM (
21✔
425
SELECT user1, memberships.groupid, (SELECT COUNT(*) FROM users_logins WHERE userid = memberships.userid) AS logincount FROM users_related
426
INNER JOIN memberships ON users_related.user1 = memberships.userid
427
INNER JOIN users u1 ON users_related.user1 = u1.id AND u1.deleted IS NULL AND u1.systemrole = 'User' AND u1.deleted IS NULL 
428
INNER JOIN users u2 ON users_related.user2 = u2.id AND u2.deleted IS NULL AND u2.systemrole = 'User' AND u2.deleted IS NULL 
429
WHERE
430
user1 < user2 AND
431
notified = 0 AND
432
memberships.groupid IN $groupq 
21✔
433
HAVING logincount > 0
434
UNION
435
SELECT user1, memberships.groupid, (SELECT COUNT(*) FROM users_logins WHERE userid = memberships.userid) AS logincount FROM users_related
436
INNER JOIN memberships ON users_related.user2 = memberships.userid
437
INNER JOIN users u3 ON users_related.user2 = u3.id AND u3.deleted IS NULL AND u3.systemrole = 'User' AND u3.deleted IS NULL
438
INNER JOIN users u4 ON users_related.user1 = u4.id AND u4.deleted IS NULL AND u4.systemrole = 'User' AND u4.deleted IS NULL
439
WHERE
440
user1 < user2 AND
441
notified = 0 AND
442
memberships.groupid IN $groupq 
21✔
443
HAVING logincount > 0 
444
) t GROUP BY groupid;";
21✔
445
            $relatedmembers = $this->dbhr->preQuery($relatedsql, NULL, FALSE, FALSE);
21✔
446

447
            # We only want to show edit reviews upto 7 days old - after that assume they're ok.
448
            #
449
            # See also MessageCollection.
450
            $mysqltime = date("Y-m-d", strtotime("Midnight 7 days ago"));
21✔
451
            $editreviewcounts = $this->dbhr->preQuery("SELECT groupid, COUNT(DISTINCT messages_edits.msgid) AS count FROM messages_edits INNER JOIN messages_groups ON messages_edits.msgid = messages_groups.msgid WHERE timestamp > '$mysqltime' AND reviewrequired = 1 AND messages_groups.groupid IN $groupq AND messages_groups.deleted = 0 GROUP BY groupid;");
21✔
452

453
            # We only want to show happiness upto 31 days old - after that just let it slide.  We're only interested
454
            # in ones with interesting comments.
455
            #
456
            # This code matches the feedback code on the client.
457
            $mysqltime = date("Y-m-d", strtotime(MessageCollection::RECENTPOSTS));
21✔
458
            $happsql = "SELECT messages_groups.groupid, COUNT(DISTINCT messages_groups.msgid) AS count FROM messages_outcomes 
21✔
459
              INNER JOIN messages_groups ON messages_groups.msgid = messages_outcomes.msgid 
460
              INNER JOIN messages ON messages.id = messages_outcomes.msgid
461
              WHERE messages_outcomes.timestamp > '$mysqltime' 
21✔
462
              AND messages_groups.arrival > '$mysqltime'
21✔
463
              AND groupid IN $groupq
21✔
464
                " . $this->getHappinessFilter() . "
21✔
465
              AND reviewed = 0 
466
              GROUP BY groupid";
21✔
467
            $happinesscounts = $this->dbhr->preQuery($happsql);
21✔
468
            #error_log($happsql);
469

470
            $c = new ChatMessage($this->dbhr, $this->dbhm);
21✔
471
            $reviewcounts = $c->getReviewCountByGroup($me, NULL, FALSE);
21✔
472
            $reviewcountsother = $c->getReviewCountByGroup($me, NULL, TRUE);
21✔
473

474
            foreach ($groupids as $groupid) {
21✔
475
                # Depending on our group settings we might not want to show this work as primary; "other" work is displayed
476
                # less prominently in the client.
477
                #
478
                # If we have the active flag use that; otherwise assume that the legacy showmessages flag tells us.  Default
479
                # to active.
480
                # TODO Retire showmessages entirely and remove from user configs.
481
                $active = array_key_exists('active', $mysettings[$groupid]) ? $mysettings[$groupid]['active'] : (!array_key_exists('showmessages', $mysettings[$groupid]) || $mysettings[$groupid]['showmessages']);
21✔
482

483
                $thisone = [
21✔
484
                    'pending' => 0,
21✔
485
                    'pendingother' => 0,
21✔
486
                    'spam' => 0,
21✔
487
                    'pendingmembers' => 0,
21✔
488
                    'pendingmembersother' => 0,
21✔
489
                    'pendingevents' => 0,
21✔
490
                    'pendingvolunteering' => 0,
21✔
491
                    'spammembers' => 0,
21✔
492
                    'spammembersother' => 0,
21✔
493
                    'editreview' => 0,
21✔
494
                    'pendingadmins' => 0,
21✔
495
                    'happiness' => 0,
21✔
496
                    'relatedmembers' => 0,
21✔
497
                    'chatreview' => 0,
21✔
498
                    'chatreviewother' => 0
21✔
499
                ];
21✔
500

501
                if ($active) {
21✔
502
                    foreach ($pendingspamcounts as $count) {
21✔
503
                        if ($count['groupid'] == $groupid) {
3✔
504
                            if ($count['collection'] == MessageCollection::PENDING) {
3✔
505
                                if ($count['held']) {
3✔
506
                                    $thisone['pendingother'] = $count['count'];
×
507
                                } else {
508
                                    $thisone['pending'] = $count['count'];
3✔
509
                                }
510
                            } else {
511
                                $thisone['spam'] = $count['count'];
×
512
                            }
513
                        }
514
                    }
515

516
                    foreach ($spammembercounts as $count) {
21✔
517
                        if ($count['groupid'] == $groupid) {
1✔
518
                            if ($count['held']) {
1✔
519
                                $thisone['spammembersother'] = $count['count'];
×
520
                            } else {
521
                                $thisone['spammembers'] = $count['count'];
1✔
522
                            }
523
                        }
524
                    }
525

526
                    foreach ($pendingeventcounts as $count) {
21✔
527
                        if ($count['groupid'] == $groupid) {
×
528
                            $thisone['pendingevents'] = $count['count'];
×
529
                        }
530
                    }
531

532
                    foreach ($pendingvolunteercounts as $count) {
21✔
533
                        if ($count['groupid'] == $groupid) {
1✔
534
                            $thisone['pendingvolunteering'] = $count['count'];
1✔
535
                        }
536
                    }
537

538
                    foreach ($editreviewcounts as $count) {
21✔
539
                        if ($count['groupid'] == $groupid) {
4✔
540
                            $thisone['editreview'] = $count['count'];
4✔
541
                        }
542
                    }
543

544
                    foreach ($pendingadmins as $count) {
21✔
545
                        if ($count['groupid'] == $groupid) {
1✔
546
                            $thisone['pendingadmins'] = $count['count'];
1✔
547
                        }
548
                    }
549

550
                    foreach ($happinesscounts as $count) {
21✔
551
                        if ($count['groupid'] == $groupid) {
×
552
                            $thisone['happiness'] = $count['count'];
×
553
                        }
554
                    }
555

556
                    foreach ($relatedmembers as $count) {
21✔
557
                        if ($count['groupid'] == $groupid) {
1✔
558
                            $thisone['relatedmembers'] = $count['count'];
1✔
559
                        }
560
                    }
561

562
                    foreach ($reviewcounts as $count) {
21✔
563
                        if ($count['groupid'] == $groupid) {
3✔
564
                            $thisone['chatreview'] = $count['count'];
3✔
565
                        }
566
                    }
567

568
                    foreach ($reviewcountsother as $count) {
21✔
569
                        if ($count['groupid'] == $groupid) {
×
570
                            $thisone['chatreviewother'] = $count['count'];
×
571
                        }
572
                    }
573
                } else {
574
                    foreach ($pendingspamcounts as $count) {
1✔
575
                        if ($count['groupid'] == $groupid) {
1✔
576
                            $thisone['pendingother'] = $count['count'];
1✔
577
                        }
578
                    }
579

580
                    foreach ($spammembercounts as $count) {
1✔
581
                        if ($count['groupid'] == $groupid) {
1✔
582
                            $thisone['spammembersother'] = $count['count'];
1✔
583
                        }
584
                    }
585

586
                    foreach ($reviewcounts as $count) {
1✔
587
                        if ($count['groupid'] == $groupid) {
×
588
                            $thisone['chatreviewother'] += $count['count'];
×
589
                        }
590
                    }
591

592
                    foreach ($reviewcountsother as $count) {
1✔
593
                        if ($count['groupid'] == $groupid) {
×
594
                            $thisone['chatreviewother'] += $count['count'];
×
595
                        }
596
                    }
597
                }
598

599
                $ret[$groupid] = $thisone;
21✔
600
            }
601
        }
602

603
        return($ret);
28✔
604
    }
605

606
    private function getHappinessFilter() {
607
        return " AND messages_outcomes.comments IS NOT NULL
24✔
608
              AND messages_outcomes.comments != 'Sorry, this is no longer available.'
609
              AND messages_outcomes.comments != 'Thanks, this has now been taken.'
610
              AND messages_outcomes.comments != 'Thanks, I\'m no longer looking for this.' 
611
              AND messages_outcomes.comments != 'Sorry, this has now been taken.'
612
              AND messages_outcomes.comments != 'Thanks for the interest, but this has now been taken.'
613
              AND messages_outcomes.comments != 'Thanks, these have now been taken.'
614
              AND messages_outcomes.comments != 'Thanks, this has now been received.'
615
              AND messages_outcomes.comments != 'Withdrawn on user unsubscribe'
616
              AND messages_outcomes.comments != 'Auto-Expired'";
24✔
617
    }
618

619
    public function getPublic($summary = FALSE) {
620
        $atts = parent::getPublic();
210✔
621

622
        # Contact mails
623
        $atts['modsemail'] = $this->getModsEmail();
210✔
624
        $atts['autoemail'] = $this->getAutoEmail();
210✔
625
        $atts['groupemail'] = $this->getGroupEmail();
210✔
626

627
        # Add in derived properties.
628
        $atts['namedisplay'] = $atts['namefull'] ? $atts['namefull'] : $atts['nameshort'];
210✔
629
        $settings = json_decode($atts['settings'], true);
210✔
630

631
        if ($settings) {
210✔
632
            $atts['settings'] = array_replace_recursive($this->defaultSettings, $settings);
209✔
633
        } else {
634
            $atts['settings'] = $this->defaultSettings;
2✔
635
        }
636

637
        $atts['founded'] = Utils::ISODate($this->group['founded']);
210✔
638

639
        foreach (['affiliationconfirmed'] as $datefield) {
210✔
640
            $atts[$datefield] = Utils::pres($datefield, $atts) ? Utils::ISODate($atts[$datefield]) : NULL;
210✔
641
        }
642

643
        # Images.  We pass those ids in to get the paths.  This removes the DB operations for constructing the
644
        # Attachment, which is valuable for people on many groups.
645
        if (defined('IMAGE_DOMAIN')) {
210✔
646
            $a = new Attachment($this->dbhr, $this->dbhm, NULL, Attachment::TYPE_GROUP);
210✔
647
            $b = new Attachment($this->dbhr, $this->dbhm, NULL, Attachment::TYPE_GROUP);
210✔
648

649
            $atts['profile'] = $atts['profile'] ? $a->getPath(FALSE, $atts['profile']) : NULL;
210✔
650
            $atts['cover'] = $atts['cover'] ? $b->getPath(FALSE, $atts['cover']) : NULL;
210✔
651
        }
652

653
        $atts['url'] = $atts['onhere'] ? ('https://' . USER_SITE . '/explore/' . $atts['nameshort']) : ("https://groups.yahoo.com/neo/groups/" . $atts['nameshort'] . "/info");
210✔
654

655
        if ($summary) {
210✔
656
            foreach (['settings', 'description', 'welcomemail'] as $att) {
10✔
657
                unset($atts[$att]);
10✔
658
            }
659
        } else {
660
            if (Utils::pres('defaultlocation', $atts)) {
203✔
661
                $l = new Location($this->dbhr, $this->dbhm, $atts['defaultlocation']);
1✔
662
                $atts['defaultlocation'] = $l->getPublic();
1✔
663
            }
664
        }
665

666
        $atts['microvolunteeringoptions'] = Utils::pres('microvolunteeringoptions', $atts) ? json_decode($atts['microvolunteeringoptions'], TRUE) : [
210✔
667
            'approvedmessages' => 1,
210✔
668
            'wordmatch' => 1,
210✔
669
            'photorotate' => 1
210✔
670
        ];
210✔
671

672
        return($atts);
210✔
673
    }
674

675
    public function getMembers($limit = 10, $search = NULL, &$ctx = NULL, $searchid = NULL, $collection = MembershipCollection::APPROVED, $groupids = NULL, $yps = NULL, $ydt = NULL, $ops = NULL, $filter = Group::FILTER_NONE) {
676
        $ret = [];
12✔
677
        $limit = intval($limit);
12✔
678

679
        $groupids = $groupids ? $groupids : ($this->id ? [ $this-> id ] : NULL);
12✔
680

681
        if ($search) {
12✔
682
            # Remove wildcards - people put them in, but that's not how it works.
683
            $search = str_replace('*', '', $search);
3✔
684

685
            if (preg_match('/(.*)\-(.*)(@user.trashnothing.com)/', $search, $matches)) {
3✔
686
                # Sometimes people search on one TN email when that email isn't a member of the group.
687
                $search = $matches[1];
×
688
            }
689
        }
690

691
        # If we're searching for a notify address, switch to the user it.
692
        $search = preg_match('/notify-(.*)-(.*)' . USER_DOMAIN . '/', $search, $matches) ? $matches[2] : $search;
12✔
693

694
        $date = is_null($ctx) ? NULL : $this->dbhr->quote(date("Y-m-d H:i:s", $ctx['Added']));
12✔
695
        $addq = (is_null($ctx) || !Utils::pres('id', $ctx)) ? '' : (" AND (memberships.added < $date OR (memberships.added = $date AND memberships.id < " . $this->dbhr->quote($ctx['id']) . ")) ");
12✔
696
        $groupq = $groupids ? " memberships.groupid IN (" . implode(',', $groupids) . ") " : " 1=1 ";
12✔
697
        $opsq = $ops ? (" AND memberships.ourPostingStatus = " . $this->dbhr->quote($ydt)) : '';
12✔
698
        $modq = '';
12✔
699
        $bounceq = '';
12✔
700
        $filterq = '';
12✔
701
        $uq = '';
12✔
702

703
        switch ($filter) {
704
            case Group::FILTER_WITHCOMMENTS:
705
                $filterq = ' INNER JOIN users_comments ON users_comments.userid = memberships.userid ';
1✔
706
                $filterq = $groupids ? ("$filterq AND users_comments.groupid IN (" . implode(',', $groupids) . ") ") : $filterq;
1✔
707
                break;
1✔
708
            case Group::FILTER_MODERATORS:
709
                $filterq = '';
2✔
710
                $modq = " AND memberships.role IN ('Owner', 'Moderator') ";
2✔
711
                break;
2✔
712
            case Group::FILTER_BOUNCING:
713
                $bounceq = ' AND users.bouncing = 1 ';
1✔
714
                $uq = $uq ? $uq : ' INNER JOIN users ON users.id = memberships.userid ';
1✔
715
                break;
1✔
716
            default:
717
                $filterq = '';
9✔
718
                break;
9✔
719
        }
720

721
        # Collection filter.  If we're searching on a specific id then don't put it in.
722
        $collectionq = '';
12✔
723

724
        if (!$searchid) {
12✔
725
            if ($collection == MembershipCollection::SPAM) {
11✔
726
                # This collection is handled separately; we use the reviewrequestedat field.
727
                #
728
                # This is to avoid moving members into a spam collection and then having to remember whether they
729
                # came from Pending or Approved.
730
                #
731
                # If we have reviewed someone recently, don't show them again as it gets annoying.
732
                $collectionq = " AND reviewrequestedat IS NOT NULL AND (reviewedat IS NULL OR DATE(reviewedat) < DATE_SUB(NOW(), INTERVAL 31 DAY))";
1✔
733
            } else if ($collection) {
10✔
734
                $collectionq = ' AND memberships.collection = ' . $this->dbhr->quote($collection) . ' ';
10✔
735
            }
736
        }
737

738
        $sqlpref = "SELECT DISTINCT memberships.* FROM memberships 
12✔
739
              INNER JOIN `groups` ON groups.id = memberships.groupid
740
              $uq
12✔
741
              $filterq";
12✔
742

743
        $members = [];
12✔
744

745
        if ($collection == MembershipCollection::SPAM) {
12✔
746
            # Order by userid as we might have the same member subject to review on multiple groups.
747
            #
748
            # FORCE INDEX seems to be necessary.
749
            $sqlpref = "SELECT DISTINCT memberships.* FROM memberships FORCE INDEX (reviewrequestedat) 
1✔
750
              INNER JOIN `groups` ON groups.id = memberships.groupid
751
              $uq
1✔
752
              $filterq";
1✔
753
            $searchq = $searchid ? (" AND memberships.userid = " . $this->dbhr->quote($searchid) . " ") : '';
1✔
754
            $addq = is_null($ctx) ? '' : (" AND memberships.userid < " . $this->dbhr->quote($ctx['userid']) . " ");
1✔
755
            $sql = "$sqlpref WHERE $groupq $collectionq $addq $searchq $opsq $modq $bounceq";
1✔
756
            $sql .= " ORDER BY memberships.userid DESC LIMIT $limit;";
1✔
757
            $members = $this->dbhr->preQuery($sql);
1✔
758

759
            # No need to check for spam_users - they will get auto-removed by check_spammers.php.
760
        } else if ($search) {
11✔
761
            # We're searching.  It turns out to be more efficient to get the userids using the indexes, and then
762
            # get the rest of the stuff we need.
763
            $q = $this->dbhr->quote("$search%");
3✔
764
            $bq = $this->dbhr->quote(strrev($search) . "%");
3✔
765
            $p = strpos($search, ' ');
3✔
766
            $namesearch = $p === FALSE ? '' : ("UNION (SELECT id FROM users WHERE firstname LIKE " . $this->dbhr->quote(
3✔
767
                        substr($search, 0, $p) . '%'
3✔
768
                    ) . " AND lastname LIKE " . $this->dbhr->quote(substr($search, $p + 1) . '%')) . ') ';
3✔
769

770
            $sql = "(SELECT userid AS id FROM users_emails WHERE email LIKE $q) UNION
3✔
771
                (SELECT userid AS id FROM users_emails WHERE backwards LIKE $bq) UNION
3✔
772
                (SELECT id FROM users WHERE id = " . $this->dbhr->quote($search) . ") UNION
3✔
773
                (SELECT id FROM users WHERE fullname LIKE $q) UNION
3✔
774
                (SELECT id FROM users WHERE yahooid LIKE $q)
3✔
775
                $namesearch";
3✔
776
            $userids = $this->dbhr->preQuery($sql);
3✔
777
            $ids = array_column($userids, 'id');
3✔
778

779
            if (count($ids))
3✔
780
            {
781
                $sql = "$sqlpref 
3✔
782
                  INNER JOIN users ON users.id = memberships.userid 
783
                  LEFT JOIN users_emails ON memberships.userid = users_emails.userid 
784
                  WHERE users.id IN (" . implode(',', $ids) . ") AND 
3✔
785
                  $groupq $collectionq $addq $opsq";
3✔
786

787
                $sql .= " ORDER BY memberships.added DESC, memberships.id DESC LIMIT $limit;";
3✔
788
                $members = $this->dbhr->preQuery($sql);
3✔
789

790
                # Also search in banned users.
791
                if ($groupids && count($groupids)) {
3✔
792
                    $sql = "SELECT userid AS id, userid, groupid, 'Member' AS role, 'Banned' AS collection, NULL AS configid, date AS added, NULL AS settings, 0 AS syncdelete, NULL AS heldby, 0 AS emailfrequency, 0 AS eventsallowed, 0 AS volunteeringallowed, 'PROHIBITED' as ourPostingStatus, NULL as reviewrequestededat, NULL AS reviewreason, NULL AS timestamp FROM users_banned WHERE userid IN (" . implode(',', $ids) . ") AND groupid IN (" . implode(',', $groupids) . ");";
3✔
793
                    $members2 = $this->dbhr->preQuery($sql);
3✔
794
                    $members = array_unique(array_merge($members, $members2));
3✔
795
                }
796
            }
797
        } else {
798
            $searchq = $searchid ? (" AND memberships.userid = " . $this->dbhr->quote($searchid) . " ") : '';
9✔
799
            $sql = "$sqlpref WHERE $groupq $collectionq $addq $searchq $opsq $modq $bounceq";
9✔
800
            $sql .= " ORDER BY memberships.added DESC, memberships.id DESC LIMIT $limit;";
9✔
801
            $members = $this->dbhr->preQuery($sql);
9✔
802
        }
803

804
        # Get the infos in a single go.
805
        $uids = array_filter(array_column($members, 'userid'));
12✔
806
        $infousers = [];
12✔
807

808
        if (count($uids)) {
12✔
809
            foreach ($uids as $uid) {
12✔
810
                $infousers[$uid] = [
12✔
811
                    'id' => $uid
12✔
812
                ];
12✔
813
            }
814

815
            $u = new User($this->dbhr, $this->dbhm);
12✔
816
            $u->getInfos($infousers);
12✔
817
            $u->getPublicLocations($infousers);
12✔
818

819
            if ($collection == MembershipCollection::SPAM) {
12✔
820
                $latlngs = $u->getLatLngs($infousers, FALSE, FALSE);
1✔
821

822
                foreach ($latlngs as $userid => $latlng) {
1✔
823
                    $infousers[$userid]['info']['privateposition'] = $latlng;
1✔
824
                }
825
            }
826
        }
827

828
        $banusers = [];
12✔
829

830
        if ($groupids && count($groupids) == 1) {
12✔
831
            # Get the bans.
832
            if (count($uids)) {
12✔
833
                $bans = $this->dbhr->preQuery("SELECT * FROM users_banned WHERE userid IN (" . implode(',', $uids) . ') AND groupid = ?;', [
12✔
834
                    $groupids[0]
12✔
835
                ]);
12✔
836

837
                foreach ($bans as $ban) {
12✔
838
                    $banusers[$ban['userid']] = [
1✔
839
                        'bandate' => Utils::ISODate($ban['date']),
1✔
840
                        'bannedby' => $ban['byuser']
1✔
841
                    ];
1✔
842
                }
843
            }
844
        }
845

846
        # Suspect members might be on multiple groups, so make sure we only return one.
847
        $uids = [];
12✔
848

849
        $ctx = [ 'Added' => NULL ];
12✔
850

851
        foreach ($members as $member) {
12✔
852
            $thisepoch = strtotime($member['added']);
12✔
853

854
            if (is_null($ctx['Added']) || $thisepoch < $ctx['Added']) {
12✔
855
                $ctx['Added'] = $thisepoch;
12✔
856
            }
857

858
            if ($collection == MembershipCollection::SPAM) {
12✔
859
                $ctx['userid'] = array_key_exists('userid', $ctx) ? min($member['userid'], $ctx['userid']) : $member['userid'];
1✔
860
            } else {
861
                $ctx['id'] = array_key_exists('id', $ctx) ? min($member['id'], $ctx['id']) : $member['id'];
11✔
862
            }
863

864
            if (!Utils::pres($member['userid'], $uids)) {
12✔
865
                $uids[$member['userid']] = TRUE;
12✔
866

867
                $u = User::get($this->dbhr, $this->dbhm, $member['userid']);
12✔
868
                $thisone = $u->getPublic($groupids, TRUE);
12✔
869
                #error_log("{$member['userid']} has " . count($thisone['comments']));
870

871
                # We want to return an id of the membership, because the same user might be pending on two groups, and
872
                # a userid of the user's id.
873
                $thisone['userid'] = $thisone['id'];
12✔
874
                $thisone['id'] = $member['id'];
12✔
875
                $thisone['trustlevel'] = $u->getPrivate('trustlevel');
12✔
876
                $thisone['engagement'] = $u->getPrivate('engagement');
12✔
877
                $thisone['collection'] = $member['collection'];
12✔
878

879
                # We want to return both the email used on this group and any others we have.
880
                $emails = $u->getEmails();
12✔
881
                $email = NULL;
12✔
882
                $others = [];
12✔
883

884
                # Groups we host only use a single email.
885
                $email = $u->getEmailPreferred();
12✔
886
                foreach ($emails as $anemail) {
12✔
887
                    if ($anemail['email'] != $email) {
11✔
888
                        $others[] = $anemail;
1✔
889
                    }
890
                }
891

892
                $thisone['joined'] = Utils::ISODate($member['added']);
12✔
893

894
                # Defaults match ones in User.php
895
                #error_log("Settings " . var_export($member, TRUE));
896
                $thisone['settings'] = $member['settings'] ? json_decode($member['settings'], TRUE) : [
12✔
897
                    'active' => 1,
12✔
898
                    'showchat' => 1,
12✔
899
                    'pushnotify' => 1,
12✔
900
                    'eventsallowed' => 1
12✔
901
                ];
12✔
902

903
                # Sort so that we have a deterministic order for UT.
904
                usort($others, function($a, $b) {
12✔
905
                    return(strcmp($a['email'], $b['email']));
×
906
                });
12✔
907

908
                $thisone['settings']['configid'] = $member['configid'];
12✔
909
                $thisone['email'] = $email;
12✔
910
                $thisone['groupid'] = $member['groupid'];
12✔
911
                $thisone['otheremails'] = $others;
12✔
912
                $thisone['role'] = $u->getRoleForGroup($member['groupid'], FALSE);
12✔
913
                $thisone['emailfrequency'] = $member['emailfrequency'];
12✔
914
                $thisone['eventsallowed'] = $member['eventsallowed'];
12✔
915
                $thisone['volunteeringallowed'] = $member['volunteeringallowed'];
12✔
916
                $thisone['autorepostsdisable'] = $u->getSetting('autorepostsdisable', FALSE);
12✔
917

918
                # Our posting status only applies for groups we host.  In that case, the default is moderated.
919
                $thisone['ourpostingstatus'] = Utils::presdef('ourPostingStatus', $member, Group::POSTING_MODERATED);
12✔
920

921
                $thisone['heldby'] = $member['heldby'];
12✔
922

923
                if (Utils::pres('heldby', $thisone)) {
12✔
924
                    $u = User::get($this->dbhr, $this->dbhm, $thisone['heldby']);
×
925
                    $thisone['heldby'] = $u->getPublic(NULL, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE);
×
926
                }
927

928
                if ($filter ==  Group::FILTER_MODERATORS) {
12✔
929
                    # Also add in the time this mod was last active.  This is not the same as when they last moderated
930
                    # but indicates if they have been on the platform, which is what you want to find mods who have
931
                    # drifted off.  Getting the correct value is too timeconsuming.
932
                    $thisone['lastmoderated'] = Utils::ISODate($u->getPrivate('lastaccess'));
2✔
933
                }
934

935
                # Pick up the info we fetched above.
936
                $thisone['info'] = $infousers[$thisone['userid']]['info'];
12✔
937

938
                if (Utils::pres($thisone['userid'], $banusers)) {
12✔
939
                    $thisone['bandate'] = $banusers[$thisone['userid']]['bandate'];
1✔
940
                    $thisone['bannedby'] = $banusers[$thisone['userid']]['bannedby'];
1✔
941
                }
942

943
                $ret[] = $thisone;
12✔
944
            }
945
        }
946

947
        return($ret);
12✔
948
    }
949

950
    public function getHappinessMembers($groupids, &$ctx, $filter = NULL, $limit = 10) {
951
        $ret = [];
3✔
952
        $filterq = '';
3✔
953

954
        if ($filter) {
3✔
955
            foreach ([ User::HAPPY, User::UNHAPPY, User::FINE] as $val) {
1✔
956
                if ($filter == $val) {
1✔
957
                    if ($val ==  'Fine') {
1✔
958
                        $filterq = " AND (messages_outcomes.outcome IS NULL OR messages_outcomes.happiness = '$val') ";
×
959
                    } else {
960
                        $filterq = " AND messages_outcomes.happiness = '$val' ";
1✔
961
                    }
962
                }
963
            }
964
        }
965

966
        $groupids = $groupids ? $groupids : ($this->id ? [ $this-> id ] : NULL);
3✔
967
        $groupq2 = $groupids ? " messages_groups.groupid IN (" . implode(',', $groupids) . ") " : " 1=1 ";
3✔
968

969
        # Only interested in showing recent ones, which makes the query faster.
970
        $start = date('Y-m-d', strtotime(MessageCollection::RECENTPOSTS));
3✔
971

972
        # We want unreviewed first, then most recent.
973
        $ctxq = is_null($ctx) ? " WHERE messages_outcomes.timestamp > '$start' " :
3✔
974
            (" WHERE
1✔
975
            messages_outcomes.reviewed >= " . intval($ctx['reviewed']) . " AND   
1✔
976
            messages_outcomes.timestamp > '$start' AND 
1✔
977
            (messages_outcomes.timestamp < '" . Utils::safedate($ctx['timestamp']) . "' OR 
1✔
978
                (messages_outcomes.timestamp = '" . Utils::safedate($ctx['timestamp']) . "' AND
1✔
979
                 messages_outcomes.id < " . intval($ctx['id']) . "))");
1✔
980

981
        $sql = "SELECT messages_outcomes.*, messages.fromuser, messages_groups.groupid, messages.subject FROM messages_outcomes
3✔
982
            INNER JOIN messages_groups ON messages_groups.msgid = messages_outcomes.msgid AND $groupq2
3✔
983
            INNER JOIN messages ON messages.id = messages_outcomes.msgid
984
            $ctxq
3✔
985
            $filterq " . $this->getHappinessFilter() . "
3✔
986
            AND messages_groups.arrival > '$start'
3✔
987
            ORDER BY messages_outcomes.reviewed ASC, messages_outcomes.timestamp DESC, messages_outcomes.id DESC LIMIT $limit";
3✔
988
        $members = $this->dbhr->preQuery($sql, []);
3✔
989

990
        # Get the users in a single go for speed.
991
        $uids = array_column($members, 'fromuser');
3✔
992
        $u = new User($this->dbhr, $this->dbhm);
3✔
993
        $users = $u->getPublicsById($uids, NULL, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, NULL, FALSE);
3✔
994

995
        # Get the preferred emails.
996
        $u->getPublicEmails($users);
3✔
997

998
        foreach ($users as $userid => $user) {
3✔
999
            if (Utils::pres('emails', $user)) {
2✔
1000
                foreach ($user['emails'] as $email) {
2✔
1001
                    if ($email['preferred'] || (!Mail::ourDomain($email['email']) && !Utils::pres('email', $users[$userid]))) {
2✔
1002
                        $users[$userid]['email'] = $email['email'];
2✔
1003
                    }
1004
                }
1005
            }
1006
        }
1007

1008
        $last = NULL;
3✔
1009

1010
        foreach ($members as $member) {
3✔
1011
            # Ignore dups.
1012
            if ($last && $member['msgid'] == $last) {
2✔
1013
                continue;
×
1014
            }
1015

1016
            $last = $member['msgid'];
2✔
1017

1018
            $ctx = [
2✔
1019
                'id' => $member['id'],
2✔
1020
                'timestamp' => $member['timestamp'],
2✔
1021
                'reviewed' => $member['reviewed']
2✔
1022
            ];
2✔
1023

1024
            $member['user']  = $users[$member['fromuser']];
2✔
1025

1026
            $member['message'] = [
2✔
1027
                'id' => $member['msgid'],
2✔
1028
                'subject' => $member['subject'],
2✔
1029
            ];
2✔
1030

1031
            unset($member['msgid']);
2✔
1032
            unset($member['subject']);
2✔
1033

1034
            $member['timestamp'] = Utils::ISODate($member['timestamp']);
2✔
1035
            $ret[] = $member;
2✔
1036
        }
1037

1038
        return($ret);
3✔
1039
    }
1040

1041
    public function getBanned($groupid, &$ctx) {
1042
        $ctx = $ctx ? $ctx : [];
1✔
1043

1044
        if (Utils::pres('date', $ctx)) {
1✔
1045
            $members = $this->dbhr->preQuery("SELECT date AS bandate, byuser AS bannedby, groupid, userid FROM users_banned WHERE groupid = ? AND date < ? ORDER BY date DESC;", [
1✔
1046
                $groupid,
1✔
1047
                $ctx['date']
1✔
1048
            ]);
1✔
1049
        } else {
1050
            $members = $this->dbhr->preQuery("SELECT date AS bandate, byuser AS bannedby, groupid, userid FROM users_banned WHERE groupid = ? ORDER BY date DESC;", [
1✔
1051
                $groupid
1✔
1052
            ]);
1✔
1053
        }
1054

1055
        $ret = [];
1✔
1056

1057
        $u = new User($this->dbhr, $this->dbhm);
1✔
1058
        $users = $u->getPublicsById(array_column($members, 'userid'));
1✔
1059

1060
        foreach ($members as $member) {
1✔
1061
            $thisone = array_merge($users[$member['userid']], $member);
1✔
1062
            $thisone['bandate'] = Utils::ISODate($thisone['bandate']);
1✔
1063
            $ret[] = $thisone;
1✔
1064
            $ctx['date'] = $member['bandate'];
1✔
1065
        }
1066

1067
        return $ret;
1✔
1068
    }
1069

1070
    public function ourPS($status) {
1071
        # For historical reasons, the ourPostingStatus field has various values, equivalent to those on Yahoo.  But
1072
        # we only support three settings - MODERATED, DEFAULT aka Group Settings, and PROHIBITED aka Can't Post.
1073
        switch ($status) {
1074
            case NULL: $status = NULL; break;
31✔
1075
            case Group::POSTING_MODERATED: $status = Group::POSTING_MODERATED; break;
3✔
1076
            case Group::POSTING_PROHIBITED: $status = Group::POSTING_PROHIBITED; break;
3✔
1077
            default: $status = Group::POSTING_DEFAULT; break;
2✔
1078
        }
1079

1080
        return($status);
32✔
1081
    }
1082

1083
    public function setSettings($settings)
1084
    {
1085
        $str = json_encode($settings);
16✔
1086
        $me = Session::whoAmI($this->dbhr, $this->dbhm);
16✔
1087
        $this->dbhm->preExec("UPDATE `groups` SET settings = ? WHERE id = ?;", [ $str, $this->id ]);
16✔
1088
        Group::clearCache($this->id);
16✔
1089
        $this->group['settings'] = $str;
16✔
1090
        $this->log->log([
16✔
1091
            'type' => Log::TYPE_GROUP,
16✔
1092
            'subtype' => Log::SUBTYPE_EDIT,
16✔
1093
            'groupid' => $this->id,
16✔
1094
            'byuser' => $me ? $me->getId() : NULL,
16✔
1095
            'text' => $this->getEditLog([
16✔
1096
                'settings' => $settings
16✔
1097
            ])
16✔
1098
        ]);
16✔
1099

1100
        return(true);
16✔
1101
    }
1102

1103
    public function getSetting($key, $def, $settings = NULL) {
1104
        if (is_null($settings) && Utils::pres('settings', $this->group)) {
203✔
1105
            $settings = $this->group['settings'];
201✔
1106
        } else {
1107
            $settings = $this->defaultSettings;
3✔
1108
        }
1109

1110
        $settings = $this->id ? json_decode($settings, true) : NULL;
203✔
1111
        return($settings && array_key_exists($key, $settings) ? $settings[$key] : $def);
203✔
1112
    }
1113

1114
    public function getSponsorships() {
1115
        $sql = "SELECT * FROM groups_sponsorship WHERE groupid = ? AND startdate <= NOW() AND enddate >= DATE(NOW()) AND visible = 1 ORDER BY amount DESC, tagline IS NOT NULL, description IS NOT NULL;";
12✔
1116
        return $this->dbhr->preQuery($sql, [
12✔
1117
            $this->id
12✔
1118
        ]);
12✔
1119
    }
1120

1121
    public function getConfirmKey() {
1122
        $key = NULL;
1✔
1123

1124
        # Don't reset the key each time, otherwise we can have timing windows where the key is reset, thereby
1125
        # invalidating an invitation which is in progress.
1126
        $groups = $this->dbhr->preQuery("SELECT confirmkey FROM `groups` WHERE id = ?;" , [ $this->id ]);
1✔
1127
        foreach ($groups as $group) {
1✔
1128
            $key = $group['confirmkey'];
1✔
1129
        }
1130

1131
        if (!$key) {
1✔
1132
            $key = Utils::randstr(32);
1✔
1133
            $sql = "UPDATE `groups` SET confirmkey = ? WHERE id = ?;";
1✔
1134
            $rc = $this->dbhm->preExec($sql, [ $key, $this->id ]);
1✔
1135
            Group::clearCache($this->id);
1✔
1136
        }
1137

1138
        return($key);
1✔
1139
    }
1140

1141
    public function getName() {
1142
        return($this->group['namefull'] ? $this->group['namefull'] : $this->group['nameshort']);
115✔
1143
    }
1144

1145
    public function listByType($type, $support, $polys = FALSE) {
1146
        $typeq = $type ? "type = ?" : '1=1';
3✔
1147
        $showq = $support ? '' : 'AND publish = 1 AND listable = 1';
3✔
1148
        $suppfields = $support ? ", founded, lastmoderated, lastmodactive, lastautoapprove, activemodcount, backupmodsactive, backupownersactive, onmap, affiliationconfirmed, affiliationconfirmedby": '';
3✔
1149
        $polyfields = $polys ? ", CASE WHEN poly IS NULL THEN polyofficial ELSE poly END AS poly, polyofficial" : '';
3✔
1150

1151
        $sql = "SELECT groups.id, groups.settings, groups_images.id AS attid, nameshort, region, namefull, lat, lng, altlat, altlng, publish $suppfields $polyfields, mentored, onhere, ontn, onmap, external, profile, tagline, contactmail, onlovejunk FROM `groups` LEFT JOIN groups_images ON groups_images.groupid = groups.id WHERE $typeq ORDER BY CASE WHEN namefull IS NOT NULL THEN namefull ELSE nameshort END, groups_images.id DESC;";
3✔
1152
        $groups = $this->dbhr->preQuery($sql, [ $type ]);
3✔
1153
        $a = new Attachment($this->dbhr, $this->dbhm, NULL, Attachment::TYPE_GROUP);
3✔
1154

1155
        $g = new Group($this->dbhr, $this->dbhm);
3✔
1156

1157
        if ($support) {
3✔
1158
            $start = date('Y-m-d', strtotime(MessageCollection::RECENTPOSTS));
2✔
1159
            $autoapproves = $this->dbhr->preQuery("SELECT COUNT(*) AS count, groupid FROM logs WHERE timestamp >= ? AND type = ? AND subtype = ? GROUP BY groupid;", [
2✔
1160
                $start,
2✔
1161
                Log::TYPE_MESSAGE,
2✔
1162
                Log::SUBTYPE_AUTO_APPROVED
2✔
1163
            ]);
2✔
1164

1165
            $manualapproves = $this->dbhr->preQuery("SELECT COUNT(*) AS count, groupid FROM logs WHERE timestamp >= ? AND type = ? AND subtype = ? GROUP BY groupid;", [
2✔
1166
                $start,
2✔
1167
                Log::TYPE_MESSAGE,
2✔
1168
                Log::SUBTYPE_APPROVED
2✔
1169
            ]);
2✔
1170
        }
1171

1172
        $lastname = NULL;
3✔
1173
        $ret = [];
3✔
1174

1175
        foreach ($groups as &$group) {
3✔
1176
            if (!$lastname || $lastname != $group['nameshort']) {
3✔
1177
                $group['namedisplay'] = $group['namefull'] ? $group['namefull'] : $group['nameshort'];
3✔
1178
                $group['profile'] = $group['profile'] ? $a->getPath(FALSE, $group['attid']) : NULL;
3✔
1179

1180
                if ($group['contactmail']) {
3✔
1181
                    $group['modsmail'] = $group['contactmail'];
2✔
1182
                } else {
1183
                    $group['modsmail'] = $group['nameshort'] . "-volunteers@" . GROUP_DOMAIN;
2✔
1184
                }
1185

1186
                if ($support) {
3✔
1187
                    foreach ($autoapproves as $approve) {
2✔
1188
                        if ($approve['groupid'] ==  $group['id']) {
2✔
1189
                            $group['recentautoapproves'] = $approve['count'];
1✔
1190
                        }
1191
                    }
1192

1193
                    foreach ($manualapproves as $approve) {
2✔
1194
                        if ($approve['groupid'] ==  $group['id']) {
2✔
1195
                            # Exclude the autoapproves, which have an approved log as well as an autoapproved log.
1196
                            $group['recentmanualapproves'] = $approve['count'] - Utils::presdef('recentautoapproves', $group, 0);
1✔
1197
                        }
1198
                    }
1199

1200
                    if (Utils::pres('recentautoapproves', $group)) {
2✔
1201
                        $total = $group['recentmanualapproves'] + $group['recentautoapproves'];
1✔
1202
                        $group['recentautoapprovespercent'] = $total ? (round(100 * $group['recentautoapproves']) / $total) : 0;
1✔
1203
                    } else {
1204
                        $group['recentautoapprovespercent'] = 0;
1✔
1205
                    }
1206
                }
1207

1208
                $group['nearbygroups'] = $g->getSetting('nearbygroups', $g->defaultSettings['nearbygroups'], $group['settings']);
3✔
1209
                $group['showjoin'] = $g->getSetting('showjoin', $g->defaultSettings['showjoin'], $group['settings']);
3✔
1210
                unset($group['settings']);
3✔
1211

1212
                $ret[] = $group;
3✔
1213
            }
1214

1215
            $lastname = $group['nameshort'];
3✔
1216
        }
1217

1218
        return($ret);
3✔
1219
    }
1220

1221
    public function welcomeReview($gid = NULL, $limit = 10) {
1222
        # Send copy of the welcome mail to mods for review.
1223
        $idq = $gid ? " AND id = $gid " : "";
1✔
1224
        $count = 0;
1✔
1225

1226
        $groups = $this->dbhr->preQuery("SELECT id FROM `groups` WHERE (welcomereview IS NULL OR DATEDIFF(NOW(), welcomereview) >= 365) AND welcomemail IS NOT NULL $idq LIMIT $limit;");
1✔
1227
        foreach ($groups as $group) {
1✔
1228
            $g = Group::get($this->dbhr, $this->dbhm, $group['id']);
1✔
1229
            $mods = $g->getMods();
1✔
1230
            error_log($g->getName());
1✔
1231
            foreach ($mods as $mod) {
1✔
1232
                $u = new User($this->dbhr, $this->dbhm, $mod);
1✔
1233
                if ($u->sendOurMails() && $u->getEmailPreferred()) {
1✔
1234
                    error_log("..." . $u->getEmailPreferred());
1✔
1235
                    $g->sendWelcome($mod, TRUE);
1✔
1236
                    $count++;
1✔
1237

1238
                    $this->dbhm->preExec("UPDATE `groups` SET welcomereview = NOW() WHERE id = ?;", [
1✔
1239
                        $group['id']
1✔
1240
                    ]);
1✔
1241
                }
1242
            }
1243
        }
1244

1245
        return $count;
1✔
1246
    }
1247

1248
    static public function getOpenCount($dbhr, $id) {
1249
        $mysqltime = date("Y-m-d", strtotime(MessageCollection::RECENTPOSTS));
1✔
1250
        $counts = $dbhr->preQuery("SELECT COUNT(*) AS count FROM `messages_groups` LEFT JOIN messages_outcomes ON messages_outcomes.msgid = messages_groups.msgid WHERE arrival >= ? AND groupid = ? AND messages_outcomes.id IS NULL AND messages_groups.deleted = 0;", [
1✔
1251
            $mysqltime,
1✔
1252
            $id
1✔
1253
        ]);
1✔
1254

1255
        return $counts[0]['count'];
1✔
1256
    }
1257

1258
    public function findPopularMessages() {
1259
        # Delete any old ones.
1260
        $this->dbhm->preExec("UPDATE messages_popular SET expired = 1 WHERE TIMESTAMPDIFF(HOUR, timestamp, NOW()) >= 24 AND shared = 0 AND declined = 0;");
4✔
1261

1262
        # Find messages with the most views which lie within the official group area (CGA), and which have not
1263
        # previously been popular.
1264
        $msgs = $this->dbhr->preQuery("SELECT COUNT(*) AS count, messages_likes.msgid, messages_groups.groupid FROM messages_likes 
4✔
1265
    INNER JOIN messages_groups ON messages_groups.msgid = messages_likes.msgid
1266
    INNER JOIN `groups` ON groups.id = messages_groups.groupid
1267
    INNER JOIN messages ON messages.id = messages_groups.msgid
1268
    INNER JOIN messages_attachments ma on messages_groups.msgid = ma.msgid 
1269
    LEFT JOIN messages_popular ON messages_popular.msgid = messages_groups.msgid
1270
    WHERE TIMESTAMPDIFF(HOUR, messages_likes.timestamp, NOW()) <= 24 AND 
1271
          messages_groups.deleted = 0 AND
1272
          DATEDIFF(NOW(), messages_groups.arrival) <= 31 AND
1273
          messages_groups.collection = ? AND ST_Contains(ST_GeomFromText(groups.polyofficial, {$this->dbhr->SRID()}), ST_SRID(POINT(messages.lng,messages.lat),  {$this->dbhr->SRID()})) AND
4✔
1274
          messages_popular.id IS NULL
1275
    GROUP BY messages_likes.msgid 
1276
    ORDER BY messages_groups.groupid ASC, count DESC;", [
4✔
1277
        MessageCollection::APPROVED
4✔
1278
        ]);
4✔
1279

1280
        # Processing in this order means we'll end up with the most popular one per group.
1281
        $forgroup = [];
4✔
1282

1283
        foreach ($msgs as $msg) {
4✔
1284
            $forgroup[$msg['groupid']] = $msg['msgid'];
4✔
1285
        }
1286

1287
        foreach ($forgroup as $groupid => $msgid) {
4✔
1288
            # Don't share the same message multiple times.
1289
            $exists = $this->dbhr->preQuery("SELECT id FROM messages_popular WHERE msgid = ?;", [
4✔
1290
                $msgid
4✔
1291
            ]);
4✔
1292

1293
            if (!count($exists)) {
4✔
1294
                $this->dbhm->preExec("INSERT INTO messages_popular (groupid, msgid) VALUES (?, ?);", [
4✔
1295
                    $groupid,
4✔
1296
                    $msgid
4✔
1297
                ]);
4✔
1298
            }
1299
        }
1300
    }
1301

1302
    public function getPopularMessages($groupid = NULL) {
1303
        $ret = [];
32✔
1304
        $me = Session::whoAmI($this->dbhr, $this->dbhm);
32✔
1305
        $gids = NULL;
32✔
1306

1307
        if ($groupid) {
32✔
1308
            $gids = [ $groupid ];
4✔
1309
        } else if ($me) {
30✔
1310
            $gids1 = $me->getModeratorships();
29✔
1311

1312
            foreach ($gids1 as $gid) {
29✔
1313
                if ($me->activeModForGroup($gid)) {
20✔
1314
                    $gids[] = $gid;
20✔
1315
                }
1316
            }
1317
        }
1318

1319
        if ($gids && count($gids)) {
32✔
1320
            $msgs = $this->dbhr->preQuery("SELECT messages_popular.* FROM messages_popular
22✔
1321
            INNER JOIN groups_facebook ON groups_facebook.groupid = messages_popular.groupid
1322
            INNER JOIN messages ON messages.id = messages_popular.msgid 
1323
            WHERE TIMESTAMPDIFF(HOUR, NOW(), messages.arrival) <= 48 AND messages_popular.groupid IN (" . implode(',', $gids) . ") AND shared = 0 AND declined = 0 AND expired = 0 AND deleted IS NULL
22✔
1324
            GROUP BY messages_popular.id;");
22✔
1325

1326
            $ret = [];
22✔
1327

1328
            foreach ($msgs as $msg) {
22✔
1329
                $msg['timestamp'] = Utils::ISODate($msg['timestamp']);
4✔
1330
                $ret[] = $msg;
4✔
1331
            }
1332
        }
1333

1334
        return $ret;
32✔
1335
    }
1336

1337
    public function sharedPopularMessage($msgid) {
1338
        $this->dbhm->preExec("UPDATE messages_popular SET shared = 1, declined = 0 WHERE msgid = ?;", [
2✔
1339
            $msgid
2✔
1340
        ]);
2✔
1341

1342
        // Return rows affected so that we can tell whether this was the first attempt - that gets used to avoid
1343
        // duplicates due to the window during the share.
1344
        return $this->dbhm->rowsAffected();
2✔
1345
    }
1346

1347
    public function hidPopularMessage($msgid) {
1348
        $this->dbhm->preExec("UPDATE messages_popular SET declined = 1, shared = 0 WHERE msgid = ?;", [
2✔
1349
            $msgid
2✔
1350
        ]);
2✔
1351

1352
        // Return rows affected so that we can tell whether this was the first attempt - that gets used to avoid
1353
        // duplicates due to the window during the share.
1354
        return $this->dbhm->rowsAffected();
2✔
1355
    }
1356

1357
    public function getLastApproval($uid) {
1358
        $approvals = $this->dbhr->preQuery("SELECT COUNT(*) AS count, DATEDIFF(NOW(), MAX(arrival)) AS ago FROM messages_groups WHERE approvedby = ? AND groupid = ?", [
×
1359
            $uid,
×
1360
            $this->id
×
1361
        ]);
×
1362

1363
        $ret = $approvals[0]['count'] > 0 ? $approvals[0]['ago'] : PHP_INT_MAX;
×
1364
        #error_log("Last approval for {$this->id} by $uid was $ret days ago");
1365
        return $ret;
×
1366
    }
1367

1368
    public function getModsToNotify() {
1369
        $ret = [];
×
1370

1371
        # This is to find mods to notify about things on the group which require attention, ideally from the
1372
        # owner.  Although we have owner/mod status, that is no guarantee that the owners are actually active.
1373
        # So we want to find people who are known to be active that we can notify.
1374
        #
1375
        # 1. First choice is people with owner status on the group, who are set to be active mods, who have been
1376
        #    actually active within the last month.
1377
        # 2. Next choice if there are none of them is the same, but who are set to backup mod status.
1378
        # 3. Next choice if there are none of them is people with mod status, who are set to be active mods, who
1379
        #    have been actually active.
1380
        # 4. Next choice is the same but who are set to backup mod status.
1381
        # 5. Next choice is Mentors.
1382
        $owners = $this->getMods([ User::ROLE_OWNER ]);
×
1383

1384
        foreach ($owners as $owner) {
×
1385
            if ($this->getLastApproval($owner) <= 31) {
×
1386
                $u = User::get($this->dbhr, $this->dbhm, $owner);
×
1387

1388
                if ($u->activeModForGroup($this->id)) {
×
1389
                    error_log("#$owner " . $u->getEmailPreferred() . " is active and active owner for group");
×
1390
                    $ret[] = $owner;
×
1391
                }
1392
            }
1393
        }
1394

1395
        if (!count($ret)) {
×
1396
            foreach ($owners as $owner) {
×
1397
                if ($this->getLastApproval($owner) <= 31) {
×
1398
                    $u = User::get($this->dbhr, $this->dbhm, $owner);
×
1399

1400
                    if (!$u->activeModForGroup($this->id)) {
×
1401
                        error_log("#$owner " . $u->getEmailPreferred() . " is active and backup owner for group");
×
1402
                        $ret[] = $owner;
×
1403
                    }
1404
                }
1405
            }
1406
        }
1407

1408
        if (!count($ret)) {
×
1409
            $mods = $this->getMods([ User::ROLE_MODERATOR ]);
×
1410

1411
            foreach ($mods as $mod) {
×
1412
                if ($this->getLastApproval($mod) <= 31) {
×
1413
                    $u = User::get($this->dbhr, $this->dbhm, $mod);
×
1414

1415
                    if ($u->activeModForGroup($this->id)) {
×
1416
                        error_log("$mod " . $u->getEmailPreferred() . " is active and active mod for group");
×
1417
                        $ret[] = $mod;
×
1418
                    }
1419
                }
1420
            }
1421
        }
1422

1423
        if (!count($ret)) {
×
1424
            foreach ($mods as $mod) {
×
1425
                if ($this->getLastApproval($mod) <= 31) {
×
1426
                    $u = User::get($this->dbhr, $this->dbhm, $mod);
×
1427

1428
                    if (!$u->activeModForGroup($this->id)) {
×
1429
                        error_log("$mod " . $u->getEmailPreferred() . " is active and backup mod for group");
×
1430
                        $ret[] = $mod;
×
1431
                    }
1432
                }
1433
            }
1434
        }
1435

1436

1437
        if (!count($ret)) {
×
1438
            error_log("...fallback to Mentors");
×
1439
            $ret[]= MENTORS_ADDR;
×
1440
        }
1441

1442
        return $ret;
×
1443
    }
1444

1445
    public function sendWelcome($userid, $review) {
1446
        $u = User::get($this->dbhr, $this->dbhm, $userid);
3✔
1447

1448
        if ($u->sendOurMails() && $u->getEmailPreferred()) {
3✔
1449
            $welcome = $this->getPrivate('welcomemail');
3✔
1450
            $to = $u->getEmailPreferred();
3✔
1451

1452
            $loader = new \Twig_Loader_Filesystem(IZNIK_BASE . '/mailtemplates/twig/welcome');
3✔
1453
            $twig = new \Twig_Environment($loader);
3✔
1454

1455
            $name = $this->getName();
3✔
1456

1457
            $html = $twig->render('group.html', [
3✔
1458
                'email' => $to,
3✔
1459
                'message' => nl2br($welcome),
3✔
1460
                'review' => $review,
3✔
1461
                'groupname' => $name
3✔
1462
            ]);
3✔
1463

1464
            if ($to) {
3✔
1465
                list ($transport, $mailer) = Mail::getMailer();
3✔
1466
                $message = \Swift_Message::newInstance()
3✔
1467
                    ->setSubject(($review ? "Please review: " : "") . "Welcome to $name")
3✔
1468
                    ->setFrom([$this->getAutoEmail() => "$name Volunteers"])
3✔
1469
                    ->setReplyTo([$this->getModsEmail() => "$name Volunteers"])
3✔
1470
                    ->setTo($to)
3✔
1471
                    ->setDate(time())
3✔
1472
                    ->setBody($welcome);
3✔
1473

1474
                # Add HTML in base-64 as default quoted-printable encoding leads to problems on
1475
                # Outlook.
1476
                $htmlPart = \Swift_MimePart::newInstance();
3✔
1477
                $htmlPart->setCharset('utf-8');
3✔
1478
                $htmlPart->setEncoder(new \Swift_Mime_ContentEncoder_Base64ContentEncoder);
3✔
1479
                $htmlPart->setContentType('text/html');
3✔
1480
                $htmlPart->setBody($html);
3✔
1481
                $message->attach($htmlPart);
3✔
1482

1483
                Mail::addHeaders($this->dbhr, $this->dbhm, $message, Mail::WELCOME, $userid);
3✔
1484

1485
                $this->sendIt($mailer, $message);
3✔
1486
            }
1487
        }
1488
    }
1489

1490
    public function sendIt($mailer, $message)
1491
    {
1492
        $mailer->send($message);
1✔
1493
    }
1494
}
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

© 2025 Coveralls, Inc