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

Freegle / Iznik / 11965

10 May 2026 07:07AM UTC coverage: 69.033% (-3.8%) from 72.843%
11965

Pull #431

circleci

edwh
fix(group-customisation): replace Mail::raw() with CustomisationReminderMail

Mail::fake() is a no-op for Mail::raw(); Mail::assertSentCount() always
returned 0. Using a proper Mailable fixes test assertions. Also fixes
test_skips_group_with_all_attrs_set: groups.profile is a FK to
groups_images.id, not a filename string.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pull Request #431: feat(batch): migrate group_customisation.php to groups:remind-customisation

9127 of 10554 branches covered (86.48%)

Branch coverage included in aggregate %.

75 of 85 new or added lines in 3 files covered. (88.24%)

11990 existing lines in 141 files now uncovered.

101223 of 149298 relevant lines covered (67.8%)

19.69 hits per line

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

98.39
/iznik-batch/app/Services/GroupCustomisationService.php
1
<?php
2

3
namespace App\Services;
4

5
use App\Mail\Group\CustomisationReminderMail;
6
use App\Models\Group;
7
use App\Models\Membership;
8
use Illuminate\Support\Facades\DB;
9
use Illuminate\Support\Facades\Log;
10
use Illuminate\Support\Facades\Mail;
11

12
/**
13
 * Sends monthly reminders to group moderators about missing customisation attributes.
14
 *
15
 * Migrated from iznik-server/scripts/cron/group_customisation.php
16
 */
17
class GroupCustomisationService
18
{
19
    private const SYSTEM_MOD_EMAIL = 'modtools@modtools.org';
20

21
    private const CUST_ATTRS = [
22
        'profile'     => '* Profile picture - this helps people recognise your group.',
23
        'tagline'     => '* Tagline - something short and snappy, ideally with a local reference.',
24
        'welcomemail' => '* Welcome Mail - this is sent out to people when they join - your chance to welcome them, and let them know anything they need to (e.g. whether pets are allowed).',
25
        'description' => '* Description - this is longer than the tagline, and is visible on the site - often containing similar information to the welcome mail.',
26
    ];
27

28
    public function sendReminders(bool $dryRun = false): array
10✔
29
    {
30
        $noreplyAddr = config('freegle.mail.noreply_addr', 'noreply@ilovefreegle.org');
10✔
31
        $mentorsAddr = config('freegle.mail.mentors_addr', 'mentors@ilovefreegle.org');
10✔
32
        $modSite     = config('freegle.sites.mod', 'https://modtools.org');
10✔
33

34
        $groups = DB::table('groups')
10✔
35
            ->where('type', Group::TYPE_FREEGLE)
10✔
36
            ->where('publish', 1)
10✔
37
            ->where('onhere', 1)
10✔
38
            ->orderByRaw('RAND()')
10✔
39
            ->select(['id', 'nameshort', 'profile', 'tagline', 'welcomemail', 'description'])
10✔
40
            ->get();
10✔
41

42
        $sent    = 0;
10✔
43
        $skipped = 0;
10✔
44

45
        foreach ($groups as $group) {
10✔
46
            $missing = $this->buildMissingList($group);
10✔
47

48
            if ($missing === '') {
10✔
49
                $skipped++;
1✔
50
                continue;
1✔
51
            }
52

53
            $modEmails = $this->getModEmails($group->id);
10✔
54

55
            if (empty($modEmails)) {
10✔
56
                Log::info("GroupCustomisation: no mod emails for group {$group->id} {$group->nameshort}");
10✔
57
                $skipped++;
10✔
58
                continue;
10✔
59
            }
60

61
            $body = "Just to remind you, you can make your Freegle group look more local and friendly for your members by customising how it appears on Freegle Direct.\n\n"
10✔
62
                . "Here are some things you could add:\n\n"
10✔
63
                . $missing . "\n"
10✔
64
                . "You can change these from ModTools ({$modSite}), in Settings - Group Settings - Group Appearance. ModTools helps you run your group, and is free to use for all Freegle groups.\n\n"
10✔
65
                . "If you need help with this, then please contact {$mentorsAddr}\n\n"
10✔
66
                . "P.S. This is an automated message sent once a month. We send it regularly because sometimes the moderators on a group change, and they might not know about this stuff.";
10✔
67

68
            Log::info("GroupCustomisation: sending reminder for {$group->nameshort}", [
10✔
69
                'group_id' => $group->id,
10✔
70
                'mod_count' => count($modEmails),
10✔
71
                'dry_run'   => $dryRun,
10✔
72
            ]);
10✔
73

74
            if (!$dryRun) {
10✔
75
                Mail::to($modEmails)
9✔
76
                    ->send(new CustomisationReminderMail($group->nameshort, $missing));
9✔
77
            }
78

79
            $sent++;
10✔
80
        }
81

82
        return [
10✔
83
            'groups_checked' => count($groups),
10✔
84
            'sent'           => $sent,
10✔
85
            'skipped'        => $skipped,
10✔
86
        ];
10✔
87
    }
88

89
    private function buildMissingList(object $group): string
10✔
90
    {
91
        $missing = '';
10✔
92

93
        foreach (self::CUST_ATTRS as $attr => $description) {
10✔
94
            if (empty($group->$attr)) {
10✔
95
                $missing .= $description . "\n";
10✔
96
            }
97
        }
98

99
        return $missing;
10✔
100
    }
101

102
    /**
103
     * @return string[]  Preferred email addresses for all active mods/owners of this group.
104
     */
105
    private function getModEmails(int $groupId): array
10✔
106
    {
107
        $systemUserId = DB::table('users_emails')
10✔
108
            ->where('email', self::SYSTEM_MOD_EMAIL)
10✔
109
            ->value('userid');
10✔
110

111
        $query = DB::table('memberships')
10✔
112
            ->join('users_emails', 'users_emails.userid', '=', 'memberships.userid')
10✔
113
            ->where('memberships.groupid', $groupId)
10✔
114
            ->whereIn('memberships.role', [Membership::ROLE_OWNER, Membership::ROLE_MODERATOR])
10✔
115
            ->where('memberships.collection', Membership::COLLECTION_APPROVED)
10✔
116
            ->where('users_emails.preferred', 1);
10✔
117

118
        if ($systemUserId) {
10✔
NEW
119
            $query->where('memberships.userid', '!=', $systemUserId);
×
120
        }
121

122
        return $query->pluck('users_emails.email')->all();
10✔
123
    }
124
}
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