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

Freegle / Iznik / 11910

09 May 2026 09:20PM UTC coverage: 69.035% (-3.8%) from 72.843%
11910

Pull #431

circleci

edwh
refactor(group-customisation): use config for FROM name, fix dry-run output verb

Replace hardcoded 'Freegle' FROM name with config('freegle.branding.name').
Fix output verb to say "would send N" instead of "sent N" during --dry-run.

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

9128 of 10555 branches covered (86.48%)

Branch coverage included in aggregate %.

71 of 78 new or added lines in 2 files covered. (91.03%)

11990 existing lines in 141 files now uncovered.

101221 of 149291 relevant lines covered (67.8%)

19.67 hits per line

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

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

3
namespace App\Services;
4

5
use App\Models\Group;
6
use App\Models\Membership;
7
use App\Models\UserEmail;
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
8✔
29
    {
30
        $noreplyAddr = config('freegle.mail.noreply_addr', 'noreply@ilovefreegle.org');
8✔
31
        $mentorsAddr = config('freegle.mail.mentors_addr', 'mentors@ilovefreegle.org');
8✔
32
        $modSite     = config('freegle.sites.mod', 'https://modtools.org');
8✔
33

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

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

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

48
            if ($missing === '') {
8✔
NEW
49
                $skipped++;
×
NEW
50
                continue;
×
51
            }
52

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

55
            if (empty($modEmails)) {
8✔
56
                Log::info("GroupCustomisation: no mod emails for group {$group->id} {$group->nameshort}");
8✔
57
                $skipped++;
8✔
58
                continue;
8✔
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"
8✔
62
                . "Here are some things you could add:\n\n"
8✔
63
                . $missing . "\n"
8✔
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"
8✔
65
                . "If you need help with this, then please contact {$mentorsAddr}\n\n"
8✔
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.";
8✔
67

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

74
            if (!$dryRun) {
8✔
75
                Mail::raw($body, function ($message) use ($group, $modEmails, $noreplyAddr) {
7✔
NEW
76
                    $message->to($modEmails)
×
NEW
77
                        ->from($noreplyAddr, config('freegle.branding.name', 'Freegle'))
×
NEW
78
                        ->returnPath($noreplyAddr)
×
NEW
79
                        ->subject("Reminder - ways to make {$group->nameshort} more welcoming");
×
80
                });
7✔
81
            }
82

83
            $sent++;
8✔
84
        }
85

86
        return [
8✔
87
            'groups_checked' => count($groups),
8✔
88
            'sent'           => $sent,
8✔
89
            'skipped'        => $skipped,
8✔
90
        ];
8✔
91
    }
92

93
    private function buildMissingList(object $group): string
8✔
94
    {
95
        $missing = '';
8✔
96

97
        foreach (self::CUST_ATTRS as $attr => $description) {
8✔
98
            if (empty($group->$attr)) {
8✔
99
                $missing .= $description . "\n";
8✔
100
            }
101
        }
102

103
        return $missing;
8✔
104
    }
105

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

115
        $query = DB::table('memberships')
8✔
116
            ->join('users_emails', 'users_emails.userid', '=', 'memberships.userid')
8✔
117
            ->where('memberships.groupid', $groupId)
8✔
118
            ->whereIn('memberships.role', [Membership::ROLE_OWNER, Membership::ROLE_MODERATOR])
8✔
119
            ->where('memberships.collection', Membership::COLLECTION_APPROVED)
8✔
120
            ->where('users_emails.preferred', 1);
8✔
121

122
        if ($systemUserId) {
8✔
NEW
123
            $query->where('memberships.userid', '!=', $systemUserId);
×
124
        }
125

126
        return $query->pluck('users_emails.email')->all();
8✔
127
    }
128
}
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