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

Freegle / iznik-server / #2450

25 Nov 2025 12:36PM UTC coverage: 90.579%. Remained the same
#2450

push

php-coveralls

edwh
Merge remote-tracking branch 'origin/master'

6 of 10 new or added lines in 3 files covered. (60.0%)

94 existing lines in 2 files now uncovered.

26335 of 29074 relevant lines covered (90.58%)

31.22 hits per line

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

96.62
/http/api/message.php
1
<?php
2
namespace Freegle\Iznik;
3

4
function message() {
5
    global $dbhr, $dbhm;
75✔
6

7
    $myid = Session::whoAmId($dbhr, $dbhm);
75✔
8

9
    $id = (Utils::presint('id', $_REQUEST, NULL));
75✔
10
    $tnpostid = (Utils::presint('tnpostid', $_REQUEST, NULL));
75✔
11
    $ret = [ 'ret' => 100, 'status' => 'Unknown verb' ];
75✔
12

13
    $ids = [ NULL ];
75✔
14

15
    if ($tnpostid && !$id) {
75✔
16
        # Get the (potentially multiple) messages which have this TN id.
17
        $m = new Message($dbhr, $dbhm);
1✔
18
        $ids = $m->findByTnPostId($tnpostid);
1✔
19
    } else if ($id) {
74✔
20
        $ids = [ $id ];
72✔
21
    }
22

23
    # Normally we have just one id, but if we are (for example) doing an edit from TN, we should apply it to all messages.
24
    foreach ($ids as $id) {
75✔
25
        $collection = Utils::presdef('collection', $_REQUEST, MessageCollection::APPROVED);
75✔
26
        $groupid = (Utils::presint('groupid', $_REQUEST, NULL));
75✔
27
        $reason = Utils::presdef('reason', $_REQUEST, NULL);
75✔
28
        $action = Utils::presdef('action', $_REQUEST, NULL);
75✔
29
        $subject = Utils::presdef('subject', $_REQUEST, NULL);
75✔
30
        $body = Utils::presdef('body', $_REQUEST, NULL);
75✔
31
        $stdmsgid = (Utils::presint('stdmsgid', $_REQUEST, NULL));
75✔
32
        $messagehistory = array_key_exists('messagehistory', $_REQUEST) ? filter_var($_REQUEST['messagehistory'], FILTER_VALIDATE_BOOLEAN) : FALSE;
75✔
33
        $localonly = array_key_exists('localonly', $_REQUEST) ? filter_var($_REQUEST['localonly'], FILTER_VALIDATE_BOOLEAN) : FALSE;
75✔
34
        $userid = (Utils::presint('userid', $_REQUEST, NULL));
75✔
35
        $userid = $userid ? $userid : NULL;
75✔
36
        $summary = array_key_exists('summary', $_REQUEST) ? filter_var($_REQUEST['summary'], FILTER_VALIDATE_BOOLEAN) : FALSE;
75✔
37

38
        $ret = [ 'ret' => 100, 'status' => 'Unknown verb' ];
75✔
39
        $ischat = FALSE;
75✔
40

41
        switch ($_REQUEST['type']) {
75✔
42
            case 'GET':
75✔
43
            case 'PUT':
64✔
44
            case 'DELETE': {
62✔
45
                $m = new Message($dbhr, $dbhm, $id);
55✔
46

47
                if ((!$m->getID() && $collection != MessageCollection::DRAFT) || $m->getDeleted()) {
55✔
48
                    $ret = ['ret' => 3, 'status' => 'Message does not exist'];
1✔
49
                    $m = NULL;
1✔
50
                } else {
51
                    switch ($collection) {
52
                        case MessageCollection::APPROVED:
53
                        case MessageCollection::DRAFT:
54
                            # No special checks for approved or draft - we could even be logged out.
55
                            break;
52✔
56
                        case MessageCollection::PENDING:
57
                        case MessageCollection::REJECTED:
58
                            if (!$myid) {
4✔
59
                                $ret = ['ret' => 1, 'status' => 'Not logged in'];
2✔
60
                                $m = NULL;
2✔
61
                            } else {
62
                                $groups = $m->getGroups();
4✔
63
                                $me = Session::whoAmI($dbhr, $dbhm);
4✔
64

65
                                if (count($groups) == 0 || !$groupid || ($me && !$me->isModOrOwner($groups[0]))) {
4✔
66
                                    $ret = ['ret' => 2, 'status' => 'Permission denied 1'];
2✔
67
                                    $m = NULL;
2✔
68
                                }
69
                            }
70
                            break;
4✔
71
                        case MessageCollection::CHAT:
72
                            # We can see the original message for a chat if we're a mod.  This is used in chat
73
                            # message review when we want to show the source.
74
                            $me = Session::whoAmI($dbhr, $dbhm);
1✔
75

76
                            if (!$myid || !$me->isModerator() || !$m->isChatByEmail()) {
1✔
77
                                $ret = ['ret' => 2, 'status' => 'Permission denied 3'];
1✔
78
                                $m = NULL;
1✔
79
                            } else {
80
                                $ischat = TRUE;
1✔
81
                            }
82
                            break;
1✔
83
                        default:
84
                            # If they don't say what they're doing properly, they can't do it.
85
                            $m = NULL;
1✔
86
                            $ret = [ 'ret' => 101, 'status' => 'Bad collection' ];
1✔
87
                            break;
1✔
88
                    }
89
                }
90

91
                if ($m) {
55✔
92
                    if ($_REQUEST['type'] == 'GET') {
54✔
93
                        $userlist = [];
34✔
94
                        $locationlist = [];
34✔
95
                        $atts = $m->getPublic($messagehistory, FALSE, FALSE, $userlist, $locationlist, $summary);
34✔
96
                        $me = Session::whoAmI($dbhr, $dbhm);
34✔
97
                        $mod = $me && $me->isModerator();
34✔
98

99
                        if ($mod && count($atts['groups']) == 0) {
34✔
100
                            $atts['message'] = $m->getPrivate('message');
1✔
101
                        }
102

103
                        # Moderators can view message history (raw email source) for moderation purposes
104
                        $canseemessagehistory = $messagehistory && $mod;
34✔
105

106
                        $cansee = $summary || $m->canSee($atts) || $ischat || $canseemessagehistory;
34✔
107

108
                        # We want to return the groups info even if we can't see the message, so that we can tell them
109
                        # which group to join.
110
                        $ret = [
34✔
111
                            'ret' => 2,
34✔
112
                            'status' => 'Permission denied 4',
34✔
113
                            'groups' => []
34✔
114
                        ];
34✔
115

116
                        foreach ($atts['groups'] as &$group) {
34✔
117
                            # The groups info returned in the message is not enough - doesn't include settings, for
118
                            # example.
119
                            $g = Group::get($dbhr, $dbhm, $group['groupid']);
31✔
120
                            $ret['groups'][$group['groupid']] = $g->getPublic();
31✔
121
                        }
122

123
                        if ($cansee) {
34✔
124
                            $ret['ret'] = 0;
34✔
125
                            $ret['status'] = 'Success';
34✔
126
                            $ret['message'] = $atts;
34✔
127
                        }
128
                    } else if ($_REQUEST['type'] == 'PUT') {
30✔
129
                        if ($collection == MessageCollection::DRAFT) {
29✔
130
                            # Draft messages are created by users, rather than parsed out from emails.  We might be
131
                            # creating one, or updating one.
132
                            $locationid = (Utils::presint('locationid', $_REQUEST, NULL));
29✔
133

134
                            $ret = [ 'ret' => 3, 'status' => 'Missing location - client error' ];
29✔
135

136
                            $email = Utils::presdef('email', $_REQUEST, NULL);
29✔
137
                            $sourceheader = Utils::pres('app', $_REQUEST) ? Message::FREEGLE_APP : Message::PLATFORM;
29✔
138
                            $uid = NULL;
29✔
139

140
                            if ($email) {
29✔
141
                                # We're queueing a draft so we need to save the user it.
142
                                $u = new User($dbhr, $dbhm);
×
143
                                $uid = $u->findByEmail($email);
×
144
                            }
145

146
                            if ($locationid) {
29✔
147
                                # We check the ID on the message object to handle the case where the client passes
148
                                # an ID which is not valid on the server.
149
                                if (!$m->getID()) {
29✔
150
                                    $id = $m->createDraft($uid, $sourceheader);
29✔
151

152
                                    # Use the master to avoid any replication windows.
153
                                    $m = new Message($dbhm, $dbhm, $id);
29✔
154

155
                                    # Record the last message we created in our session.  We use this to give access to
156
                                    # this message even if we're not logged in - for example when setting the FOP after
157
                                    # message submission.
158
                                    $_SESSION['lastmessage'] = $id;
29✔
159
                                } else {
160
                                    # The message should be ours.
161
                                    $sql = "SELECT * FROM messages_drafts WHERE msgid = ? AND session = ? OR (userid IS NOT NULL AND userid = ?);";
1✔
162
                                    $drafts = $dbhr->preQuery($sql, [ $id, session_id(), $myid ]);
1✔
163
                                    $m = NULL;
1✔
164
                                    foreach ($drafts as $draft) {
1✔
165
                                        $m = new Message($dbhr, $dbhm, $draft['msgid']);
1✔
166

167
                                        # Update the arrival time so that it doesn't appear to be expired.
168
                                        $m->setPrivate('arrival', date("Y-m-d H:i:s", time()));
1✔
169
                                    }
170

171
                                    # The message is not in drafts.  This can happen if someone creates a draft on one
172
                                    # device, then completes it on another, then goes back to the first and edits the
173
                                    # draft into a new post.  In this case create a new draft message, which will
174
                                    # override the one on the client.
175
                                    if (!$m) {
1✔
176
                                        $m = new Message($dbhr, $dbhm);
1✔
177
                                        $id = $m->createDraft(NULL, $sourceheader);
1✔
178
                                        $m = new Message($dbhm, $dbhm, $id);
1✔
179
                                        $_SESSION['lastmessage'] = $id;
1✔
180
                                    }
181
                                }
182

183
                                if ($m) {
29✔
184
                                    # Drafts have:
185
                                    # - a locationid
186
                                    # - a groupid (optional)
187
                                    # - a type
188
                                    # - an item
189
                                    # - a number available
190
                                    # - a subject constructed from the type, item and location.
191
                                    # - a fromuser if known (we might not have logged in yet)
192
                                    # - a textbody
193
                                    # - zero or more attachments
194
                                    if ($groupid) {
29✔
195
                                        $dbhm->preExec("UPDATE messages_drafts SET groupid = ? WHERE msgid = ?;", [$groupid, $m->getID()]);
27✔
196
                                    }
197

198
                                    $type = Utils::presdef('messagetype', $_REQUEST, NULL);
29✔
199

200
                                    # Associated the item with the message.  Use the master to avoid replication windows.
201
                                    $item = Utils::presdef('item', $_REQUEST, NULL);
29✔
202

203
                                    if (!$item || trim($item) === '') {
29✔
204
                                        $ret = ['ret' => 3, 'status' => 'Item is required'];
1✔
205
                                        break;
1✔
206
                                    }
207

208
                                    $i = new Item($dbhm, $dbhm);
28✔
209
                                    $itemid = $i->create($item);
28✔
210
                                    $m->deleteItems();
28✔
211
                                    $m->addItem($itemid);
28✔
212

213
                                    $fromuser = $myid;
28✔
214

215
                                    if (!$fromuser) {
28✔
216
                                        # Creating a draft - use the supplied email.
217
                                        $fromuser = $uid;
8✔
218
                                    }
219

220
                                    $textbody = Utils::presdef('textbody', $_REQUEST, NULL);
28✔
221
                                    $attachments = Utils::presdef('attachments', $_REQUEST, []);
28✔
222
                                    $m->setPrivate('locationid', $locationid);
28✔
223
                                    $m->setPrivate('type', $type);
28✔
224
                                    $m->setPrivate('subject', $item);
28✔
225
                                    $m->setPrivate('fromuser', $fromuser);
28✔
226
                                    $m->setPrivate('textbody', $textbody);
28✔
227
                                    $m->setPrivate('fromip', Utils::presdef('REMOTE_ADDR', $_SERVER, NULL));
28✔
228

229
                                    $availablenow = Utils::presint('availablenow', $_REQUEST, 1);
28✔
230
                                    $m->setPrivate('availableinitially', $availablenow);
28✔
231
                                    $m->setPrivate('availablenow', $availablenow);
28✔
232

233
                                    $m->replaceAttachments($attachments);
28✔
234

235
                                    $ret = [
28✔
236
                                        'ret' => 0,
28✔
237
                                        'status' => 'Success',
28✔
238
                                        'id' => $id
28✔
239
                                    ];
28✔
240
                                }
241
                            }
242
                        }
243
                    } else if ($_REQUEST['type'] == 'DELETE') {
1✔
244
                        $role = $m->getRoleForMessage()[0];
1✔
245
                        if ($role != User::ROLE_OWNER && $role != User::ROLE_MODERATOR) {
1✔
246
                            $ret = ['ret' => 2, 'status' => 'Permission denied 5'];
1✔
247
                        } else {
248
                            $m->delete($reason, NULL, NULL, NULL, NULL, $localonly);
1✔
249
                            $ret = [
1✔
250
                                'ret' => 0,
1✔
251
                                'status' => 'Success'
1✔
252
                            ];
1✔
253
                        }
254
                    }
255
                }
256
            }
257
                break;
54✔
258

259
            case 'PATCH': {
61✔
260
                $m = new Message($dbhr, $dbhm, $id);
13✔
261
                $ret = ['ret' => 3, 'status' => 'Message does not exist'];
13✔
262

263
                if ($m->getID()) {
13✔
264
                    # See if we can modify.
265
                    $canmod = $myid == $m->getFromuser();
13✔
266

267
                    if (!$canmod) {
13✔
268
                        $role = $m->getRoleForMessage()[0];
4✔
269
                        $canmod = $role == User::ROLE_MODERATOR || $role == User::ROLE_OWNER;
4✔
270

271
                        if ($role == User::ROLE_OWNER && Utils::pres('partner', $_SESSION)) {
4✔
272
                            # We have acquired owner rights by virtue of being a partner.  Pretend to be that user for the
273
                            # rest of the call.
274
                            $_SESSION['id'] = $m->getFromuser();
1✔
275
                        }
276
                    }
277

278
                    if ($canmod) {
13✔
279
                        # Ignore the canedit flag here - the client will either show or not show the edit button on this
280
                        # basis but editing is part of the repost flow and therefore needs to work.
281
                        $subject = Utils::presdef('subject', $_REQUEST, NULL);
12✔
282
                        $msgtype = Utils::presdef('msgtype', $_REQUEST, NULL);
12✔
283
                        $item = Utils::presdef('item', $_REQUEST, NULL);
12✔
284
                        $locationid = Utils::presint('locationid', $_REQUEST, NULL);
12✔
285
                        $location = Utils::presdef('location', $_REQUEST, NULL);
12✔
286
                        $lat = Utils::presfloat('lat', $_REQUEST, NULL);
12✔
287
                        $lng = Utils::presfloat('lng', $_REQUEST, NULL);
12✔
288
                        $textbody = Utils::presdef('textbody', $_REQUEST, NULL);
12✔
289
                        $fop = array_key_exists('FOP', $_REQUEST) ? $_REQUEST['FOP'] : NULL;
12✔
290
                        $availableinitially = Utils::presint('availableinitially', $_REQUEST, NULL);
12✔
291
                        $availablenow = Utils::presint('availablenow', $_REQUEST, NULL);
12✔
292
                        $attachments = array_key_exists('attachments', $_REQUEST) ? $_REQUEST['attachments'] : NULL;
12✔
293
                        $deliverypossible = array_key_exists('deliverypossible', $_REQUEST) ? Utils::presbool('deliverypossible', $_REQUEST, FALSE) : NULL;
12✔
294
                        $deadline = Utils::presdef('deadline', $_REQUEST, NULL);
12✔
295

296
                        $ret = [
12✔
297
                            'ret' => 0,
12✔
298
                            'status' => 'Success'
12✔
299
                        ];
12✔
300

301
                        if (!is_null($availablenow)) {
12✔
302
                            $m->setPrivate('availablenow', $availablenow);
5✔
303
                        }
304

305
                        if (!is_null($availableinitially)) {
12✔
306
                            $m->setPrivate('availableinitially', $availableinitially);
4✔
307
                        }
308

309
                        if ($location && !$locationid) {
12✔
310
                            $l = new Location($dbhr, $dbhm);
4✔
311
                            $locationid = $l->findByName($location);
4✔
312
                        }
313

314
                        if ($subject || $textbody || $msgtype || $item || $locationid || !is_null($attachments) || $lat || $lng) {
12✔
315
                            $partner = Utils::pres('partner', $_SESSION);
10✔
316

317
                            if ($partner) {
10✔
318
                                # Photos might have changed.
319
                                $m->deleteAllAttachments();
1✔
320
                                $textbody = $m->scrapePhotos($textbody);
1✔
321
                                $m->saveAttachments($id);
1✔
322

323
                                # Lat/lng might have changed
324
                                if ($lat || $lng) {
1✔
325
                                    $m->setPrivate('lat', $lat);
1✔
326
                                    $m->setPrivate('lng', $lng);
1✔
327
                                }
328
                            }
329

330
                            $me = Session::whoAmI($dbhr, $dbhm);
10✔
331

332
                            $rc = $m->edit($subject,
10✔
333
                                           $textbody,
10✔
334
                                           $msgtype,
10✔
335
                                           $item,
10✔
336
                                           $locationid,
10✔
337
                                           $attachments,
10✔
338
                                           TRUE,
10✔
339
                                           ($partner || ($me && $me->isApprovedMember($groupid))) ? $groupid : NULL);
10✔
340

341
                            $ret = $rc ? $ret : ['ret' => 2, 'status' => 'Edit failed'];
10✔
342

343
                            if ($rc) {
10✔
344
                                $ret = [
10✔
345
                                    'ret' => 0,
10✔
346
                                    'status' => 'Success'
10✔
347
                                ];
10✔
348
                            }
349
                        }
350

351
                        if (!is_null($fop)) {
12✔
352
                            $m->setFOP($fop);
1✔
353
                        }
354

355
                        if (!is_null($deliverypossible)) {
12✔
UNCOV
356
                            $m->setPrivate('deliverypossible', $deliverypossible);
×
357
                        }
358

359
                        if (array_key_exists('deadline', $_REQUEST) && (!$deadline || $deadline > '1970-01-01')) {
12✔
360
                            // Deadline can be null.
361
                            $m->setPrivate('deadline', $deadline, TRUE);
1✔
362

363
                            if ($deadline) {
1✔
364
                                // If we have a deadline in the future then make sure the message is not expired - that
365
                                // can happen if someone edits a message and extends the deadline.  If the message has
366
                                // also expired for other reasons then messages_expired will add it back in.
367
                                $m->removeExpiryOutcome();
1✔
368
                            }
369
                        }
370

371
                        if ($groupid) {
12✔
372
                            $dbhm->preExec("UPDATE messages_drafts SET groupid = ? WHERE msgid = ?;", [$groupid, $m->getID()]);
12✔
373
                        }
374
                    } else {
375
                        $ret = ['ret' => 2,
2✔
376
                            'status' => 'Permission denied 6',
2✔
377
                            'fromuser' => $m->getFromuser()
2✔
378
                        ];
2✔
379
                    }
380
                }
381
            }
382
                break;
13✔
383

384
            case 'POST': {
57✔
385
                $m = new Message($dbhr, $dbhm, $id);
57✔
386
                $ret = $m && $id && $m->getId() == $id ? ['ret' => 2, 'status' => 'Permission denied 7 '] : ['ret' => 10, 'status' => 'Message does not exist'];
57✔
387

388
                $role = $m && $id && $m->getId() == $id ? $m->getRoleForMessage()[0] : User::ROLE_NONMEMBER;
57✔
389

390
                if ($id && $m->getID() == $id) {
57✔
391
                    # These actions don't require permission, but they do need to be logged in as they record the userid.
392
                    if ($myid) {
56✔
393
                        if ($action =='Love') {
50✔
394
                            $m->like($myid, Message::LIKE_LOVE);
1✔
395
                            $ret = [ 'ret' => 0, 'status' => 'Success' ];
1✔
396
                        } else if ($action == 'Unlove') {
50✔
397
                            $m->unlike($myid, Message::LIKE_LOVE);
1✔
398
                            $ret = [ 'ret' => 0, 'status' => 'Success' ];
1✔
399
                        } else if ($action == 'Laugh') {
50✔
400
                            $m->like($myid, Message::LIKE_LAUGH);
1✔
401
                            $ret = [ 'ret' => 0, 'status' => 'Success' ];
1✔
402
                        } else if ($action == 'Unlaugh') {
50✔
403
                            $m->unlike($myid, Message::LIKE_LAUGH);
1✔
404
                            $ret = [ 'ret' => 0, 'status' => 'Success' ];
1✔
405
                        } else if ($action == 'View') {
50✔
406
                            $m->like($myid, Message::LIKE_VIEW);
1✔
407
                            $ret = ['ret' => 0, 'status' => 'Success'];
50✔
408
                        }
409
                    } else if ($action == 'View') {
15✔
410
                        // We don't currently record logged out views.
411
                        $ret = ['ret' => 0, 'status' => 'Success'];
1✔
412
                    }
413
                }
414

415
                if ($role == User::ROLE_MODERATOR || $role == User::ROLE_OWNER) {
57✔
416
                    $ret = [ 'ret' => 0, 'status' => 'Success' ];
55✔
417

418
                    switch ($action) {
419
                        case 'Delete':
55✔
420
                            # The delete call will handle any rejection on Yahoo if required.
421
                            $m->delete($reason, NULL, $subject, $body, $stdmsgid);
1✔
422
                            break;
1✔
423
                        case 'Reject':
55✔
424
                            # Ignore requests for messages which aren't pending.  Legitimate timing window when there
425
                            # are multiple mods.
426
                            if ($m->isPending($groupid)) {
3✔
427
                                $m->reject($groupid, $subject, $body, $stdmsgid);
2✔
428
                            }
429
                            break;
3✔
430
                        case 'Approve':
54✔
431
                            # Ignore requests for messages which aren't pending.  Legitimate timing window when there
432
                            # are multiple mods.
433
                            if ($m->isPending($groupid)) {
8✔
434
                                $m->approve($groupid, $subject, $body, $stdmsgid);
8✔
435
                            }
436
                            break;
8✔
437
                        case 'Reply':
50✔
438
                            $m->reply($groupid, $subject, $body, $stdmsgid);
1✔
439
                            break;
1✔
440
                        case 'Hold':
49✔
441
                            $m->hold();
1✔
442
                            break;
1✔
443
                        case 'Release':
49✔
444
                            $m->release();
1✔
445
                            break;
1✔
446
                        case 'Move':
48✔
447
                            $ret = $m->move($groupid);
1✔
448
                            break;
1✔
449
                        case 'Spam':
47✔
450
                            # Record for training.
451
                            $m->spam();
1✔
452
                            break;
1✔
453
                        case 'JoinAndPost':
47✔
454
                            # This is the mainline case for someone posting a message.  We find the nearest group, sign
455
                            # them up if need be, and then post the message.  We do this without being logged in, because
456
                            # that reduces friction.  If there is abuse of this, then we will find other ways to block the
457
                            # abuse.
458
                            $ret = ['ret' => 3, 'status' => 'Not our message'];
26✔
459
                            $sql = "SELECT * FROM messages_drafts WHERE msgid = ?;";
26✔
460
                            $drafts = $dbhr->preQuery($sql, [$id]);
26✔
461
                            $newuser = NULL;
26✔
462
                            $pw = NULL;
26✔
463
                            $hitwindow = FALSE;
26✔
464
                            #error_log("$sql, $id, " . session_id() . ", $myid");
465

466
                            foreach ($drafts as $draft) {
26✔
467
                                $m = new Message($dbhr, $dbhm, $draft['msgid']);
26✔
468

469
                                if (!$draft['groupid']) {
26✔
470
                                    # No group specified.  Find the group nearest the location.
UNCOV
471
                                    $l = new Location($dbhr, $dbhm, $m->getPrivate('locationid'));
×
UNCOV
472
                                    $ret = ['ret' => 4, 'status' => 'No nearby groups found'];
×
UNCOV
473
                                    $nears = $l->groupsNear(200);
×
474
                                } else {
475
                                    # A preferred group for this message.
476
                                    $nears = [ $draft['groupid'] ];
26✔
477
                                }
478

479
                                // @codeCoverageIgnoreStart
480
                                if (defined('USER_GROUP_OVERRIDE') && !Utils::pres('ignoregroupoverride', $_REQUEST)) {
481
                                    # We're in testing mode
482
                                    $g = new Group($dbhr, $dbhm);
483
                                    $nears = [ $g->findByShortName(USER_GROUP_OVERRIDE) ];
484
                                }
485
                                // @codeCoverageIgnoreEnd
486

487
                                if (count($nears) > 0) {
26✔
488
                                    $groupid = $nears[0];
26✔
489

490
                                    # Now we know which group we'd like to post on.  Make sure we have a user set up.
491
                                    $email = Utils::presdef('email', $_REQUEST, NULL);
26✔
492

493
                                    $u = User::get($dbhr, $dbhm);
26✔
494

495
                                    $ret = ['ret' => 5, 'status' => 'Failed to create user or email'];
26✔
496
                                    $unvalidated = FALSE;
26✔
497

498
                                    $me = Session::whoAmI($dbhr, $dbhm);
26✔
499

500
                                    if (!$email) {
26✔
501
                                        # The client ought to provide one.  But if they don't and we're logged in
502
                                        # then we can use ours.
503
                                        if ($me) {
1✔
504
                                            $uid = $me->getId();
1✔
505
                                            $email = $me->getEmailPreferred();
1✔
506
                                        }
507
                                    } else {
508
                                        list ($uid, $unvalidated) = $u->findByEmailIncludingUnvalidated($email);
25✔
509

510
                                        if ($unvalidated) {
25✔
511
                                            // They have tried to submit with an email which has not yet been validated
512
                                            // by them.  They are prompted to validate when they change email, so
513
                                            // they are expected to have validated it by now.
514
                                            $ret = ['ret' => 11, 'status' => 'Unvalidated email'];
1✔
515
                                        }
516
                                    }
517

518
                                    if (!$unvalidated) {
26✔
519
                                        if (!$uid) {
25✔
520
                                            if ($me) {
5✔
521
                                                // They've given us an email which is not on the system, but they're
522
                                                // logged in.  We don't expect this to happen - because the client
523
                                                // isn't supposed to offer the option to change email during posting
524
                                                // if they're logged in.
525
                                                $ret = ['ret' => 12, 'status' => 'Unvalidated email'];
1✔
526
                                            } else {
527
                                                # We don't yet know this user.  Create them.
528
                                                $name = substr($email, 0, strpos($email, '@'));
4✔
529
                                                $newuser = $u->create(null, null, $name, 'Created to allow post');
4✔
530

531
                                                # Create a password and mail it to them.  Also log them in and return it.  This
532
                                                # avoids us having to ask the user for a password, though they can change it if
533
                                                # they like.  Less friction.
534
                                                $pw = $u->inventPassword();
4✔
535
                                                $u->addLogin(User::LOGIN_NATIVE, $newuser, $pw);
4✔
536
                                                $eid = $u->addEmail($email, 1);
4✔
537

538
                                                if (!$eid) {
4✔
539
                                                    # There's a timing window where a parallel request could have added this
540
                                                    # email.  Check.
UNCOV
541
                                                    $uid2 = $u->findByEmail($email);
×
542

UNCOV
543
                                                    if ($uid2) {
×
544
                                                        # That has happened.  Delete the user we created and use the other.
UNCOV
545
                                                        $u->delete();
×
UNCOV
546
                                                        $newuser = null;
×
547
                                                        $pw = null;
×
UNCOV
548
                                                        $hitwindow = TRUE;
×
549

UNCOV
550
                                                        $u = User::get($dbhr, $dbhm, $uid2);
×
551
                                                        $eid = $u->getIdForEmail($email)['id'];
×
552

553
                                                        if ($u->getEmailPreferred() != $email) {
×
554
                                                            # The email specified is the one they currently want to use - make sure it's
UNCOV
555
                                                            $u->addEmail($email, 1, TRUE);
×
556
                                                        }
557
                                                    }
558
                                                } else {
559
                                                    $u->login($pw);
4✔
560
                                                    $u->welcome($email, $pw);
5✔
561
                                                }
562
                                            }
563
                                        } else if ($myid && $myid != $uid) {
20✔
564
                                            # We know the user, but it's not the one we're logged in as.  It's most likely
565
                                            # that the user is just confused about multiple email addresses.  We will reject
566
                                            # the message - the client is supposed to detect this case earlier on.
567
                                            $ret = ['ret' => 6, 'status' => 'That email address is in use for a different user.'];
1✔
568
                                        } else {
569
                                            $u = User::get($dbhr, $dbhm, $uid);
19✔
570
                                            $eid = $u->getIdForEmail($email)['id'];
19✔
571

572
                                            if ($u->getEmailPreferred() != $email) {
19✔
573
                                                # The email specified is the one they currently want to use - make sure it's
574
                                                # in there.
575
                                                $u->addEmail($email, 1, TRUE);
15✔
576
                                            }
577
                                        }
578

579
                                        if ($u->getId() && $eid) {
25✔
580
                                            # Now we have a user and an email.  We need to make sure they're a member of the
581
                                            # group in question.
582
                                            $g = Group::get($dbhr, $dbhm, $groupid);
23✔
583
                                            $fromemail = NULL;
23✔
584
                                            $cont = TRUE;
23✔
585

586
                                            # Check the message for worry words.
587
                                            $w = new WorryWords($dbhr, $dbhm, $groupid);
23✔
588
                                            $worry = $w->checkMessage($m->getID(), $m->getFromuser(), $m->getSubject(), $m->getTextbody());
23✔
589

590
                                            # Assume this post is moderated unless we decide otherwise below.
591
                                            $me = Session::whoAmI($dbhr, $dbhm);
23✔
592

593
                                            if ($u->isBanned($groupid)) {
23✔
594
                                                # We're not allowed to post.
595
                                                $cont = FALSE;
1✔
596
                                                $ret = ['ret' => 9, 'status' => 'Banned from this group'];
1✔
597
                                            } else if (!$u->isApprovedMember($groupid)) {
22✔
598
                                                # We're not yet a member.  Join the group.
599
                                                $addworked = $u->addMembership($groupid);
6✔
600

601
                                                # This is now a member, and we always moderate posts from new members,
602
                                                # so this goes to pending.
603
                                                $postcoll = MessageCollection::PENDING;
6✔
604

605
                                                if ($addworked === FALSE) {
6✔
606
                                                    # We couldn't join - we're banned.  Suppress the message below.
UNCOV
607
                                                    $cont = FALSE;
×
608

609
                                                    # Pretend it worked, if we suppressed a banned message.
610
                                                    $ret = ['ret' => 0, 'status' => 'Success', 'groupid' => $groupid, 'id' => $id];
6✔
611
                                                }
612
                                            } else if ($u->getMembershipAtt($groupid, 'ourPostingStatus') == Group::POSTING_PROHIBITED) {
17✔
613
                                                # We're not allowed to post.
614
                                                $cont = FALSE;
2✔
615
                                                $ret = ['ret' => 8, 'status' => 'Not allowed to post on this group'];
2✔
616
                                            } else {
617
                                                # They're already a member, so we might be able to put this straight
618
                                                # to approved.
619
                                                #
620
                                                # The entire group might be moderated, or the member might be, in which
621
                                                # case the message goes to pending, otherwise approved.
622
                                                #
623
                                                # Worrying messages always go to Pending.
624
                                                if ($worry || $g->getPrivate('overridemoderation') ==  Group::OVERRIDE_MODERATION_ALL) {
15✔
625
                                                    $postcoll = MessageCollection::PENDING;
2✔
626
                                                } else {
627
                                                    $postcoll = ($g->getSetting('moderated', 0) || $g->getSetting('close', 0)) ? MessageCollection::PENDING : $u->postToCollection($groupid);
13✔
628
                                                }
629
                                            }
630

631
                                            # Check if it's spam
632
                                            $s = new Spam($dbhr, $dbhm);
23✔
633
                                            list ($rc, $reason) = $s->checkMessage($m);
23✔
634

635
                                            if ($rc) {
23✔
636
                                                # It is.  Put in pending for review.
637
                                                $postcoll = MessageCollection::PENDING;
1✔
638
                                            }
639

640
                                            if ($worry) {
23✔
641
                                                $m->setPrivate('spamtype', Spam::REASON_WORRY_WORD);
1✔
642
                                                $m->setPrivate('spamreason','Referred to worry word ' . $worry[0]['worryword']['keyword']);
1✔
643
                                            }
644

645
                                            # We want the message to come from one of our emails rather than theirs, so
646
                                            # that replies come back to us and privacy is maintained.
647
                                            $fromemail = $u->inventEmail();
23✔
648

649
                                            # Make sure this email is attached to the user so that we don't invent
650
                                            # another next time.
651
                                            $u->addEmail($fromemail, 0, FALSE);
23✔
652

653
                                            $m->constructSubject($groupid);
23✔
654

655
                                            if ($cont) {
23✔
656
                                                if ($fromemail) {
20✔
657
                                                    $dbhm->preExec("INSERT IGNORE INTO messages_groups (msgid, groupid, collection,arrival, msgtype) VALUES (?,?,?,NOW(),?);", [
20✔
658
                                                        $draft['msgid'],
20✔
659
                                                        $groupid,
20✔
660
                                                        $postcoll,
20✔
661
                                                        $m->getType()
20✔
662
                                                    ]);
20✔
663

664
                                                    $ret = ['ret' => 7, 'status' => 'Failed to submit'];
20✔
665

666
                                                    if ($m->submit($u, $fromemail, $groupid)) {
20✔
667
                                                        # We sent it.
668
                                                        $ret = ['ret' => 0, 'status' => 'Success', 'groupid' => $groupid];
20✔
669

670
                                                        if ($postcoll == MessageCollection::APPROVED) {
20✔
671
                                                            # We index now; for pending messages we index when they are approved.
672
                                                            $m->addToSpatialIndex();
2✔
673
                                                            $m->index();
2✔
674
                                                        }
675
                                                    }
676
                                                }
677
                                            }
678

679
                                            # This user has been active recently.
680
                                            $dbhm->background("UPDATE users SET lastaccess = NOW() WHERE id = " . $u->getId() . ";");
23✔
681
                                        }
682
                                    }
683
                                }
684
                            }
685

686
                            $ret['newuser'] = $newuser;
26✔
687
                            $ret['newpassword'] = $pw;
26✔
688
                            $ret['hitwindow'] = $hitwindow;
26✔
689

690
                            break;
26✔
691
                        case 'BackToDraft':
28✔
692
                        case 'RejectToDraft':
28✔
693
                            # This is a message which has been rejected or reposted, which we are now going to edit.
694
                            $ret = ['ret' => 3, 'status' => 'Message does not exist'];
1✔
695
                            $sql = "SELECT * FROM messages WHERE id = ?;";
1✔
696
                            $msgs = $dbhr->preQuery($sql, [ $id ]);
1✔
697

698
                            foreach ($msgs as $msg) {
1✔
699
                                $m = new Message($dbhr, $dbhm, $id);
1✔
700
                                $ret = ['ret' => 4, 'status' => 'Failed to edit message'];
1✔
701

702
                                $role = $m->getRoleForMessage()[0];
1✔
703

704
                                if ($role == User::ROLE_MODERATOR || $role == User::ROLE_OWNER) {
1✔
705
                                    $rc = $m->backToDraft();
1✔
706

707
                                    if ($rc) {
1✔
708
                                        $ret = ['ret' => 0, 'status' => 'Success', 'messagetype' => $m->getType() ];
1✔
709
                                    }
710
                                }
711
                            }
712
                            break;
1✔
713
                        case 'RevertEdits':
27✔
714
                            $editid = (Utils::presint('editid', $_REQUEST, 0));
2✔
715
                            $role = $m->getRoleForMessage()[0];
2✔
716

717
                            if ($role ==  User::ROLE_OWNER || $role ==  User::ROLE_MODERATOR) {
2✔
718
                                $m->revertEdit($editid);
2✔
719
                                $ret = ['ret' => 0, 'status' => 'Success' ];
2✔
720
                            }
721
                            break;
2✔
722
                        case 'ApproveEdits':
27✔
723
                            $editid = (Utils::presint('editid', $_REQUEST, 0));
2✔
724
                            $role = $m->getRoleForMessage()[0];
2✔
725

726
                            if ($role ==  User::ROLE_OWNER || $role ==  User::ROLE_MODERATOR) {
2✔
727
                                $m->approveEdit($editid);
2✔
728
                                $ret = ['ret' => 0, 'status' => 'Success' ];
2✔
729
                            }
730
                            break;
2✔
731
                        case 'PartnerConsent':
27✔
732
                            $partner = Utils::presdef('partner', $_REQUEST, NULL);
1✔
733
                            $ret = ['ret' => 5, 'status' => 'Invalid parameters' ];
1✔
734

735
                            $role = $m->getRoleForMessage()[0];
1✔
736

737
                            if ($partner && ($role ==  User::ROLE_OWNER || $role ==  User::ROLE_MODERATOR)) {
1✔
738
                                if ($m->partnerConsent($partner)) {
1✔
739
                                    $ret = ['ret' => 0, 'status' => 'Success' ];
1✔
740
                                }
741
                            }
742
                            break;
1✔
743
                        case 'BackToPending':
26✔
744
                            # This is a message which has been rejected or reposted, which we are now going to edit.
745
                            $ret = ['ret' => 3, 'status' => 'Message does not exist'];
1✔
746
                            $sql = "SELECT msgid FROM messages_groups WHERE msgid = ? AND collection = ?;";
1✔
747
                            $msgs = $dbhr->preQuery($sql, [ $id, MessageCollection::APPROVED ]);
1✔
748

749
                            foreach ($msgs as $msg) {
1✔
750
                                $m = new Message($dbhr, $dbhm, $id);
1✔
751
                                $ret = ['ret' => 4, 'status' => 'Failed to move message'];
1✔
752

753
                                $role = $m->getRoleForMessage()[0];
1✔
754

755
                                if ($role == User::ROLE_MODERATOR || $role == User::ROLE_OWNER) {
1✔
756
                                    $rc = $m->backToPending();
1✔
757

758
                                    # We should cancel this on LoveJunk.  We won't send this to them again if it is
759
                                    # later approved, but this is rare so won't affect numbers.
760
                                    $l = new LoveJunk($dbhr, $dbhm);
1✔
761
                                    $l->delete($m->getId());
1✔
762

763
                                    $ret = ['ret' => 0, 'status' => 'Success' ];
1✔
764
                                }
765
                            }
766
                            break;
1✔
767
                    }
768
                }
769

770
                if ($id && $id == $m->getId()) {
57✔
771
                    # Other actions which we can do on our own messages.
772
                    $canmod = $myid == $m->getFromuser();
56✔
773

774
                    if (!$canmod) {
56✔
775
                        $role = $m->getRoleForMessage()[0];
22✔
776
                        $canmod = $role == User::ROLE_MODERATOR || $role == User::ROLE_OWNER;
22✔
777

778
                        if ($role == User::ROLE_OWNER && Utils::pres('partner', $_SESSION)) {
22✔
779
                            # We have acquired owner rights by virtue of being a partner.  Pretend to be that user for the
780
                            # rest of the call.
781
                            $_SESSION['id'] = $m->getFromuser();
1✔
782
                            $myid = $m->getFromuser();
1✔
783
                        }
784
                    }
785

786
                    if ($canmod) {
56✔
787
                        if ($userid > 0) {
54✔
788
                            $r = new ChatRoom($dbhr, $dbhm);
9✔
789
                            list ($rid, $blocked) = $r->createConversation($myid, $userid);
9✔
790
                            $cm = new ChatMessage($dbhr, $dbhm);
9✔
791
                        }
792

793
                        switch ($action) {
794
                            case 'Promise':
54✔
795
                                if ($userid) {
4✔
796
                                    # Userid is optional - TN can promise without a userid.
797
                                    $m->promise($userid);
4✔
798
                                    # Don't process immediately - there might be earlier unprocessed chatmessages sent via APIv2.
799
                                    # We must maintain message order until we fully move over to APIv2.
800
                                    list ($mid, $banned) = $cm->create($rid, $myid, NULL, ChatMessage::TYPE_PROMISED, $id, TRUE, NULL, NULL, NULL, NULL, NULL, FALSE, FALSE, FALSE);
4✔
801
                                    $ret = ['ret' => 0, 'status' => 'Success', 'id' => $mid];
4✔
802
                                } else {
803
                                    $m->promise($m->getFromuser());
1✔
804
                                    $ret = ['ret' => 0, 'status' => 'Success'];
1✔
805
                                }
806
                                break;
4✔
807
                            case 'Renege':
54✔
808
                                # Userid is optional - TN doesn't use it.
809
                                if ($userid > 0) {
3✔
810
                                    $m->renege($userid);
3✔
811
                                    # Don't process immediately - there might be earlier unprocessed chatmessages sent via APIv2.
812
                                    # We must maintain message order until we fully move over to APIv2.
813
                                    list ($mid, $banned) = $cm->create($rid, $myid, NULL, ChatMessage::TYPE_RENEGED, $id, TRUE, NULL, NULL, NULL, NULL, NULL, FALSE, FALSE, FALSE);
3✔
814
                                    $ret = ['ret' => 0, 'status' => 'Success', 'id' => $mid];
3✔
815
                                } else {
816
                                    $m->renege($m->getFromuser());
1✔
817
                                    $ret = ['ret' => 0, 'status' => 'Success'];
1✔
818
                                }
819

820
                                break;
3✔
821
                            case 'OutcomeIntended':
52✔
822
                                # Ignore duplicate attempts by user to supply an outcome.
823
                                if (!$m->hasOutcome()) {
6✔
824
                                    $outcome = Utils::presdef('outcome', $_REQUEST, NULL);
6✔
825
                                    $m->intendedOutcome($outcome);
6✔
826
                                }
827
                                $ret = ['ret' => 0, 'status' => 'Success'];
6✔
828
                                break;
6✔
829
                            case 'AddBy':
47✔
830
                                $count = Utils::presint('count', $_REQUEST, NULL);
5✔
831

832
                                if (!is_null($count)) {
5✔
833
                                    $m->addBy($userid, $count);
5✔
834
                                    $ret = ['ret' => 0, 'status' => 'Success'];
5✔
835
                                }
836
                                break;
5✔
837
                            case 'RemoveBy':
47✔
838
                                $m->removeBy($userid);
4✔
839
                                $ret = ['ret' => 0, 'status' => 'Success'];
4✔
840
                                break;
4✔
841
                            case 'Outcome':
47✔
842
                                # Ignore duplicate attempts by user to supply an outcome, unless it's on a post
843
                                # that has expired.  That allows us to make an expired post as TAKEN, for example.
844
                                $existingOutcome = $m->hasOutcome();
12✔
845

846
                                if (!$existingOutcome || $existingOutcome == Message::OUTCOME_EXPIRED) {
12✔
847
                                    $outcome = Utils::presdef('outcome', $_REQUEST, NULL);
12✔
848
                                    $h = Utils::presdef('happiness', $_REQUEST, NULL);
12✔
849
                                    $happiness = NULL;
12✔
850

851
                                    switch ($h) {
852
                                        case User::HAPPY:
853
                                        case User::FINE:
854
                                        case User::UNHAPPY:
855
                                            $happiness = $h;
3✔
856
                                            break;
3✔
857
                                    }
858

859
                                    $comment = Utils::presdef('comment', $_REQUEST, NULL);
12✔
860
                                    $messageForOthers = Utils::presdef('message', $_REQUEST, NULL);
12✔
861

862
                                    $ret = ['ret' => 1, 'status' => 'Odd action'];
12✔
863

864
                                    switch ($outcome) {
865
                                        case Message::OUTCOME_TAKEN: {
866
                                            if ($m->getType() == Message::TYPE_OFFER) {
8✔
867
                                                $m->mark($outcome, $comment, $happiness, $userid, $messageForOthers);
8✔
868
                                                $ret = ['ret' => 0, 'status' => 'Success'];
8✔
869
                                            };
870
                                            break;
8✔
871
                                        }
872
                                        case Message::OUTCOME_RECEIVED: {
873
                                            if ($m->getType() == Message::TYPE_WANTED) {
2✔
874
                                                $m->mark($outcome, $comment, $happiness, $userid);
2✔
875
                                                $ret = ['ret' => 0, 'status' => 'Success'];
2✔
876
                                            };
877
                                            break;
2✔
878
                                        }
879
                                        case Message::OUTCOME_WITHDRAWN: {
880
                                            $ret = ['ret' => 0, 'status' => 'Success'];
3✔
881

882
                                            # The message might still be pending.
883
                                            $groups = $m->getGroups(FALSE, TRUE);
3✔
884

885
                                            foreach ($groups as $gid) {
3✔
886
                                                if ($m->isPending($gid)) {
3✔
887
                                                    $g = Group::get($dbhr, $dbhm, $gid);
1✔
888

889
                                                    # If a message is withdrawn while it's pending we might as well delete it.
890
                                                    $m->delete("Withdrawn pending");
1✔
891
                                                    $ret['deleted'] = TRUE;
1✔
892
                                                } else {
893
                                                    $m->withdraw($comment, $happiness, $userid);
2✔
894
                                                }
895
                                            }
896

897
                                            break;
12✔
898
                                        }
899
                                    }
900
                                } else {
901
                                    $ret = ['ret' => 0, 'status' => 'Success'];
1✔
902
                                }
903
                                break;
12✔
904
                        }
905
                    }
906
                }
907
            }
908
        }
909
    }
910

911
    return($ret);
75✔
912
}
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