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

Freegle / iznik-server / e90df0bf-8f6b-4ec4-92e6-15504f5b3adf

pending completion
e90df0bf-8f6b-4ec4-92e6-15504f5b3adf

push

circleci

edwh
Test fixes.

19715 of 20791 relevant lines covered (94.82%)

32.56 hits per line

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

90.41
/http/api/session.php
1
<?php
2
namespace Freegle\Iznik;
3

4
require_once(IZNIK_BASE . '/lib/geoPHP/geoPHP.inc');
1✔
5

6
function session() {
7
    global $dbhr, $dbhm;
8
    $me = NULL;
45✔
9

10
    $modtools = Session::modtools();
45✔
11

12
    $sessionLogout = function($dbhr, $dbhm) {
45✔
13
        $id = 'No session';
2✔
14
        @session_start();
2✔
15
        if (isset($_SESSION)) {
2✔
16
            $id = Utils::pres('id', $_SESSION);
2✔
17
            if ($id) {
2✔
18
                $s = new Session($dbhr, $dbhm);
2✔
19
                $s->destroy($id, null);
2✔
20
            }
21
        }
22

23
        # Destroy the PHP session
24
        try {
25
            @session_destroy();
2✔
26
            @session_unset();
2✔
27
            @session_start();
2✔
28
            @session_regenerate_id(true);
2✔
29
        } catch (\Exception $e) {
×
30
        }
31

32
        return $id;
2✔
33
    };
34

35
    $ret = [ 'ret' => 100, 'status' => 'Unknown verb' ];
45✔
36

37
    switch ($_REQUEST['type']) {
45✔
38
        case 'GET': {
45✔
39
            # Check if we're logged in.  This will handle persistent sessions, pushcreds, as well as login via
40
            # the PHP session.
41
            $me = Session::whoAmI($dbhm, $dbhm);
31✔
42

43
            # Mobile app can send its version number, which we can use to determine if it is out of date.
44
            $appversion = Utils::presdef('appversion', $_REQUEST, NULL);
31✔
45

46
            if ($appversion == '2') {
31✔
47
                $ret = array('ret' => 123, 'status' => 'App is out of date');
2✔
48
            } else {
49
                if (Utils::pres('id', $_SESSION) && $me) {
31✔
50
                    # We're logged in.
51
                    if (!$modtools) {
28✔
52
                        # ...but we are running an old version of the code, probably the app, because we have
53
                        # not indicated which version we have.
54
                        $last = Utils::presdef('lastversiontime', $_REQUEST, NULL);
1✔
55

56
                        if (!$last || time() - $last > 24 * 60 * 60) {
1✔
57
                            $webversion = Utils::presdef('webversion', $_REQUEST, NULL);
1✔
58
                            $appversion = Utils::presdef('appversion', $_REQUEST, NULL);
1✔
59
                            $dbhm->background("INSERT INTO users_builddates (userid, webversion, appversion) VALUES ({$_SESSION['id']}, '$webversion', '$appversion') ON DUPLICATE KEY UPDATE timestamp = NOW(), webversion = '$webversion', appversion = '$appversion';");
1✔
60
                        }
61

62
                        # Ensure we have the cookie set up for Discourse forum login.
63
                        if (array_key_exists('persistent', $_SESSION)) {
1✔
64
                            #error_log("Set Discourse Cookie");
65
//                            @setcookie('Iznik-Forum-SSO', json_encode($_SESSION['persistent']), 0, '/', 'ilovefreegle.org');
66
                            @setcookie('Iznik-Forum-SSO', json_encode($_SESSION['persistent']), [
1✔
67
                               'expires' => 0,
68
                               'path' => '/',
69
                               'domain' => 'ilovefreegle.org',
70
                               'secure' => 'true',
71
                               'httponly' => 'false',
72
                               'samesite' => 'None'
73
                           ]);
74
                        }
75
                    } else {
76
                        # ModTools.  Ensure we have the cookie set up for Discourse login.
77
                        if (array_key_exists('persistent', $_SESSION)) {
27✔
78
                            #error_log("Set Discourse Cookie");
79
//                            @setcookie('Iznik-Discourse-SSO', json_encode($_SESSION['persistent']), 0, '/', 'modtools.org');
80
                            @setcookie('Iznik-Discourse-SSO', json_encode($_SESSION['persistent']), [
24✔
81
                                'expires' => 0,
82
                                'path' => '/',
83
                                'domain' => 'modtools.org',
84
                                'secure' => 'true',
85
                                'httponly' => 'false',
86
                                'samesite' => 'None'
87
                            ]);
88
                        }
89
                    }
90

91
                    $components = Utils::presdef('components', $_REQUEST, ['all']);
28✔
92
                    if ($components ==  ['all']) {
28✔
93
                        // Get all
94
                        $components = NULL;
20✔
95
                    }
96

97
                    if (gettype($components) == 'string') {
28✔
98
                        $components = [ $components ];
×
99
                    }
100

101
                    $ret = [ 'ret' => 0, 'status' => 'Success', 'myid' => Utils::presdef('id', $_SESSION, NULL) ];
28✔
102

103
                    if (!$components || (gettype($components) == 'array' && in_array('me', $components))) {
28✔
104
                        # Don't want to use cached information when looking at our own session.
105
                        $ret['me'] = $me->getPublic(NULL, FALSE, FALSE, FALSE, FALSE);
22✔
106
                        $loc = $me->getCity();
22✔
107
                        $ret['me']['city'] = $loc[0];
22✔
108
                        $ret['me']['lat'] = $loc[1];
22✔
109
                        $ret['me']['lng'] = $loc[2];
22✔
110

111
                        if (Utils::pres('profile', $ret['me']) && Utils::pres('url', $ret['me']['profile']) && strpos($ret['me']['profile']['url'], IMAGE_DOMAIN) !== FALSE) {
22✔
112
                            $ret['me']['profile']['ours'] = TRUE;
22✔
113
                        }
114

115
                        # Don't need to return this, and it might be large.
116
                        $ret['me']['messagehistory'] = NULL;
22✔
117

118
                        # Whether this user is on a group where microvolunteering is enabled.
119
                        $ret['me']['microvolunteering'] = $me->microVolunteering();
22✔
120

121
                        # The trust level for this user, as used by microvolunteering.
122
                        $ret['me']['trustlevel'] = $me->getPrivate('trustlevel');
22✔
123
                    }
124

125
                    $ret['persistent'] = Utils::presdef('persistent', $_SESSION, NULL);
28✔
126
                    $ret['jwt'] = Session::JWT($dbhr, $dbhm);
28✔
127

128
                    if (!$components || in_array('notifications', $components)) {
28✔
129
                        $settings = $me->getPrivate('settings');
20✔
130
                        $settings = $settings ? json_decode($settings, TRUE) : [];
20✔
131
                        $ret['me']['settings']['notifications'] = array_merge([
20✔
132
                            'email' => TRUE,
133
                            'emailmine' => FALSE,
134
                            'push' => TRUE,
135
                            'facebook' => TRUE,
136
                            'app' => TRUE
137
                        ], Utils::presdef('notifications', $settings, []));
20✔
138

139
                        $n = new PushNotifications($dbhr, $dbhm);
20✔
140
                        $ret['me']['notifications']['push'] = $n->get($ret['me']['id']);
20✔
141
                    }
142

143
                    if ($modtools) {
28✔
144
                        if (!$components || in_array('allconfigs', $components)) {
27✔
145
                            $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
20✔
146
                            $ret['configs'] = $me->getConfigs(TRUE);
20✔
147
                        } else if (in_array('configs', $components)) {
13✔
148
                            $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
1✔
149
                            $ret['configs'] = $me->getConfigs(FALSE);
1✔
150
                        }
151
                    }
152

153
                    if (!$components || in_array('emails', $components)) {
28✔
154
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
20✔
155
                        $ret['emails'] = $me->getEmails();
20✔
156
                    }
157

158
                    if (!$components || in_array('phone', $components)) {
28✔
159
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
20✔
160
                        $phone = $me->getPhone();
20✔
161

162
                        if ($phone) {
20✔
163
                            $ret['me']['phone'] = $phone[0];
1✔
164
                            $ret['me']['phonelastsent'] = $phone[1];
1✔
165
                            $ret['me']['phonelastclicked'] = $phone[2];
1✔
166
                        }
167
                    }
168

169
                    if (!$components || in_array('aboutme', $components)) {
28✔
170
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
20✔
171
                        $ret['me']['aboutme'] = $me->getAboutMe();
20✔
172
                    }
173

174
                    if (!$components || in_array('newsfeed', $components)) {
28✔
175
                        # Newsfeed count.  We return this in the session to avoid getting it on each page transition
176
                        # in the client.
177
                        $n = new Newsfeed($dbhr, $dbhm);
20✔
178
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
20✔
179
                        $ret['newsfeedcount'] = $n->getUnseen($me->getId());
20✔
180
                    }
181

182
                    if (!$components || in_array('logins', $components)) {
28✔
183
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
20✔
184
                        $ret['logins'] = $me->getLogins(FALSE);
20✔
185
                    }
186

187
                    if (!$components || in_array('expectedreplies', $components)) {
28✔
188
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
20✔
189

190
                        if ($me) {
20✔
191
                            $expecteds = $me->listExpectedReplies($me->getId());
20✔
192
                            $ret['me']['expectedreplies'] = count($expecteds);
20✔
193
                            $ret['me']['expectedchats'] = $expecteds;
20✔
194
                        }
195
                    }
196

197
                    if ($components && in_array('openposts', $components)) {
28✔
198
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
5✔
199

200
                        if ($me) {
5✔
201
                            $m = new MessageCollection($dbhr, $dbhm);
5✔
202
                            $ret['me']['openposts'] = $m->countMyPostsOpen();
5✔
203
                        }
204
                    }
205

206
                    if (!$components || in_array('groups', $components) || in_array('work', $components)) {
28✔
207
                        # Get groups including work when we're on ModTools; don't need that on the user site.
208
                        $u = new User($dbhr, $dbhm);
24✔
209
                        $ret['groups'] = $u->getMemberships(FALSE, NULL, $modtools, TRUE, $_SESSION['id']);
24✔
210

211
                        $gids = [];
24✔
212

213
                        foreach ($ret['groups'] as &$group) {
24✔
214
                            $gids[] = $group['id'];
16✔
215

216
                            # Remove large attributes we don't need in session.
217
                            unset($group['welcomemail']);
16✔
218
                            unset($group['description']);
16✔
219
                            unset($group['settings']['chaseups']['idle']);
16✔
220
                            unset($group['settings']['branding']);
16✔
221
                        }
222

223
                        if (count($gids)) {
24✔
224
                            $polys = $dbhr->preQuery("SELECT id,  ST_AsText(ST_Envelope(polyindex)) AS bbox,  ST_AsText(polyindex) AS poly FROM `groups` WHERE id IN (" . implode(',', $gids) . ")");
16✔
225

226
                            foreach ($ret['groups'] as &$group) {
16✔
227
                                foreach ($polys as $poly) {
16✔
228
                                    if ($poly['id'] == $group['id']) {
16✔
229
                                        try {
230
                                            $g = new \geoPHP();
16✔
231
                                            $p = $g->load($poly['bbox']);
16✔
232
                                            $bbox = $p->getBBox();
16✔
233
                                            $group['bbox'] = [
16✔
234
                                                'swlat' => $bbox['miny'],
16✔
235
                                                'swlng' => $bbox['minx'],
16✔
236
                                                'nelat' => $bbox['maxy'],
16✔
237
                                                'nelng' => $bbox['maxx'],
16✔
238
                                            ];
239

240
                                            $group['polygon'] = $poly['poly'];
16✔
241
                                        } catch (\Exception $e) {
×
242
                                            error_log("Bad polygon data for {$group['id']}");
×
243
                                        }
244
                                    }
245
                                }
246
                            }
247
                        }
248

249
                        if ($modtools) {
24✔
250
                            # If we have many groups this can generate many DB calls, so quicker to prefetch for Twitter and
251
                            # Facebook, even though that makes the code hackier.
252
                            $facebooks = GroupFacebook::listForGroups($dbhr, $dbhm, $gids);
23✔
253
                            $twitters = [];
23✔
254

255
                            if (count($gids) > 0) {
23✔
256
                                # We don't want to show any ones which aren't properly linked (yet), i.e. name is null.
257
                                $tws = $dbhr->preQuery("SELECT * FROM groups_twitter WHERE groupid IN (" . implode(',', $gids) . ") AND name IS NOT NULL;");
16✔
258
                                foreach ($tws as $tw) {
16✔
259
                                    $twitters[$tw['groupid']] = $tw;
1✔
260
                                }
261
                            }
262

263
                            foreach ($ret['groups'] as &$group) {
23✔
264
                                if ($group['role'] == User::ROLE_MODERATOR || $group['role'] == User::ROLE_OWNER) {
16✔
265
                                    # Return info on Twitter status.  This isn't secret info - we don't put anything confidential
266
                                    # in here - but it's of no interest to members so there's no point delaying them by
267
                                    # fetching it.
268
                                    #
269
                                    # Similar code in group.php.
270
                                    if (array_key_exists($group['id'], $twitters)) {
14✔
271
                                        $t = new Twitter($dbhr, $dbhm, $group['id'], $twitters[$group['id']]);
1✔
272
                                        $atts = $t->getPublic();
1✔
273
                                        unset($atts['token']);
1✔
274
                                        unset($atts['secret']);
1✔
275
                                        $atts['authdate'] = Utils::ISODate($atts['authdate']);
1✔
276
                                        $group['twitter'] = $atts;
1✔
277
                                    }
278

279
                                    # Ditto Facebook.
280
                                    if (array_key_exists($group['id'], $facebooks)) {
14✔
281
                                        $group['facebook'] = [];
1✔
282

283
                                        foreach ($facebooks[$group['id']] as $atts) {
1✔
284
                                            $group['facebook'][] = $atts;
1✔
285
                                        }
286
                                    }
287
                                }
288
                            }
289

290
                            if (!$components || in_array('work', $components)) {
23✔
291
                                $ret['work'] = $me->getWorkCounts($ret['groups']);
23✔
292

293
                                # Get Discourse notifications and unread topics, to drive mods through to that site.
294
                                if ($me->isFreegleMod()) {
23✔
295
                                    $unreadcount = 0;
6✔
296
                                    $notifcount = 0;
6✔
297
                                    $newcount = 0;
6✔
298

299
                                    # We cache the discourse name in the session for speed.  It is very unlikely to change.
300
                                    $username = Utils::presdef('discoursename', $_SESSION, NULL);
6✔
301

302
                                    if (!$username) {
6✔
303
                                        # We need this quick or not at all.  Also need to pass authentication in headers rather
304
                                        # than URL parameters.
305
                                        $ctx = stream_context_create(array('http'=> [
6✔
306
                                            'timeout' => 1,
307
                                            "method" => "GET",
308
                                            "header" => "Accept-language: en\r\n" .
309
                                                "Api-Key: " . DISCOURSE_APIKEY . "\r\n" .
310
                                                "Api-Username: system\r\n"
311
                                        ]));
312

313
                                        # Have to look up the name we need for other API calls by user id.
314
                                        $username = @file_get_contents(DISCOURSE_API . '/users/by-external/' . $me->getId() . '.json', FALSE, $ctx);
6✔
315
                                    }
316

317
                                    if ($username) {
6✔
318
                                        $_SESSION['discoursename'] = $username;
×
319
                                        $discourse = Utils::presdef('discourse', $_SESSION, NULL);
×
320

321
                                        if (!$discourse || (time() - Utils::presdef('timestamp', $discourse, time()) > 300)) {
×
322
                                            $users = json_decode($username, TRUE);
×
323

324
                                            if (Utils::pres('users', $users) && count($users['users'])) {
×
325
                                                $name = $users['users'][0]['username'];
×
326

327
                                                # We don't want to fetch Discourse info too often, for speed.
328

329
                                                $ctx = stream_context_create(array('http'=> [
×
330
                                                    'timeout' => 1,
331
                                                    "method" => "GET",
332
                                                    "header" => "Accept-language: en\r\n" .
333
                                                        "Api-Key: " . DISCOURSE_APIKEY . "\r\n" .
334
                                                        "Api-Username: $name\r\n"
×
335
                                                ]));
336

337
                                                $news = @file_get_contents(DISCOURSE_API . '/new.json', FALSE, $ctx);
×
338
                                                $unreads  = @file_get_contents(DISCOURSE_API . '/unread.json', FALSE, $ctx);
×
339
                                                $notifs = @file_get_contents(DISCOURSE_API . '/session/current.json', FALSE, $ctx);
×
340

341
                                                if ($news && $unreads && $notifs) {
×
342
                                                    $topics = json_decode($news, TRUE);
×
343

344
                                                    if (Utils::pres('topic_list', $topics)) {
×
345
                                                        $newcount = count($topics['topic_list']['topics']);
×
346
                                                    }
347

348
                                                    $topics = json_decode($unreads, TRUE);
×
349

350
                                                    if (Utils::pres('topic_list', $topics)) {
×
351
                                                        $unreadcount = count($topics['topic_list']['topics']);
×
352
                                                    }
353

354
                                                    $notifs = json_decode($notifs, TRUE);
×
355
                                                    if (Utils::pres('unread_notifications', $notifs)) {
×
356
                                                        $notifcount = intval($notifs['unread_notifications']);
×
357
                                                    }
358

359
                                                    $_SESSION['discourse'] = [
×
360
                                                        'notifications' => $notifcount,
361
                                                        'unreadtopics' => $unreadcount,
362
                                                        'newtopics' => $newcount,
363
                                                        'timestamp' => time()
×
364
                                                    ];
365
                                                }
366
                                                #error_log("$name notifs $notifcount new topics $newcount unread topics $unreadcount");
367
                                            }
368
                                        }
369
                                    }
370
                                }
371

372
                                # Using the value from session means we fall back to an old value if we can't get it, e.g.
373
                                # for rate-limiting.
374
                                $ret['discourse'] = Utils::presdef('discourse', $_SESSION, NULL);
28✔
375
                            }
376
                        }
377
                    }
378
                } else {
379
                    $ret = array('ret' => 1, 'status' => 'Not logged in');
6✔
380
                }
381
            }
382

383
            break;
31✔
384
        }
385

386
        case 'POST': {
21✔
387
            # Don't want to use cached information when looking at our own session.
388
            $me = Session::whoAmI($dbhm, $dbhm);
16✔
389

390
            # Login
391
            $fblogin = array_key_exists('fblogin', $_REQUEST) ? filter_var($_REQUEST['fblogin'], FILTER_VALIDATE_BOOLEAN) : FALSE;
16✔
392
            $fbaccesstoken = Utils::presdef('fbaccesstoken', $_REQUEST, NULL);
16✔
393
            $googlelogin = array_key_exists('googlelogin', $_REQUEST) ? filter_var($_REQUEST['googlelogin'], FILTER_VALIDATE_BOOLEAN) : FALSE;
16✔
394
            $googleauthcode = array_key_exists('googleauthcode', $_REQUEST) ? $_REQUEST['googleauthcode'] : NULL;
16✔
395
            $googlejwt = array_key_exists('googlejwt', $_REQUEST) ? $_REQUEST['googlejwt'] : NULL;
16✔
396
            $yahoologin = array_key_exists('yahoologin', $_REQUEST) ? filter_var($_REQUEST['yahoologin'], FILTER_VALIDATE_BOOLEAN) : FALSE;
16✔
397
            $yahoocodelogin = Utils::presdef('yahoocodelogin', $_REQUEST, NULL);
16✔
398
            $applelogin = array_key_exists('applelogin', $_REQUEST) ? filter_var($_REQUEST['applelogin'], FILTER_VALIDATE_BOOLEAN) : FALSE;
16✔
399
            $applecredentials = array_key_exists('applecredentials', $_REQUEST) ? $_REQUEST['applecredentials'] : NULL;
16✔
400
            $mobile = array_key_exists('mobile', $_REQUEST) ? filter_var($_REQUEST['mobile'], FILTER_VALIDATE_BOOLEAN) : FALSE;
16✔
401
            $email = array_key_exists('email', $_REQUEST) ? $_REQUEST['email'] : NULL;
16✔
402
            $password = array_key_exists('password', $_REQUEST) ? $_REQUEST['password'] : NULL;
16✔
403
            $returnto = array_key_exists('returnto', $_REQUEST) ? $_REQUEST['returnto'] : NULL;
16✔
404
            $action = Utils::presdef('action', $_REQUEST, NULL);
16✔
405
            $host = Utils::presdef('host', $_REQUEST, NULL);
16✔
406
            $keyu = (Utils::presint('u', $_REQUEST, NULL));
16✔
407
            $keyk = Utils::presdef('k', $_REQUEST, NULL);
16✔
408

409
            $id = NULL;
16✔
410
            $user = User::get($dbhr, $dbhm);
16✔
411
            $f = NULL;
16✔
412
            $ret = array('ret' => 1, 'status' => 'Invalid login details');
16✔
413

414
            if ($keyu && $keyk) {
16✔
415
                # uid and key login, used in email links and impersonation.
416
                $u = new User($dbhr, $dbhm, $keyu);
1✔
417

418
                if (Utils::presdef('id', $_SESSION, NULL) ==  $keyu || $u->linkLogin($keyk)) {
1✔
419
                    $id = $keyu;
1✔
420

421
                    $ret = [ 'ret' => 0, 'status' => 'Success' ];
1✔
422
                }
423
            } else if ($fblogin) {
15✔
424
                # We've been asked to log in via Facebook.
425
                $f = new Facebook($dbhr, $dbhm);
1✔
426
                list ($session, $ret) = $f->login($fbaccesstoken);
1✔
427
                /** @var Session $session */
428
                $id = $session ? $session->getUserId() : NULL;
1✔
429
            } else if ($yahoocodelogin) {
14✔
430
                $y = Yahoo::getInstance($dbhr, $dbhm, $host);
×
431
                list ($session, $ret) = $y->loginWithCode($yahoocodelogin);
×
432
                error_log("Yahoo code login with $yahoocodelogin returned " . var_export($ret, TRUE));
×
433
                /** @var Session $session */
434
                $id = $session ? $session->getUserId() : NULL;
×
435
                error_log("User id from session $id");
×
436
            } else if ($yahoologin) {
14✔
437
                # Yahoo old-style.  This is no longer used by FD, and as of Jan 2020 didn't work.
438
                $y = Yahoo::getInstance($dbhr, $dbhm, $host);
1✔
439
                list ($session, $ret) = $y->login($returnto);
1✔
440
                /** @var Session $session */
441
                $id = $session ? $session->getUserId() : NULL;
1✔
442
            } else if ($googlelogin) {
13✔
443
                # Google
444
                $g = new Google($dbhr, $dbhm, $mobile);
1✔
445

446
                if ($googleauthcode) {
1✔
447
                    list ($session, $ret) = $g->login($googleauthcode);
×
448
                } else if ($googlejwt) {
1✔
449
                    list ($session, $ret) = $g->loginWithJWT($googlejwt);
×
450
                }
451
                /** @var Session $session */
452
                $id = $session ? $session->getUserId() : NULL;
1✔
453
            } else if ($applelogin) {
12✔
454
                # Apple
455
                $a = new Apple($dbhr, $dbhm);
1✔
456
                list ($session, $ret) = $a->login($applecredentials);
1✔
457
                /** @var Session $session */
458
                $id = $session ? $session->getUserId() : NULL;
1✔
459
            } else if ($action) {
11✔
460
                switch ($action) {
461
                    case 'LostPassword': {
5✔
462
                        $id = $user->findByEmail($email);
1✔
463
                        $ret = [ 'ret' => 2, 'status' => "We don't know that email address" ];
1✔
464
                        
465
                        if ($id) {
1✔
466
                            $u = User::get($dbhr, $dbhm, $id);
1✔
467
                            $u->forgotPassword($email);
1✔
468
                            $ret = [ 'ret' => 0, 'status' => "Success" ];
1✔
469
                        }    
470
                        
471
                        break;
1✔
472
                    }
473

474
                    case 'Unsubscribe': {
4✔
475
                        $id = $user->findByEmail($email);
1✔
476
                        $ret = [ 'ret' => 2, 'status' => "We don't know that email address" ];
1✔
477

478
                        if ($id) {
1✔
479
                            $u = User::get($dbhr, $dbhm, $id);
1✔
480

481
                            # Get an auto-login unsubscribe link
482
                            $u->confirmUnsubscribe();
1✔
483
                            $ret = [ 'ret' => 0, 'status' => "Success", 'emailsent' => TRUE ];
1✔
484
                        }
485

486
                        break;
1✔
487
                    }
488

489
                    case 'Forget': {
3✔
490
                        $ret = array('ret' => 1, 'status' => 'Not logged in');
1✔
491

492
                        if ($me) {
1✔
493
                            # We don't allow mods/owners to do this, as they might do it by accident.
494
                            $ret = array('ret' => 2, 'status' => 'Please demote yourself to a member first');
1✔
495

496
                            if (!$me->isModerator()) {
1✔
497
                                # We don't allow spammers to do this.
498
                                $ret = array('ret' => 3, 'status' => 'We can\'t do this.');
1✔
499

500
                                $s = new Spam($dbhr, $dbhm);
1✔
501

502
                                if (!$s->isSpammer($me->getEmailPreferred())) {
1✔
503
                                    $me->forget('Request');
1✔
504

505
                                    # Log out.
506
                                    $ret = array('ret' => 0, 'status' => 'Success', 'destroyed' => $sessionLogout($dbhr, $dbhm));
1✔
507
                                }
508
                            }
509
                        }
510
                        break;
1✔
511
                    }
512

513
                    case 'Related': {
2✔
514
                        $ret = array('ret' => 1, 'status' => 'Not logged in');
2✔
515

516
                        if ($me) {
2✔
517
                            $userlist = Utils::presdef('userlist', $_REQUEST, NULL);
2✔
518

519
                            $ret = [ 'ret' => 0, 'status' => "Success" ];
2✔
520

521
                            if (gettype($userlist) == 'array') {
2✔
522
                                # Check whether the userlist contains at least one userid that has recently been
523
                                # active using the same IP address.  We'd expect this to be the case, and we have
524
                                # seen examples where we get related reports from googlebot.com, which suggests
525
                                # there are extensions out there which are exfiltrating session data from user's
526
                                # browsers - perhaps during link preview.  If that happens to two users at the same
527
                                # time on the same crawler, then we would get a report of related users.  This IP
528
                                # check prevents that.
529
                                $foundip = FALSE;
2✔
530
                                $ip = $_SERVER['REMOTE_ADDR'];
2✔
531

532
                                foreach ($userlist as $userid) {
2✔
533
                                    $logs = $dbhr->preQuery("SELECT * FROM logs_api WHERE userid = ? AND ip = ?  AND response LIKE '%Success%' AND request NOT LIKE '%View%' AND request NOT LIKE '%Related%'", [
2✔
534
                                        $userid,
535
                                        $ip
536
                                    ]);
537

538
                                    if (count($logs)) {
2✔
539
                                        $foundip = TRUE;
2✔
540
                                    } else {
541
                                        $ret = array('ret' => 2, 'status' => 'Not from recently active IP');
2✔
542
                                    }
543
                                }
544

545
                                if ($foundip) {
2✔
546
                                    $me->related($userlist);
5✔
547
                                }
548
                            }
549
                        }
550
                    }
551
                }
552
            }
553
            else if ($password && $email) {
7✔
554
                # Native login via username and password
555
                $ret = array('ret' => 2, 'status' => "We don't know that email address.  If you're new, please Register.");
6✔
556
                $possid = $user->findByEmail($email);
6✔
557
                if ($possid) {
6✔
558
                    $ret = array('ret' => 3, 'status' => "The password is wrong.  Maybe you've forgotten it?");
6✔
559
                    $u = User::get($dbhr, $dbhm, $possid);
6✔
560

561
                    # If we are currently logged in as an admin, then we can force a log in as anyone else.  This is
562
                    # very useful for debugging.
563
                    $force = $me && $me->isAdmin();
6✔
564

565
                    if ($u->login($password, $force)) {
6✔
566
                        $ret = array('ret' => 0, 'status' => 'Success');
6✔
567
                        $id = $possid;
6✔
568
                    }
569
                }
570
            }
571

572
            if ($id) {
16✔
573
                # Return some more useful info.
574
                $u = User::get($dbhr, $dbhm, $id);
10✔
575
                $ret['user'] = $u->getPublic();
10✔
576
                $ret['persistent'] = Utils::presdef('persistent', $_SESSION, NULL);
10✔
577
                $ret['jwt'] = Session::JWT($dbhr, $dbhm);
10✔
578
            }
579

580
            break;
16✔
581
        }
582

583
        case 'PATCH': {
9✔
584
            # Don't want to use cached information when looking at our own session.
585
            $me = Session::whoAmI($dbhm, $dbhm);
8✔
586

587
            if (!$me) {
8✔
588
                $ret = ['ret' => 1, 'status' => 'Not logged in'];
1✔
589
            } else {
590
                $fullname = Utils::presdef('displayname', $_REQUEST, NULL);
8✔
591
                $firstname = Utils::presdef('firstname', $_REQUEST, NULL);
8✔
592
                $lastname = Utils::presdef('lastname', $_REQUEST, NULL);
8✔
593
                $password = Utils::presdef('password', $_REQUEST, NULL);
8✔
594
                $key = Utils::presdef('key', $_REQUEST, NULL);
8✔
595

596
                if ($firstname) {
8✔
597
                    $me->setPrivate('firstname', $firstname);
1✔
598
                }
599
                if ($lastname) {
8✔
600
                    $me->setPrivate('lastname', $lastname);
1✔
601
                }
602
                if ($fullname) {
8✔
603
                    # Fullname is what we set from the client.  Zap the first/last names so that people who change
604
                    # their name for privacy reasons are respected.
605
                    $me->setPrivate('fullname', $fullname);
1✔
606
                    $me->setPrivate('firstname', NULL);
1✔
607
                    $me->setPrivate('lastname', NULL);
1✔
608
                }
609

610
                $settings = Utils::presdef('settings', $_REQUEST, NULL);
8✔
611
                if ($settings) {
8✔
612
                    $me->setPrivate('settings', json_encode($settings));
3✔
613

614
                    if (Utils::pres('mylocation', $settings)) {
3✔
615
                        # Save this off as the last known location.
616
                        $me->setPrivate('lastlocation', $settings['mylocation']['id']);
×
617
                    }
618
                }
619

620
                $notifs = Utils::presdef('notifications', $_REQUEST, NULL);
8✔
621
                if ($notifs) {
8✔
622
                    $n = new PushNotifications($dbhr, $dbhm);
1✔
623
                    $push = Utils::presdef('push', $notifs, NULL);
1✔
624
                    if ($push) {
1✔
625
                        switch ($push['type']) {
1✔
626
                            case PushNotifications::PUSH_GOOGLE:
627
                            case PushNotifications::PUSH_FIREFOX:
628
                            case PushNotifications::PUSH_FCM_ANDROID:
629
                            case PushNotifications::PUSH_FCM_IOS:
630
                                $n->add($me->getId(), $push['type'], $push['subscription']);
1✔
631
                                break;
1✔
632
                        }
633
                    }
634
                }
635

636
                $ret = ['ret' => 0, 'status' => 'Success'];
8✔
637

638
                $email = Utils::presdef('email', $_REQUEST, NULL);
8✔
639
                $force = array_key_exists('force', $_REQUEST) ? filter_var($_REQUEST['force'], FILTER_VALIDATE_BOOLEAN) : FALSE;
8✔
640
                if ($email) {
8✔
641
                    if (!$me->verifyEmail($email, $force)) {
3✔
642
                        $ret = ['ret' => 10, 'status' => "We've sent a verification mail; please check your mailbox." ];
3✔
643
                    }
644
                }
645

646
                if (array_key_exists('phone', $_REQUEST)) {
8✔
647
                    $phone = $_REQUEST['phone'];
1✔
648
                    if ($phone) {
1✔
649
                        $me->addPhone($phone);
1✔
650
                    } else {
651
                        $me->removePhone();
1✔
652
                    }
653
                }
654

655
                if ($key) {
8✔
656
                    if (!$me->confirmEmail($key)) {
1✔
657
                        $ret = ['ret' => 11, 'status' => 'Confirmation failed'];
1✔
658
                    }
659
                }
660

661
                if ($password) {
8✔
662
                    $me->addLogin(User::LOGIN_NATIVE, $me->getId(), $password);
1✔
663
                }
664

665
                if (array_key_exists('onholidaytill', $_REQUEST)) {
8✔
666
                    $me->setPrivate('onholidaytill', $_REQUEST['onholidaytill']);
1✔
667
                }
668

669
                if (array_key_exists('relevantallowed', $_REQUEST)) {
8✔
670
                    $me->setPrivate('relevantallowed', $_REQUEST['relevantallowed']);
1✔
671
                }
672

673
                if (array_key_exists('newslettersallowed', $_REQUEST)) {
8✔
674
                    $me->setPrivate('newslettersallowed', $_REQUEST['newslettersallowed']);
1✔
675
                }
676

677
                if (array_key_exists('aboutme', $_REQUEST)) {
8✔
678
                    $me->setAboutMe($_REQUEST['aboutme']);
2✔
679

680
                    if (strlen($_REQUEST['aboutme']) > 5) {
2✔
681
                        # Newsworthy.  But people might edit them a lot for typos, so look for a recent other
682
                        # one and update that before adding a new one.
683
                        $n = new Newsfeed($dbhr, $dbhm);
2✔
684
                        $nid = $n->findRecent($me->getId(), Newsfeed::TYPE_ABOUT_ME);
2✔
685

686
                        if ($nid) {
2✔
687
                            # Found a recent one - update it.
688
                            $n = new Newsfeed($dbhr, $dbhm, $nid);
1✔
689
                            $n->setPrivate('message', $_REQUEST['aboutme']);
1✔
690
                        } else if (strlen($_REQUEST['aboutme']) > 32) {
2✔
691
                            # No recent ones - add a new item.  But don't do this for very short ones, as they are
692
                            # of no interest on the newsfeed.
693
                            $n->create(Newsfeed::TYPE_ABOUT_ME, $me->getId(), $_REQUEST['aboutme']);
2✔
694
                        }
695
                    }
696
                }
697

698
                $simplemail = Utils::presdef('simplemail', $_REQUEST, NULL);
8✔
699

700
                if ($simplemail) {
8✔
701
                    # This is a way to set a bunch of email settings at once.
702
                    $me->setSimpleMail($simplemail);
1✔
703
                }
704

705
                Session::clearSessionCache();
8✔
706
            }
707
            break;
8✔
708
        }
709

710
        case 'DELETE': {
1✔
711
            # Logout.  Kill all sessions for this user.
712
            $ret = array('ret' => 0, 'status' => 'Success', 'destroyed' => $sessionLogout($dbhr, $dbhm));
1✔
713
            break;
1✔
714
        }
715
    }
716

717
    return($ret);
45✔
718
}
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