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

Freegle / iznik-server / 5b752abb-8537-45da-bb5a-2ef7c5b14993

11 Dec 2024 10:40AM UTC coverage: 92.434% (-0.002%) from 92.436%
5b752abb-8537-45da-bb5a-2ef7c5b14993

push

circleci

edwh
Track sources of users

3 of 4 new or added lines in 1 file covered. (75.0%)

2 existing lines in 2 files now uncovered.

25483 of 27569 relevant lines covered (92.43%)

31.46 hits per line

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

88.63
/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;
50✔
8
    $me = NULL;
50✔
9

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

12
    $sessionLogout = function($dbhr, $dbhm) {
50✔
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
    };
50✔
34

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

37
    switch ($_REQUEST['type']) {
50✔
38
        case 'GET': {
50✔
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);
33✔
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);
33✔
45

46
            if ($appversion == '2') {
33✔
47
                $ret = array('ret' => 123, 'status' => 'App is out of date');
2✔
48
            } else {
49
                if (Utils::pres('id', $_SESSION) && $me) {
33✔
50
                    # We're logged in.
51
                    if (!$modtools) {
29✔
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,
1✔
68
                               'path' => '/',
1✔
69
                               'domain' => 'ilovefreegle.org',
1✔
70
                               'secure' => 'true',
1✔
71
                               'httponly' => 'false',
1✔
72
                               'samesite' => 'None'
1✔
73
                           ]);
1✔
74
                        }
75
                    } else {
76
                        # ModTools.  Ensure we have the cookie set up for Discourse login.
77
                        if (array_key_exists('persistent', $_SESSION)) {
28✔
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']), [
26✔
81
                                'expires' => 0,
26✔
82
                                'path' => '/',
26✔
83
                                'domain' => 'modtools.org',
26✔
84
                                'secure' => 'true',
26✔
85
                                'httponly' => 'false',
26✔
86
                                'samesite' => 'None'
26✔
87
                            ]);
26✔
88
                        }
89
                    }
90

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

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

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

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

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

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

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

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

124
                        $ret['me']['source'] = $me->getPrivate('source');
23✔
125
                    }
126

127
                    $ret['persistent'] = Utils::presdef('persistent', $_SESSION, NULL);
29✔
128
                    $ret['jwt'] = Session::JWT($dbhr, $dbhm);
29✔
129

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

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

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

155
                    if (!$components || in_array('emails', $components)) {
29✔
156
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
21✔
157
                        $ret['emails'] = $me->getEmails();
21✔
158
                    }
159

160
                    if (!$components || in_array('phone', $components)) {
29✔
161
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
21✔
162
                        $phone = $me->getPhone();
21✔
163

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

171
                    if (!$components || in_array('aboutme', $components)) {
29✔
172
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
21✔
173
                        $ret['me']['aboutme'] = $me->getAboutMe();
21✔
174
                    }
175

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

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

189
                    if (!$components || in_array('expectedreplies', $components)) {
29✔
190
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
21✔
191

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

199
                    if ($components && in_array('openposts', $components)) {
29✔
200
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
5✔
201

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

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

213
                        $gids = [];
25✔
214

215
                        foreach ($ret['groups'] as &$group) {
25✔
216
                            $gids[] = $group['id'];
17✔
217

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

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

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

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

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

256
                            foreach ($ret['groups'] as &$group) {
24✔
257
                                if ($group['role'] == User::ROLE_MODERATOR || $group['role'] == User::ROLE_OWNER) {
17✔
258
                                    # Return info on Facebook status.  This isn't secret info - we don't put anything confidential
259
                                    # in here - but it's of no interest to members so there's no point delaying them by
260
                                    # fetching it.
261
                                    #
262
                                    # Similar code in group.php.
263
                                    if (array_key_exists($group['id'], $facebooks)) {
15✔
264
                                        $group['facebook'] = [];
1✔
265

266
                                        foreach ($facebooks[$group['id']] as $atts) {
1✔
267
                                            $group['facebook'][] = $atts;
1✔
268
                                        }
269
                                    }
270
                                }
271
                            }
272

273
                            if (!$components || in_array('work', $components)) {
24✔
274
                                $ret['work'] = $me->getWorkCounts($ret['groups']);
24✔
275

276
                                # Get Discourse notifications and unread topics, to drive mods through to that site.
277
                                if ($me->isFreegleMod()) {
24✔
278
                                    $unreadcount = 0;
8✔
279
                                    $notifcount = 0;
8✔
280
                                    $newcount = 0;
8✔
281

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

285
                                    if (!$username) {
8✔
286
                                        # We need this quick or not at all.  Also need to pass authentication in headers rather
287
                                        # than URL parameters.
288
                                        $ctx = stream_context_create(array('http'=> [
8✔
289
                                            'timeout' => 1,
8✔
290
                                            "method" => "GET",
8✔
291
                                            "header" => "Accept-language: en\r\n" .
8✔
292
                                                "Api-Key: " . DISCOURSE_APIKEY . "\r\n" .
8✔
293
                                                "Api-Username: system\r\n"
8✔
294
                                        ]));
8✔
295

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

300
                                    if ($username) {
8✔
301
                                        $_SESSION['discoursename'] = $username;
×
302
                                        $discourse = Utils::presdef('discourse', $_SESSION, NULL);
×
303

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

307
                                            if (Utils::pres('users', $users) && count($users['users'])) {
×
308
                                                $name = $users['users'][0]['username'];
×
309

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

312
                                                $ctx = stream_context_create(array('http'=> [
×
313
                                                    'timeout' => 1,
×
314
                                                    "method" => "GET",
×
315
                                                    "header" => "Accept-language: en\r\n" .
×
316
                                                        "Api-Key: " . DISCOURSE_APIKEY . "\r\n" .
×
317
                                                        "Api-Username: $name\r\n"
×
318
                                                ]));
×
319

320
                                                $news = @file_get_contents(DISCOURSE_API . '/new.json', FALSE, $ctx);
×
321
                                                $unreads  = @file_get_contents(DISCOURSE_API . '/unread.json', FALSE, $ctx);
×
322
                                                $notifs = @file_get_contents(DISCOURSE_API . '/session/current.json', FALSE, $ctx);
×
323

324
                                                if ($news && $unreads && $notifs) {
×
325
                                                    $topics = json_decode($news, TRUE);
×
326

327
                                                    if (Utils::pres('topic_list', $topics)) {
×
328
                                                        $newcount = count($topics['topic_list']['topics']);
×
329
                                                    }
330

331
                                                    $topics = json_decode($unreads, TRUE);
×
332

333
                                                    if (Utils::pres('topic_list', $topics)) {
×
334
                                                        $unreadcount = count($topics['topic_list']['topics']);
×
335
                                                    }
336

337
                                                    $notifs = json_decode($notifs, TRUE);
×
338
                                                    if (Utils::pres('unread_notifications', $notifs)) {
×
339
                                                        $notifcount = intval($notifs['unread_notifications']);
×
340
                                                    }
341

342
                                                    $_SESSION['discourse'] = [
×
343
                                                        'notifications' => $notifcount,
×
344
                                                        'unreadtopics' => $unreadcount,
×
345
                                                        'newtopics' => $newcount,
×
346
                                                        'timestamp' => time()
×
347
                                                    ];
×
348
                                                }
349
                                                #error_log("$name notifs $notifcount new topics $newcount unread topics $unreadcount");
350
                                            }
351
                                        }
352
                                    }
353
                                }
354

355
                                # Using the value from session means we fall back to an old value if we can't get it, e.g.
356
                                # for rate-limiting.
357
                                $ret['discourse'] = Utils::presdef('discourse', $_SESSION, NULL);
29✔
358
                            }
359
                        }
360
                    }
361
                } else {
362
                    $ret = array('ret' => 1, 'status' => 'Not logged in');
7✔
363
                }
364
            }
365

366
            break;
33✔
367
        }
368

369
        case 'POST': {
25✔
370
            # Don't want to use cached information when looking at our own session.
371
            $me = Session::whoAmI($dbhm, $dbhm);
18✔
372

373
            # Login
374
            $fblogin = array_key_exists('fblogin', $_REQUEST) ? filter_var($_REQUEST['fblogin'], FILTER_VALIDATE_BOOLEAN) : FALSE;
18✔
375
            $fbaccesstoken = Utils::presdef('fbaccesstoken', $_REQUEST, NULL);
18✔
376
            $fblimited = Utils::presbool('fblimited', $_REQUEST, FALSE);
18✔
377
            $googlelogin = array_key_exists('googlelogin', $_REQUEST) ? filter_var($_REQUEST['googlelogin'], FILTER_VALIDATE_BOOLEAN) : FALSE;
18✔
378
            $googleauthcode = array_key_exists('googleauthcode', $_REQUEST) ? $_REQUEST['googleauthcode'] : NULL;
18✔
379
            $googlejwt = array_key_exists('googlejwt', $_REQUEST) ? $_REQUEST['googlejwt'] : NULL;
18✔
380
            $yahoologin = array_key_exists('yahoologin', $_REQUEST) ? filter_var($_REQUEST['yahoologin'], FILTER_VALIDATE_BOOLEAN) : FALSE;
18✔
381
            $yahoocodelogin = Utils::presdef('yahoocodelogin', $_REQUEST, NULL);
18✔
382
            $applelogin = array_key_exists('applelogin', $_REQUEST) ? filter_var($_REQUEST['applelogin'], FILTER_VALIDATE_BOOLEAN) : FALSE;
18✔
383
            $applecredentials = array_key_exists('applecredentials', $_REQUEST) ? $_REQUEST['applecredentials'] : NULL;
18✔
384
            $mobile = array_key_exists('mobile', $_REQUEST) ? filter_var($_REQUEST['mobile'], FILTER_VALIDATE_BOOLEAN) : FALSE;
18✔
385
            $email = array_key_exists('email', $_REQUEST) ? $_REQUEST['email'] : NULL;
18✔
386
            $password = array_key_exists('password', $_REQUEST) ? $_REQUEST['password'] : NULL;
18✔
387
            $returnto = array_key_exists('returnto', $_REQUEST) ? $_REQUEST['returnto'] : NULL;
18✔
388
            $action = Utils::presdef('action', $_REQUEST, NULL);
18✔
389
            $host = Utils::presdef('host', $_REQUEST, NULL);
18✔
390
            $keyu = (Utils::presint('u', $_REQUEST, NULL));
18✔
391
            $keyk = Utils::presdef('k', $_REQUEST, NULL);
18✔
392

393
            $id = NULL;
18✔
394
            $user = User::get($dbhr, $dbhm);
18✔
395
            $f = NULL;
18✔
396
            $ret = array('ret' => 1, 'status' => 'Invalid login details');
18✔
397

398
            if ($keyu && $keyk) {
18✔
399
                # uid and key login, used in email links and impersonation.
400
                $u = new User($dbhr, $dbhm, $keyu);
1✔
401

402
                if (Utils::presdef('id', $_SESSION, null) == $keyu || $u->linkLogin($keyk)) {
1✔
403
                    $id = $keyu;
1✔
404

405
                    $ret = ['ret' => 0, 'status' => 'Success'];
1✔
406
                }
407
            } else if ($fblimited) {
17✔
408
                # We've been asked to log in using Facebook Limited Login
409
                $f = new Facebook($dbhr, $dbhm);
×
410
                list ($session, $ret) = $f->loginLimited($fbaccesstoken);
×
411
                /** @var Session $session */
412
                $id = $session ? $session->getUserId() : NULL;
×
413
            } else if ($fblogin) {
17✔
414
                # We've been asked to log in via Facebook.
415
                $f = new Facebook($dbhr, $dbhm);
1✔
416
                list ($session, $ret) = $f->login($fbaccesstoken);
1✔
417
                /** @var Session $session */
418
                $id = $session ? $session->getUserId() : NULL;
1✔
419
            } else if ($yahoocodelogin) {
16✔
420
                $y = Yahoo::getInstance($dbhr, $dbhm, $host);
×
421
                list ($session, $ret) = $y->loginWithCode($yahoocodelogin);
×
422
                error_log("Yahoo code login with $yahoocodelogin returned " . var_export($ret, TRUE));
×
423
                /** @var Session $session */
424
                $id = $session ? $session->getUserId() : NULL;
×
425
                error_log("User id from session $id");
×
426
            } else if ($yahoologin) {
16✔
427
                # Yahoo old-style.  This is no longer used by FD, and as of Jan 2020 didn't work.
428
                $y = Yahoo::getInstance($dbhr, $dbhm, $host);
1✔
429
                list ($session, $ret) = $y->login($returnto);
1✔
430
                /** @var Session $session */
431
                $id = $session ? $session->getUserId() : NULL;
1✔
432
            } else if ($googlelogin) {
15✔
433
                # Google
434
                $g = new Google($dbhr, $dbhm, $mobile);
1✔
435

436
                if ($googleauthcode) {
1✔
437
                    list ($session, $ret) = $g->login($googleauthcode);
×
438
                } else if ($googlejwt) {
1✔
439
                    list ($session, $ret) = $g->loginWithJWT($googlejwt);
×
440
                }
441
                /** @var Session $session */
442
                $id = $session ? $session->getUserId() : NULL;
1✔
443
            } else if ($applelogin) {
14✔
444
                # Apple
445
                $a = new Apple($dbhr, $dbhm);
1✔
446
                list ($session, $ret) = $a->login($applecredentials);
1✔
447
                /** @var Session $session */
448
                $id = $session ? $session->getUserId() : NULL;
1✔
449
            } else if ($action) {
13✔
450
                switch ($action) {
451
                    case 'LostPassword': {
6✔
452
                        $id = $user->findByEmail($email);
1✔
453
                        $ret = [ 'ret' => 2, 'status' => "We don't know that email address" ];
1✔
454
                        
455
                        if ($id) {
1✔
456
                            $u = User::get($dbhr, $dbhm, $id);
1✔
457
                            $u->forgotPassword($email);
1✔
458
                            $ret = [ 'ret' => 0, 'status' => "Success" ];
1✔
459
                        }    
460
                        
461
                        break;
1✔
462
                    }
463

464
                    case 'Unsubscribe': {
5✔
465
                        $id = $user->findByEmail($email);
1✔
466
                        $ret = [ 'ret' => 2, 'status' => "We don't know that email address" ];
1✔
467

468
                        if ($id) {
1✔
469
                            $u = User::get($dbhr, $dbhm, $id);
1✔
470

471
                            # Get an auto-login unsubscribe link
472
                            $u->confirmUnsubscribe();
1✔
473
                            $ret = [ 'ret' => 0, 'status' => "Success", 'emailsent' => TRUE ];
1✔
474
                        }
475

476
                        break;
1✔
477
                    }
478

479
                    case 'Forget': {
4✔
480
                        $ret = array('ret' => 1, 'status' => 'Not logged in');
2✔
481

482
                        $partner = Utils::presdef('partner', $_REQUEST, NULL);
2✔
483
                        $id = Utils::presdef('id', $_REQUEST, NULL);
2✔
484

485
                        if ($partner) {
2✔
486
                            // Might not be logged in but still have control over this user.
487
                            list ($partner, $domain) = Session::partner($dbhr, $partner);
1✔
488

489
                            if ($partner) {
1✔
490
                                $u = new User($dbhr, $dbhm, $id);
1✔
491

492
                                if ($u->getId() == $id && $u->getPrivate('ljuserid')) {
1✔
493
                                    $u->limbo();
1✔
494
                                    $ret = [ 'ret' => 0, 'status' => "Success" ];
1✔
495
                                }
496
                            }
497
                        } else if ($me) {
2✔
498
                            # We don't allow mods/owners to do this, as they might do it by accident.
499
                            $ret = array('ret' => 2, 'status' => 'Please demote yourself to a member first');
1✔
500

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

505
                                $s = new Spam($dbhr, $dbhm);
1✔
506

507
                                if (!$s->isSpammer($me->getEmailPreferred())) {
1✔
508
                                    $me->limbo();
1✔
509

510
                                    # Log out.
511
                                    $ret = array('ret' => 0, 'status' => 'Success', 'destroyed' => $sessionLogout($dbhr, $dbhm));
1✔
512
                                }
513
                            }
514
                        }
515
                        break;
2✔
516
                    }
517

518
                    case 'Related': {
2✔
519
                        $ret = array('ret' => 1, 'status' => 'Not logged in');
2✔
520

521
                        if ($me) {
2✔
522
                            $userlist = Utils::presdef('userlist', $_REQUEST, NULL);
2✔
523

524
                            $ret = [ 'ret' => 0, 'status' => "Success" ];
2✔
525

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

537
                                foreach ($userlist as $userid) {
2✔
538
                                    $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✔
539
                                        $userid,
2✔
540
                                        $ip
2✔
541
                                    ]);
2✔
542

543
                                    if (count($logs)) {
2✔
544
                                        $foundip = TRUE;
2✔
545
                                    } else {
546
                                        $ret = array('ret' => 2, 'status' => 'Not from recently active IP');
2✔
547
                                    }
548
                                }
549

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

567
                    # If we are currently logged in as an admin, then we can force a log in as anyone else.  This is
568
                    # very useful for debugging.
569
                    $force = $me && $me->isAdmin();
7✔
570

571
                    if ($u->login($password, $force)) {
7✔
572
                        $ret = array('ret' => 0, 'status' => 'Success');
7✔
573
                        $id = $possid;
7✔
574
                    }
575
                }
576
            }
577

578
            if ($id) {
18✔
579
                # Return some more useful info.
580
                $u = User::get($dbhr, $dbhm, $id);
12✔
581
                $ret['user'] = $u->getPublic();
12✔
582
                $ret['persistent'] = Utils::presdef('persistent', $_SESSION, NULL);
12✔
583
                $ret['jwt'] = Session::JWT($dbhr, $dbhm);
12✔
584
            }
585

586
            break;
18✔
587
        }
588

589
        case 'PATCH': {
11✔
590
            # Don't want to use cached information when looking at our own session.
591
            $me = Session::whoAmI($dbhm, $dbhm);
10✔
592

593
            if (!$me) {
10✔
594
                $ret = ['ret' => 1, 'status' => 'Not logged in'];
1✔
595
            } else {
596
                if (array_key_exists('marketingconsent', $_REQUEST)) {
10✔
597
                    // Consent to marketing under PECR.
598
                    $consent = Utils::presbool('marketingconsent', $_REQUEST, FALSE);
1✔
599
                    $me->setPrivate('marketingconsent', $consent);
1✔
600
                }
601

602
                $fullname = Utils::presdef('displayname', $_REQUEST, NULL);
10✔
603
                $firstname = Utils::presdef('firstname', $_REQUEST, NULL);
10✔
604
                $lastname = Utils::presdef('lastname', $_REQUEST, NULL);
10✔
605
                $password = Utils::presdef('password', $_REQUEST, NULL);
10✔
606
                $key = Utils::presdef('key', $_REQUEST, NULL);
10✔
607
                $source = Utils::presdef('source', $_REQUEST, NULL);
10✔
608

609
                if ($source) {
10✔
NEW
610
                    $me->setPrivate('source', $source);
×
611
                }
612
                if ($firstname) {
10✔
613
                    $me->setPrivate('firstname', $firstname);
1✔
614
                }
615
                if ($lastname) {
10✔
616
                    $me->setPrivate('lastname', $lastname);
1✔
617
                }
618
                if ($fullname) {
10✔
619
                    # Fullname is what we set from the client.  Zap the first/last names so that people who change
620
                    # their name for privacy reasons are respected.
621
                    $me->setPrivate('fullname', $fullname);
1✔
622
                    $me->setPrivate('firstname', NULL);
1✔
623
                    $me->setPrivate('lastname', NULL);
1✔
624
                }
625

626
                $settings = Utils::presdef('settings', $_REQUEST, NULL);
10✔
627
                if ($settings) {
10✔
628
                    $me->setPrivate('settings', json_encode($settings));
3✔
629

630
                    if (Utils::pres('mylocation', $settings)) {
3✔
631
                        # Save this off as the last known location.
632
                        $me->setPrivate('lastlocation', $settings['mylocation']['id']);
×
633
                    }
634
                }
635

636
                $notifs = Utils::presdef('notifications', $_REQUEST, NULL);
10✔
637
                if ($notifs) {
10✔
638
                    $n = new PushNotifications($dbhr, $dbhm);
1✔
639
                    $push = Utils::presdef('push', $notifs, NULL);
1✔
640
                    if ($push && Utils::pres('type', $push) && Utils::pres('subscription', $push)) {
1✔
641
                        switch ($push['type']) {
1✔
642
                            case PushNotifications::PUSH_GOOGLE:
643
                            case PushNotifications::PUSH_FIREFOX:
644
                            case PushNotifications::PUSH_FCM_ANDROID:
645
                            case PushNotifications::PUSH_FCM_IOS:
646
                            case PushNotifications::PUSH_BROWSER_PUSH:
647
                                $n->add($me->getId(), $push['type'], $push['subscription']);
1✔
648
                                break;
1✔
649
                        }
650
                    }
651
                }
652

653
                $ret = ['ret' => 0, 'status' => 'Success'];
10✔
654

655
                $email = Utils::presdef('email', $_REQUEST, NULL);
10✔
656
                $force = array_key_exists('force', $_REQUEST) ? filter_var($_REQUEST['force'], FILTER_VALIDATE_BOOLEAN) : FALSE;
10✔
657
                if ($email) {
10✔
658
                    if (!$me->verifyEmail($email, $force)) {
4✔
659
                        $ret = ['ret' => 10, 'status' => "We've sent a verification mail; please check your mailbox." ];
4✔
660
                    }
661
                }
662

663
                if (array_key_exists('phone', $_REQUEST)) {
10✔
664
                    $phone = $_REQUEST['phone'];
1✔
665
                    if ($phone) {
1✔
666
                        $me->addPhone($phone);
1✔
667
                    } else {
668
                        $me->removePhone();
1✔
669
                    }
670
                }
671

672
                if ($key) {
10✔
673
                    $confirmid = $me->confirmEmail($key);
1✔
674

675
                    if (!$confirmid) {
1✔
676
                        $ret = ['ret' => 11, 'status' => 'Confirmation failed'];
1✔
677
                    } else {
678
                        # That might have merged.
679
                        $sessions = $dbhr->preQuery("SELECT * FROM sessions WHERE userid = ?;", [
1✔
680
                            $confirmid
1✔
681
                        ]);
1✔
682

683
                        foreach ($sessions as $session) {
1✔
684
                            # We need to return new session info for the merged user.  The client will pick
685
                            # this up and use it for future requests to the v2 API.  If we didn't do
686
                            # this they'd have a JWT/persistent for the old user/session which no longer exists.
687
                            $_SESSION['id'] = $confirmid;
1✔
688
                            $_SESSION['persistent'] = [
1✔
689
                                'id' => $session['id'],
1✔
690
                                'series' => $session['series'],
1✔
691
                                'token' => $session['token'],
1✔
692
                                'userid' => $confirmid
1✔
693
                            ];
1✔
694

695
                            $ret['jwt'] = Session::JWT($dbhr, $dbhm);
1✔
696
                            $ret['persistent'] = Utils::presdef('persistent', $_SESSION, NULL);
1✔
697
                        }
698
                    }
699
                }
700

701
                if ($password) {
10✔
702
                    $me->addLogin(User::LOGIN_NATIVE, $me->getId(), $password);
1✔
703
                }
704

705
                if (array_key_exists('onholidaytill', $_REQUEST)) {
10✔
706
                    $me->setPrivate('onholidaytill', $_REQUEST['onholidaytill']);
1✔
707
                }
708

709
                if (array_key_exists('relevantallowed', $_REQUEST)) {
10✔
710
                    $me->setPrivate('relevantallowed', $_REQUEST['relevantallowed']);
1✔
711
                }
712

713
                if (array_key_exists('newslettersallowed', $_REQUEST)) {
10✔
714
                    $me->setPrivate('newslettersallowed', $_REQUEST['newslettersallowed']);
1✔
715
                }
716

717
                if (array_key_exists('aboutme', $_REQUEST)) {
10✔
718
                    $me->setAboutMe($_REQUEST['aboutme']);
2✔
719

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

726
                        if ($nid) {
2✔
727
                            # Found a recent one - update it.
728
                            $n = new Newsfeed($dbhr, $dbhm, $nid);
1✔
729
                            $n->setPrivate('message', $_REQUEST['aboutme']);
1✔
730
                        } else if (strlen($_REQUEST['aboutme']) > 32) {
2✔
731
                            # No recent ones - add a new item.  But don't do this for very short ones, as they are
732
                            # of no interest on the newsfeed.
733
                            $n->create(Newsfeed::TYPE_ABOUT_ME, $me->getId(), $_REQUEST['aboutme']);
2✔
734
                        }
735
                    }
736
                }
737

738
                $simplemail = Utils::presdef('simplemail', $_REQUEST, NULL);
10✔
739

740
                if ($simplemail) {
10✔
741
                    # This is a way to set a bunch of email settings at once.
742
                    $me->setSimpleMail($simplemail);
1✔
743
                }
744

745
                if (array_key_exists('deleted', $_REQUEST)) {
10✔
746
                    # Users who leave have deleted set, but can restore their account.  If they don't, their data
747
                    # will be purged later in the background.
748
                    $me->setPrivate('deleted', NULL);
×
749
                }
750

751
                Session::clearSessionCache();
10✔
752
            }
753
            break;
10✔
754
        }
755

756
        case 'DELETE': {
1✔
757
            # Logout.  Kill all sessions for this user.
758
            $ret = array('ret' => 0, 'status' => 'Success', 'destroyed' => $sessionLogout($dbhr, $dbhm));
1✔
759
            break;
1✔
760
        }
761
    }
762

763
    return($ret);
50✔
764
}
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