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

Freegle / iznik-server / 49885719-c516-447d-96c8-ce59b253cca2

03 Jan 2025 06:28PM UTC coverage: 92.309% (-0.01%) from 92.321%
49885719-c516-447d-96c8-ce59b253cca2

push

circleci

edwh
Test fixes.

25504 of 27629 relevant lines covered (92.31%)

31.52 hits per line

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

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

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

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

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

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

46
            if ($appversion == '2') {
34✔
47
                $ret = array('ret' => 123, 'status' => 'App is out of date');
2✔
48
            } else {
49
                if (Utils::pres('id', $_SESSION) && $me) {
34✔
50
                    # We're logged in.
51
                    if (!$modtools) {
30✔
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)) {
29✔
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']), [
27✔
81
                                'expires' => 0,
27✔
82
                                'path' => '/',
27✔
83
                                'domain' => 'modtools.org',
27✔
84
                                'secure' => 'true',
27✔
85
                                'httponly' => 'false',
27✔
86
                                'samesite' => 'None'
27✔
87
                            ]);
27✔
88
                        }
89
                    }
90

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

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

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

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

111
                        # If we've not logged in with a secure login mechanism then we can't access Support.
112
                        if (!$me->isAdminOrSupport() && ($ret['me']['systemrole'] == User::SYSTEMROLE_SUPPORT || $ret['me']['systemrole'] == User::SYSTEMROLE_ADMIN)) {
24✔
113
                            $ret['me']['systemrole'] = User::ROLE_MODERATOR;
1✔
114
                            $ret['me']['supportdisabled'] = TRUE;
1✔
115
                        }
116

117
                        if (Utils::pres('profile', $ret['me']) && Utils::pres('url', $ret['me']['profile']) && strpos($ret['me']['profile']['url'], IMAGE_DOMAIN) !== FALSE) {
24✔
118
                            $ret['me']['profile']['ours'] = TRUE;
24✔
119
                        }
120

121
                        # Don't need to return this, and it might be large.
122
                        $ret['me']['messagehistory'] = NULL;
24✔
123

124
                        # Whether this user is on a group where microvolunteering is enabled.
125
                        $ret['me']['microvolunteering'] = $me->microVolunteering();
24✔
126

127
                        # The trust level for this user, as used by microvolunteering.
128
                        $ret['me']['trustlevel'] = $me->getPrivate('trustlevel');
24✔
129

130
                        $ret['me']['source'] = $me->getPrivate('source');
24✔
131
                    }
132

133
                    $ret['persistent'] = Utils::presdef('persistent', $_SESSION, NULL);
30✔
134
                    $ret['jwt'] = Session::JWT($dbhr, $dbhm);
30✔
135

136
                    if (!$components || in_array('notifications', $components)) {
30✔
137
                        $settings = $me->getPrivate('settings');
22✔
138
                        $settings = $settings ? json_decode($settings, TRUE) : [];
22✔
139
                        $ret['me']['settings']['notifications'] = array_merge([
22✔
140
                            'email' => TRUE,
22✔
141
                            'emailmine' => FALSE,
22✔
142
                            'push' => TRUE,
22✔
143
                            'facebook' => TRUE,
22✔
144
                            'app' => TRUE
22✔
145
                        ], Utils::presdef('notifications', $settings, []));
22✔
146

147
                        $n = new PushNotifications($dbhr, $dbhm);
22✔
148
                        $ret['me']['notifications']['push'] = $n->get($ret['me']['id']);
22✔
149
                    }
150

151
                    if ($modtools) {
30✔
152
                        if (!$components || in_array('allconfigs', $components)) {
29✔
153
                            $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
22✔
154
                            $ret['configs'] = $me->getConfigs(TRUE);
22✔
155
                        } else if (in_array('configs', $components)) {
13✔
156
                            $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
1✔
157
                            $ret['configs'] = $me->getConfigs(FALSE);
1✔
158
                        }
159
                    }
160

161
                    if (!$components || in_array('emails', $components)) {
30✔
162
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
22✔
163
                        $ret['emails'] = $me->getEmails();
22✔
164
                    }
165

166
                    if (!$components || in_array('phone', $components)) {
30✔
167
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
22✔
168
                        $phone = $me->getPhone();
22✔
169

170
                        if ($phone) {
22✔
171
                            $ret['me']['phone'] = $phone[0];
1✔
172
                            $ret['me']['phonelastsent'] = $phone[1];
1✔
173
                            $ret['me']['phonelastclicked'] = $phone[2];
1✔
174
                        }
175
                    }
176

177
                    if (!$components || in_array('aboutme', $components)) {
30✔
178
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
22✔
179
                        $ret['me']['aboutme'] = $me->getAboutMe();
22✔
180
                    }
181

182
                    if (!$components || in_array('newsfeed', $components)) {
30✔
183
                        # Newsfeed count.  We return this in the session to avoid getting it on each page transition
184
                        # in the client.
185
                        $n = new Newsfeed($dbhr, $dbhm);
22✔
186
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
22✔
187
                        $ret['newsfeedcount'] = $n->getUnseen($me->getId());
22✔
188
                    }
189

190
                    if (!$components || in_array('logins', $components)) {
30✔
191
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
22✔
192
                        $ret['logins'] = $me->getLogins(FALSE);
22✔
193
                    }
194

195
                    if (!$components || in_array('expectedreplies', $components)) {
30✔
196
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
22✔
197

198
                        if ($me) {
22✔
199
                            $expecteds = $me->listExpectedReplies($me->getId());
22✔
200
                            $ret['me']['expectedreplies'] = count($expecteds);
22✔
201
                            $ret['me']['expectedchats'] = $expecteds;
22✔
202
                        }
203
                    }
204

205
                    if ($components && in_array('openposts', $components)) {
30✔
206
                        $me = $me ? $me : Session::whoAmI($dbhm, $dbhm);
5✔
207

208
                        if ($me) {
5✔
209
                            $m = new MessageCollection($dbhr, $dbhm);
5✔
210
                            $ret['me']['openposts'] = $m->countMyPostsOpen();
5✔
211
                        }
212
                    }
213

214
                    if (!$components || in_array('groups', $components) || in_array('work', $components)) {
30✔
215
                        # Get groups including work when we're on ModTools; don't need that on the user site.
216
                        $u = new User($dbhr, $dbhm);
26✔
217
                        $ret['groups'] = $u->getMemberships(FALSE, NULL, $modtools, TRUE, $_SESSION['id']);
26✔
218

219
                        $gids = [];
26✔
220

221
                        foreach ($ret['groups'] as &$group) {
26✔
222
                            $gids[] = $group['id'];
17✔
223

224
                            # Remove large attributes we don't need in session.
225
                            unset($group['welcomemail']);
17✔
226
                            unset($group['description']);
17✔
227
                            unset($group['settings']['chaseups']['idle']);
17✔
228
                            unset($group['settings']['branding']);
17✔
229
                        }
230

231
                        if (count($gids)) {
26✔
232
                            $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✔
233

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

248
                                            $group['polygon'] = $poly['poly'];
17✔
249
                                        } catch (\Exception $e) {
×
250
                                            error_log("Bad polygon data for {$group['id']}");
×
251
                                        }
252
                                    }
253
                                }
254
                            }
255
                        }
256

257
                        if ($modtools) {
26✔
258
                            # If we have many groups this can generate many DB calls, so quicker to prefetch for
259
                            # Facebook, even though that makes the code hackier.
260
                            $facebooks = GroupFacebook::listForGroups($dbhr, $dbhm, $gids);
25✔
261

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

272
                                        foreach ($facebooks[$group['id']] as $atts) {
1✔
273
                                            $group['facebook'][] = $atts;
1✔
274
                                        }
275
                                    }
276
                                }
277
                            }
278

279
                            if (!$components || in_array('work', $components)) {
25✔
280
                                $ret['work'] = $me->getWorkCounts($ret['groups']);
25✔
281

282
                                # Get Discourse notifications and unread topics, to drive mods through to that site.
283
                                if ($me->isFreegleMod()) {
25✔
284
                                    $unreadcount = 0;
8✔
285
                                    $notifcount = 0;
8✔
286
                                    $newcount = 0;
8✔
287

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

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

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

306
                                    if ($username) {
8✔
307
                                        $_SESSION['discoursename'] = $username;
×
308
                                        $discourse = Utils::presdef('discourse', $_SESSION, NULL);
×
309

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

313
                                            if (Utils::pres('users', $users) && count($users['users'])) {
×
314
                                                $name = $users['users'][0]['username'];
×
315

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

318
                                                $ctx = stream_context_create(array('http'=> [
×
319
                                                    'timeout' => 1,
×
320
                                                    "method" => "GET",
×
321
                                                    "header" => "Accept-language: en\r\n" .
×
322
                                                        "Api-Key: " . DISCOURSE_APIKEY . "\r\n" .
×
323
                                                        "Api-Username: $name\r\n"
×
324
                                                ]));
×
325

326
                                                $news = @file_get_contents(DISCOURSE_API . '/new.json', FALSE, $ctx);
×
327
                                                $unreads  = @file_get_contents(DISCOURSE_API . '/unread.json', FALSE, $ctx);
×
328
                                                $notifs = @file_get_contents(DISCOURSE_API . '/session/current.json', FALSE, $ctx);
×
329

330
                                                if ($news && $unreads && $notifs) {
×
331
                                                    $topics = json_decode($news, TRUE);
×
332

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

337
                                                    $topics = json_decode($unreads, TRUE);
×
338

339
                                                    if (Utils::pres('topic_list', $topics)) {
×
340
                                                        $unreadcount = count($topics['topic_list']['topics']);
×
341
                                                    }
342

343
                                                    $notifs = json_decode($notifs, TRUE);
×
344
                                                    if (Utils::pres('unread_notifications', $notifs)) {
×
345
                                                        $notifcount = intval($notifs['unread_notifications']);
×
346
                                                    }
347

348
                                                    $_SESSION['discourse'] = [
×
349
                                                        'notifications' => $notifcount,
×
350
                                                        'unreadtopics' => $unreadcount,
×
351
                                                        'newtopics' => $newcount,
×
352
                                                        'timestamp' => time()
×
353
                                                    ];
×
354
                                                }
355
                                                #error_log("$name notifs $notifcount new topics $newcount unread topics $unreadcount");
356
                                            }
357
                                        }
358
                                    }
359
                                }
360

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

372
            break;
34✔
373
        }
374

375
        case 'POST': {
26✔
376
            # Don't want to use cached information when looking at our own session.
377
            $me = Session::whoAmI($dbhm, $dbhm);
19✔
378

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

399
            $id = NULL;
19✔
400
            $user = User::get($dbhr, $dbhm);
19✔
401
            $f = NULL;
19✔
402
            $ret = array('ret' => 1, 'status' => 'Invalid login details');
19✔
403

404
            if ($keyu && $keyk) {
19✔
405
                # uid and key login, used in email links and impersonation.
406
                $u = new User($dbhr, $dbhm, $keyu);
1✔
407

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

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

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

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

474
                        if ($id) {
1✔
475
                            $u = User::get($dbhr, $dbhm, $id);
1✔
476

477
                            # Get an auto-login unsubscribe link
478
                            $u->confirmUnsubscribe();
1✔
479
                            $ret = [ 'ret' => 0, 'status' => "Success", 'emailsent' => TRUE ];
1✔
480
                        }
481

482
                        break;
1✔
483
                    }
484

485
                    case 'Forget': {
4✔
486
                        $ret = array('ret' => 1, 'status' => 'Not logged in');
2✔
487

488
                        $partner = Utils::presdef('partner', $_REQUEST, NULL);
2✔
489
                        $id = Utils::presdef('id', $_REQUEST, NULL);
2✔
490

491
                        if ($partner) {
2✔
492
                            // Might not be logged in but still have control over this user.
493
                            list ($partner, $domain) = Session::partner($dbhr, $partner);
1✔
494

495
                            if ($partner) {
1✔
496
                                $u = new User($dbhr, $dbhm, $id);
1✔
497

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

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

511
                                $s = new Spam($dbhr, $dbhm);
1✔
512

513
                                if (!$s->isSpammer($me->getEmailPreferred())) {
1✔
514
                                    $me->limbo();
1✔
515

516
                                    # Log out.
517
                                    $ret = array('ret' => 0, 'status' => 'Success', 'destroyed' => $sessionLogout($dbhr, $dbhm));
1✔
518
                                }
519
                            }
520
                        }
521
                        break;
2✔
522
                    }
523

524
                    case 'Related': {
2✔
525
                        $ret = array('ret' => 1, 'status' => 'Not logged in');
2✔
526

527
                        if ($me) {
2✔
528
                            $userlist = Utils::presdef('userlist', $_REQUEST, NULL);
2✔
529

530
                            $ret = [ 'ret' => 0, 'status' => "Success" ];
2✔
531

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

543
                                foreach ($userlist as $userid) {
2✔
544
                                    $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✔
545
                                        $userid,
2✔
546
                                        $ip
2✔
547
                                    ]);
2✔
548

549
                                    if (count($logs)) {
2✔
550
                                        $foundip = TRUE;
2✔
551
                                    } else {
552
                                        $ret = array('ret' => 2, 'status' => 'Not from recently active IP');
2✔
553
                                    }
554
                                }
555

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

573
                    # If we are currently logged in as an admin, then we can force a log in as anyone else.  This is
574
                    # very useful for debugging.
575
                    $force = $me && $me->isAdmin();
8✔
576

577
                    if ($u->login($password, $force)) {
8✔
578
                        $ret = array('ret' => 0, 'status' => 'Success');
8✔
579
                        $id = $possid;
8✔
580
                    }
581
                }
582
            }
583

584
            if ($id) {
19✔
585
                # Return some more useful info.
586
                $u = User::get($dbhr, $dbhm, $id);
13✔
587
                $ret['user'] = $u->getPublic();
13✔
588
                $ret['persistent'] = Utils::presdef('persistent', $_SESSION, NULL);
13✔
589
                $ret['jwt'] = Session::JWT($dbhr, $dbhm);
13✔
590
            }
591

592
            break;
19✔
593
        }
594

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

599
            if (!$me) {
10✔
600
                $ret = ['ret' => 1, 'status' => 'Not logged in'];
1✔
601
            } else {
602
                if (array_key_exists('marketingconsent', $_REQUEST)) {
10✔
603
                    // Consent to marketing under PECR.
604
                    $consent = Utils::presbool('marketingconsent', $_REQUEST, FALSE);
1✔
605
                    $me->setPrivate('marketingconsent', $consent);
1✔
606
                }
607

608
                $fullname = Utils::presdef('displayname', $_REQUEST, NULL);
10✔
609
                $firstname = Utils::presdef('firstname', $_REQUEST, NULL);
10✔
610
                $lastname = Utils::presdef('lastname', $_REQUEST, NULL);
10✔
611
                $password = Utils::presdef('password', $_REQUEST, NULL);
10✔
612
                $key = Utils::presdef('key', $_REQUEST, NULL);
10✔
613
                $source = Utils::presdef('source', $_REQUEST, NULL);
10✔
614

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

632
                $settings = Utils::presdef('settings', $_REQUEST, NULL);
10✔
633
                if ($settings) {
10✔
634
                    $me->setPrivate('settings', json_encode($settings));
3✔
635

636
                    if (Utils::pres('mylocation', $settings)) {
3✔
637
                        # Save this off as the last known location.
638
                        $me->setPrivate('lastlocation', $settings['mylocation']['id']);
×
639
                    }
640
                }
641

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

659
                $ret = ['ret' => 0, 'status' => 'Success'];
10✔
660

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

669
                if (array_key_exists('phone', $_REQUEST)) {
10✔
670
                    $phone = $_REQUEST['phone'];
1✔
671
                    if ($phone) {
1✔
672
                        $me->addPhone($phone);
1✔
673
                    } else {
674
                        $me->removePhone();
1✔
675
                    }
676
                }
677

678
                if ($key) {
10✔
679
                    $confirmid = $me->confirmEmail($key);
1✔
680

681
                    if (!$confirmid) {
1✔
682
                        $ret = ['ret' => 11, 'status' => 'Confirmation failed'];
1✔
683
                    } else {
684
                        # That might have merged.
685
                        $sessions = $dbhr->preQuery("SELECT * FROM sessions WHERE userid = ?;", [
1✔
686
                            $confirmid
1✔
687
                        ]);
1✔
688

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

701
                            $ret['jwt'] = Session::JWT($dbhr, $dbhm);
1✔
702
                            $ret['persistent'] = Utils::presdef('persistent', $_SESSION, NULL);
1✔
703
                        }
704
                    }
705
                }
706

707
                if ($password) {
10✔
708
                    $me->addLogin(User::LOGIN_NATIVE, $me->getId(), $password);
1✔
709
                }
710

711
                if (array_key_exists('onholidaytill', $_REQUEST)) {
10✔
712
                    $me->setPrivate('onholidaytill', $_REQUEST['onholidaytill']);
1✔
713
                }
714

715
                if (array_key_exists('relevantallowed', $_REQUEST)) {
10✔
716
                    $me->setPrivate('relevantallowed', $_REQUEST['relevantallowed']);
1✔
717
                }
718

719
                if (array_key_exists('newslettersallowed', $_REQUEST)) {
10✔
720
                    $me->setPrivate('newslettersallowed', $_REQUEST['newslettersallowed']);
1✔
721
                }
722

723
                if (array_key_exists('aboutme', $_REQUEST)) {
10✔
724
                    $me->setAboutMe($_REQUEST['aboutme']);
2✔
725

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

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

744
                $simplemail = Utils::presdef('simplemail', $_REQUEST, NULL);
10✔
745

746
                if ($simplemail) {
10✔
747
                    # This is a way to set a bunch of email settings at once.
748
                    $me->setSimpleMail($simplemail);
1✔
749
                }
750

751
                if (array_key_exists('deleted', $_REQUEST)) {
10✔
752
                    # Users who leave have deleted set, but can restore their account.  If they don't, their data
753
                    # will be purged later in the background.
754
                    $me->setPrivate('deleted', NULL);
×
755
                }
756

757
                Session::clearSessionCache();
10✔
758
            }
759
            break;
10✔
760
        }
761

762
        case 'DELETE': {
1✔
763
            # Logout.  Kill all sessions for this user.
764
            $ret = array('ret' => 0, 'status' => 'Success', 'destroyed' => $sessionLogout($dbhr, $dbhm));
1✔
765
            break;
1✔
766
        }
767
    }
768

769
    return($ret);
51✔
770
}
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