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

Freegle / iznik-server / #2540

17 Dec 2025 10:43AM UTC coverage: 89.531% (-0.7%) from 90.259%
#2540

push

php-coveralls

edwh
Fix flaky EngageTest by cleaning up engage table in setUp

The engage table tracks when users were sent engagement emails and
prevents re-sending within 7 days. This caused test isolation issues
when test users from previous runs still had entries.

Clean up engage table entries for test users at the start of each test.

26596 of 29706 relevant lines covered (89.53%)

31.64 hits per line

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

98.73
/include/misc/Log.php
1
<?php
2
namespace Freegle\Iznik;
3

4
require_once(dirname(__FILE__) . '/Loki.php');
1✔
5

6
# Logging.  This is not guaranteed against loss in the event of serious failure.
7
class Log
8
{
9
    /** @var  $dbhr LoggedPDO */
10
    var $dbhr;
11
    /** @var  $dbhm LoggedPDO */
12
    var $dbhm;
13

14
    # Log types must match the enumeration in the logs table.
15
    const TYPE_GROUP = 'Group';
16
    const TYPE_USER = 'User';
17
    const TYPE_MESSAGE = 'Message';
18
    const TYPE_CONFIG = 'Config';
19
    const TYPE_STDMSG = 'StdMsg';
20
    const TYPE_BULKOP = 'BulkOp';
21
    const TYPE_LOCATION = 'Location';
22
    const TYPE_CHAT = 'Chat';
23

24
    const SUBTYPE_CREATED = 'Created';
25
    const SUBTYPE_DELETED = 'Deleted';
26
    const SUBTYPE_EDIT = 'Edit';
27
    const SUBTYPE_APPROVED = 'Approved';
28
    const SUBTYPE_REJECTED = 'Rejected';
29
    const SUBTYPE_RECEIVED = 'Received';
30
    const SUBTYPE_NOTSPAM = 'NotSpam';
31
    const SUBTYPE_HOLD = 'Hold';
32
    const SUBTYPE_RELEASE = 'Release';
33
    const SUBTYPE_FAILURE = 'Failure';
34
    const SUBTYPE_JOINED = 'Joined';
35
    const SUBTYPE_APPLIED = 'Applied';
36
    const SUBTYPE_LEFT = 'Left';
37
    const SUBTYPE_REPLIED = 'Replied';
38
    const SUBTYPE_MAILED = 'Mailed';
39
    const SUBTYPE_LOGIN = 'Login';
40
    const SUBTYPE_LOGOUT = 'Logout';
41
    const SUBTYPE_CLASSIFIED_SPAM = 'ClassifiedSpam';
42
    const SUBTYPE_SUSPECT = 'Suspect';
43
    const SUBTYPE_SENT = 'Sent';
44
    const SUBTYPE_OUR_POSTING_STATUS = 'OurPostingStatus';
45
    const SUBTYPE_OUR_EMAIL_FREQUENCY = 'OurEmailFrequency';
46
    const SUBTYPE_ROLE_CHANGE = 'RoleChange';
47
    const SUBTYPE_MERGED = 'Merged';
48
    const SUBTYPE_SPLIT = 'Split';
49
    const SUBTYPE_MAILOFF = 'MailOff';
50
    const SUBTYPE_EVENTSOFF = 'EventsOff';
51
    const SUBTYPE_NEWSLETTERSOFF = 'NewslettersOff';
52
    const SUBTYPE_RELEVANTOFF = 'RelevantOff';
53
    const SUBTYPE_VOLUNTEERSOFF = 'VolunteersOff';
54
    const SUBTYPE_BOUNCE = 'Bounce';
55
    const SUBTYPE_SUSPEND_MAIL = 'SuspendMail';
56
    const SUBTYPE_AUTO_REPOSTED = 'Autoreposted';
57
    const SUBTYPE_OUTCOME = 'Outcome';
58
    const SUBTYPE_NOTIFICATIONOFF = 'NotificationOff';
59
    const SUBTYPE_AUTO_APPROVED = 'Autoapproved';
60
    const SUBTYPE_UNBOUNCE = 'Unbounce';
61
    const SUBTYPE_WORRYWORDS = 'WorryWords';
62
    const SUBTYPE_POSTCODECHANGE = 'PostcodeChange';
63
    const SUBTYPE_REPOST = 'Repost';
64
    
65
    const LOG_USER_CACHE_SIZE = 1000;
66

67
    function __construct($dbhr, $dbhm)
68
    {
69
        $this->dbhr = $dbhr;
679✔
70
        $this->dbhm = $dbhm;
679✔
71
    }
72

73
    public function log($params) {
74
        # Insert the current timestamp, so that the time in the log is when the log was made, rather than when
75
        # it percolates through the background processing.
76
        $params['timestamp'] = date("Y-m-d H:i:s", time());
643✔
77

78
        # We assume that the parameters passed match fields in the logs table.
79
        # If they don't, the caller is at fault and should be taken out and shot.
80
        $q = [];
643✔
81
        foreach ($params as $key => $val) {
643✔
82
            $q[] = !is_null($val) ? $this->dbhm->quote($val) : 'NULL';
643✔
83
        }
84

85
        $atts = implode('`,`', array_keys($params));
643✔
86
        $vals = implode(',', $q);
643✔
87

88
        $sql = "INSERT INTO logs (`$atts`) VALUES ($vals);";
643✔
89

90
        # No need to check return code - if it doesn't work, nobody dies.
91
        $this->dbhm->background($sql);
643✔
92

93
        # Also log to Loki (fire-and-forget, async).
94
        $loki = Loki::getInstance();
643✔
95
        if ($loki->isEnabled()) {
643✔
96
            $loki->logFromLogsTable($params);
×
97
        }
98
    }
99

100
    public function get($types, $subtypes, $groupid, $userid, $date, $search, $limit, &$ctx, $uid = NULL) {
101
        $limit = intval($limit);
8✔
102

103
        $groupq = $groupid ? " groupid = $groupid " : '1 = 1 ';
8✔
104
        $userq = $userid ? " groupid = $groupid " : '1 = 1 ';
8✔
105
        $typeq = $types ? (" AND logs.type IN ('" . implode("','", $types) . "') ") : '';
8✔
106
        $subtypeq = $subtypes ? (" AND `subtype` IN ('" . implode("','", $subtypes) . "') ") : '';
8✔
107
        $mysqltime = date("Y-m-d", strtotime("midnight $date days ago"));
8✔
108
        $dateq = $date ? " AND timestamp >= '$mysqltime' " : '';
8✔
109

110
        $searchq = $this->dbhr->quote("%$search%");
8✔
111

112
        $idq = Utils::pres('id', $ctx) ? (" AND logs.id < " . intval($ctx['id']) . " ") : '';
8✔
113
        
114
        # We might have consecutive logs for the same messages/users, so try to speed that up.
115
        if ($uid) {
8✔
116
            $sql = "SELECT logs.* FROM logs 
7✔
117
                LEFT JOIN users ON users.id = logs.user 
118
                LEFT JOIN messages ON messages.id = logs.msgid
119
                WHERE $groupq $idq $typeq $subtypeq $dateq AND 
7✔
120
                (logs.user = $uid OR logs.byuser = $uid)
7✔
121
                ORDER BY logs.id DESC LIMIT $limit";
7✔
122
        } else if (!$search) {
1✔
123
            # This is simple.
124
            $sql = "SELECT * FROM logs WHERE $groupq $idq $typeq $subtypeq $dateq ORDER BY id DESC LIMIT $limit";
1✔
125
        } else  {
126
            # This is complex.  We want to search in the various user names, and the message
127
            # subject.  And the email - and people might search using an email belonging to a member but which
128
            # isn't the fromaddr of any messages.  So first expand the email.
129
            $sql = "SELECT users_emails.userid FROM users_emails INNER JOIN memberships ON groupid = $groupid AND memberships.userid = users_emails.userid AND email LIKE $searchq;";
1✔
130
            $emails = $this->dbhr->preQuery($sql);
1✔
131
            $uids = [];
1✔
132

133
            foreach ($emails as $email) {
1✔
134
                $uids[] = $email['userid'];
1✔
135
            }
136

137
            $uidq = count($uids) > 0 ? (" OR logs.user IN (" . implode(',', $uids) . ") OR logs.byuser IN (" . implode(',', $uids) . ")") : '';
1✔
138

139
            $sql = "SELECT logs.* FROM logs 
1✔
140
                LEFT JOIN users ON users.id = logs.user 
141
                LEFT JOIN messages ON messages.id = logs.msgid
142
                WHERE $groupq $idq $typeq $subtypeq $dateq AND 
1✔
143
                ((users.firstname LIKE $searchq OR users.lastname LIKE $searchq OR users.fullname LIKE $searchq OR CONCAT(users.firstname, ' ', users.lastname) LIKE $searchq) OR
1✔
144
                 (messages.subject LIKE $searchq $uidq))
1✔
145
                ORDER BY logs.id DESC LIMIT $limit";
1✔
146
        }
147

148
        $logs = $this->dbhr->preQuery($sql);
8✔
149
        $total = count($logs);
8✔
150
        #error_log("...total logs $total");
151
        $count = 0;
8✔
152

153
        $uids = array_filter(array_unique(array_merge(array_column($logs, 'user'), array_column($logs, 'byuser'))));
8✔
154
        $u = new User($this->dbhr, $this->dbhm);
8✔
155
        $users = [];
8✔
156
        if (count($uids)) {
8✔
157
            $users = $u->getPublicsById($uids, NULL, NULL, FALSE, TRUE, TRUE);
8✔
158
        }
159

160
        $mids = array_filter(array_column($logs, 'msgid'));
8✔
161
        $msgs = [];
8✔
162

163
        if (count($mids)) {
8✔
164
            $ms = $this->dbhr->preQuery("SELECT id, subject, sourceheader, envelopeto FROM messages WHERE id IN (" . implode(',', $mids) .");");
1✔
165
            foreach ($ms as $m) {
1✔
166
                $msgs[$m['id']] = $m;
1✔
167
            }
168
        }
169

170
        foreach ($logs as &$log) {
8✔
171
            $count++;
8✔
172

173
            if ($count % 1000 == 0) {
8✔
174
                #error_log("...$count / $total");
175
            }
176

177
            $log['timestamp'] = Utils::ISODate($log['timestamp']);
8✔
178

179
            if (Utils::pres('user', $log)) {
8✔
180
                $id = $log['user'];
8✔
181
                $log['user'] = Utils::presdef($log['user'], $users, NULL);
8✔
182

183
                if (!$log['user']) {
8✔
184
                    $log['user'] = User::purgedUser($id);
1✔
185
                }
186
            }
187

188
            if (Utils::pres('byuser', $log)) {
8✔
189
                $id = $log['byuser'];
8✔
190
                $log['byuser'] = Utils::presdef($log['byuser'], $users, NULL);
8✔
191

192
                if (!$log['byuser']) {
8✔
193
                    $log['byuser'] = User::purgedUser($id);
1✔
194
                }
195
            }
196

197
            if (Utils::pres('msgid', $log)) {
8✔
198
                $log['message'] = Utils::presdef($log['msgid'], $msgs, NULL);
1✔
199
            }
200

201
            if ($log['subtype'] == Log::SUBTYPE_OUTCOME && $log['text']) {
8✔
202
                # Trim the long text leaving just the outcome.
203
                $p = strpos($log['text'], ' ');
1✔
204

205
                if ($p) {
1✔
206
                    $log['text'] = substr($log['text'], 0, $p);
1✔
207
                    $log['text'] = str_replace(':', '', $log['text']);
1✔
208
                }
209
            }
210

211
            $ctx['id'] = $log['id'];
8✔
212
        }
213

214
        return($logs);
8✔
215
    }
216

217
    public function deleteLogsForMessage($msgid) {
218
        # Need to background as the original log request might be backgrounded.
219
        $this->dbhm->background("DELETE FROM logs WHERE msgid = $msgid");
3✔
220
    }
221
}
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