User.php 29.1 KB
Newer Older
Evan Prodromou's avatar
Evan Prodromou committed
1
<?php
Evan Prodromou's avatar
Evan Prodromou committed
2
/*
3
 * StatusNet - the distributed open-source microblogging tool
4
 * Copyright (C) 2008, 2009, StatusNet, Inc.
Evan Prodromou's avatar
Evan Prodromou committed
5
 *
6 7 8 9
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
Evan Prodromou's avatar
Evan Prodromou committed
10
 *
11 12 13 14
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
Evan Prodromou's avatar
Evan Prodromou committed
15
 *
16 17 18 19
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

20
if (!defined('STATUSNET') && !defined('LACONICA')) {
Evan Prodromou's avatar
Evan Prodromou committed
21 22
    exit(1);
}
23

Evan Prodromou's avatar
Evan Prodromou committed
24 25 26
/**
 * Table Definition for user
 */
Evan Prodromou's avatar
Evan Prodromou committed
27

28
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
29
require_once 'Validate.php';
Evan Prodromou's avatar
Evan Prodromou committed
30

31
class User extends Memcached_DataObject
Evan Prodromou's avatar
Evan Prodromou committed
32 33 34 35 36 37
{
    ###START_AUTOCODE
    /* the code below is auto generated do not remove the above tag */

    public $__table = 'user';                            // table name
    public $id;                              // int(4)  primary_key not_null
Evan Prodromou's avatar
Evan Prodromou committed
38
    public $nickname;                        // varchar(64)  unique_key
39
    public $password;                        // varchar(255)
Evan Prodromou's avatar
Evan Prodromou committed
40
    public $email;                           // varchar(255)  unique_key
Evan Prodromou's avatar
Evan Prodromou committed
41
    public $incomingemail;                   // varchar(255)  unique_key
42
    public $emailnotifysub;                  // tinyint(1)   default_1
43
    public $emailnotifyfav;                  // tinyint(1)   default_1
44
    public $emailnotifynudge;                // tinyint(1)   default_1
Evan Prodromou's avatar
Evan Prodromou committed
45
    public $emailnotifymsg;                  // tinyint(1)   default_1
46
    public $emailnotifyattn;                 // tinyint(1)   default_1
47
    public $emailmicroid;                    // tinyint(1)   default_1
48 49
    public $language;                        // varchar(50)
    public $timezone;                        // varchar(50)
50
    public $emailpost;                       // tinyint(1)   default_1
51
    public $sms;                             // varchar(64)  unique_key
52 53 54 55
    public $carrier;                         // int(4)
    public $smsnotify;                       // tinyint(1)
    public $smsreplies;                      // tinyint(1)
    public $smsemail;                        // varchar(255)
56
    public $uri;                             // varchar(255)  unique_key
57
    public $autosubscribe;                   // tinyint(1)
58
    public $urlshorteningservice;            // varchar(50)   default_ur1.ca
59
    public $inboxed;                         // tinyint(1)
60 61
    public $design_id;                       // int(4)
    public $viewdesigns;                     // tinyint(1)   default_1
Evan Prodromou's avatar
Evan Prodromou committed
62 63 64 65
    public $created;                         // datetime()   not_null
    public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP

    /* Static get */
66
    function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User',$k,$v); }
Evan Prodromou's avatar
Evan Prodromou committed
67 68 69

    /* the code above is auto generated do not remove the tag below */
    ###END_AUTOCODE
Evan Prodromou's avatar
Evan Prodromou committed
70

71 72 73
    /**
     * @return Profile
     */
74 75
    function getProfile()
    {
76 77 78 79 80
        $profile = Profile::staticGet('id', $this->id);
        if (empty($profile)) {
            throw new UserNoProfileException($this);
        }
        return $profile;
81 82
    }

83 84
    function isSubscribed($other)
    {
85 86
        $profile = $this->getProfile();
        return $profile->isSubscribed($other);
87 88
    }

89
    // 'update' won't write key columns, so we have to do it ourselves.
90

91 92
    function updateKeys(&$orig)
    {
93
        $this->_connect();
94
        $parts = array();
95
        foreach (array('nickname', 'email', 'incomingemail', 'sms', 'carrier', 'smsemail', 'language', 'timezone') as $k) {
96 97 98 99 100
            if (strcmp($this->$k, $orig->$k) != 0) {
                $parts[] = $k . ' = ' . $this->_quote($this->$k);
            }
        }
        if (count($parts) == 0) {
Evan Prodromou's avatar
Evan Prodromou committed
101
            // No changes
102 103 104 105
            return true;
        }
        $toupdate = implode(', ', $parts);

106
        $table = common_database_tablename($this->tableName());
107 108 109 110 111 112 113 114 115 116
        $qry = 'UPDATE ' . $table . ' SET ' . $toupdate .
          ' WHERE id = ' . $this->id;
        $orig->decache();
        $result = $this->query($qry);
        if ($result) {
            $this->encache();
        }
        return $result;
    }

117 118 119 120 121 122 123 124 125 126
    /**
     * Check whether the given nickname is potentially usable, or if it's
     * excluded by any blacklists on this system.
     *
     * WARNING: INPUT IS NOT VALIDATED OR NORMALIZED. NON-NORMALIZED INPUT
     * OR INVALID INPUT MAY LEAD TO FALSE RESULTS.
     *
     * @param string $nickname
     * @return boolean true if clear, false if blacklisted
     */
127
    static function allowed_nickname($nickname)
128
    {
Evan Prodromou's avatar
Evan Prodromou committed
129
        // XXX: should already be validated for size, content, etc.
130
        $blacklist = common_config('nickname', 'blacklist');
131 132 133 134 135 136 137

        //all directory and file names should be blacklisted
        $d = dir(INSTALLDIR);
        while (false !== ($entry = $d->read())) {
            $blacklist[]=$entry;
        }
        $d->close();
138 139 140 141 142 143 144 145 146

        //all top level names in the router should be blacklisted
        $router = Router::get();
        foreach(array_keys($router->m->getPaths()) as $path){
            if(preg_match('/^\/(.*?)[\/\?]/',$path,$matches)){
                $blacklist[]=$matches[1];
            }
        }
        return !in_array($nickname, $blacklist);
147 148
    }

149 150 151 152 153 154
    /**
     * Get the most recent notice posted by this user, if any.
     *
     * @return mixed Notice or null
     */
    function getCurrentNotice()
155
    {
156
        $profile = $this->getProfile();
157
        return $profile->getCurrentNotice();
158 159
    }

160 161
    function getCarrier()
    {
162 163 164
        return Sms_carrier::staticGet('id', $this->carrier);
    }

165 166 167
    /**
     * @deprecated use Subscription::start($sub, $other);
     */
168 169
    function subscribeTo($other)
    {
170
        return Subscription::start($this->getProfile(), $other);
171
    }
172

173 174
    function hasBlocked($other)
    {
175 176
        $profile = $this->getProfile();
        return $profile->hasBlocked($other);
177 178
    }

179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
    /**
     * Register a new user account and profile and set up default subscriptions.
     * If a new-user welcome message is configured, this will be sent.
     *
     * @param array $fields associative array of optional properties
     *              string 'bio'
     *              string 'email'
     *              bool 'email_confirmed' pass true to mark email as pre-confirmed
     *              string 'fullname'
     *              string 'homepage'
     *              string 'location' informal string description of geolocation
     *              float 'lat' decimal latitude for geolocation
     *              float 'lon' decimal longitude for geolocation
     *              int 'location_id' geoname identifier
     *              int 'location_ns' geoname namespace to interpret location_id
     *              string 'nickname' REQUIRED
     *              string 'password' (may be missing for eg OpenID registrations)
     *              string 'code' invite code
     *              ?string 'uri' permalink to notice; defaults to local notice URL
     * @return mixed User object or false on failure
     */
200
    static function register($fields) {
201

Evan Prodromou's avatar
Evan Prodromou committed
202
        // MAGICALLY put fields into current scope
Evan Prodromou's avatar
Evan Prodromou committed
203

204
        extract($fields);
Evan Prodromou's avatar
Evan Prodromou committed
205

206
        $profile = new Profile();
207

208 209 210 211 212 213
        if(!empty($email))
        {
            $email = common_canonical_email($email);
        }

        $nickname = common_canonical_nickname($nickname);
214
        $profile->nickname = $nickname;
215 216
        if(! User::allowed_nickname($nickname)){
            common_log(LOG_WARNING, sprintf("Attempted to register a nickname that is not allowed: %s", $profile->nickname),
217
                       __FILE__);
218
            return false;
219
        }
220
        $profile->profileurl = common_profile_url($nickname);
Evan Prodromou's avatar
Evan Prodromou committed
221

222
        if (!empty($fullname)) {
223 224
            $profile->fullname = $fullname;
        }
225
        if (!empty($homepage)) {
226 227
            $profile->homepage = $homepage;
        }
228
        if (!empty($bio)) {
229 230
            $profile->bio = $bio;
        }
231
        if (!empty($location)) {
232
            $profile->location = $location;
233 234 235 236 237 238 239 240 241

            $loc = Location::fromName($location);

            if (!empty($loc)) {
                $profile->lat         = $loc->lat;
                $profile->lon         = $loc->lon;
                $profile->location_id = $loc->location_id;
                $profile->location_ns = $loc->location_ns;
            }
242
        }
Evan Prodromou's avatar
Evan Prodromou committed
243

244
        $profile->created = common_sql_now();
Evan Prodromou's avatar
Evan Prodromou committed
245

246
        $user = new User();
Evan Prodromou's avatar
Evan Prodromou committed
247

248
        $user->nickname = $nickname;
Evan Prodromou's avatar
Evan Prodromou committed
249

Evan Prodromou's avatar
Evan Prodromou committed
250
        // Users who respond to invite email have proven their ownership of that address
251

252
        if (!empty($code)) {
253 254 255 256 257 258
            $invite = Invitation::staticGet($code);
            if ($invite && $invite->address && $invite->address_type == 'email' && $invite->address == $email) {
                $user->email = $invite->address;
            }
        }

259 260 261 262
        if(isset($email_confirmed) && $email_confirmed) {
            $user->email = $email;
        }

Evan Prodromou's avatar
Evan Prodromou committed
263
        // This flag is ignored but still set to 1
Evan Prodromou's avatar
Evan Prodromou committed
264

Evan Prodromou's avatar
Evan Prodromou committed
265
        $user->inboxed = 1;
266

267 268 269 270 271 272 273 274 275 276 277 278 279
        // Set default-on options here, otherwise they'll be disabled
        // initially for sites using caching, since the initial encache
        // doesn't know about the defaults in the database.
        $user->emailnotifysub = 1;
        $user->emailnotifyfav = 1;
        $user->emailnotifynudge = 1;
        $user->emailnotifymsg = 1;
        $user->emailnotifyattn = 1;
        $user->emailmicroid = 1;
        $user->emailpost = 1;
        $user->jabbermicroid = 1;
        $user->viewdesigns = 1;

280 281
        $user->created = common_sql_now();

282
        if (Event::handle('StartUserRegister', array(&$user, &$profile))) {
283

284
            $profile->query('BEGIN');
285

286
            $id = $profile->insert();
287

288 289 290 291
            if (empty($id)) {
                common_log_db_error($profile, 'INSERT', __FILE__);
                return false;
            }
292

293
            $user->id = $id;
Evan Prodromou's avatar
Evan Prodromou committed
294 295 296 297 298 299 300

            if (!empty($uri)) {
                $user->uri = $uri;
            } else {
                $user->uri = common_user_uri($user);
            }

301 302 303
            if (!empty($password)) { // may not have a password for OpenID users
                $user->password = common_munge_password($password, $id);
            }
304

305
            $result = $user->insert();
306

307 308 309 310
            if (!$result) {
                common_log_db_error($user, 'INSERT', __FILE__);
                return false;
            }
Evan Prodromou's avatar
Evan Prodromou committed
311

312
            // Everyone gets an inbox
313

314
            $inbox = new Inbox();
315

316 317
            $inbox->user_id = $user->id;
            $inbox->notice_ids = '';
318

319
            $result = $inbox->insert();
320 321

            if (!$result) {
322
                common_log_db_error($inbox, 'INSERT', __FILE__);
Evan Prodromou's avatar
TRUE  
Evan Prodromou committed
323
                return false;
324 325
            }

326
            // Everyone is subscribed to themself
Evan Prodromou's avatar
Evan Prodromou committed
327

328 329 330 331
            $subscription = new Subscription();
            $subscription->subscriber = $user->id;
            $subscription->subscribed = $user->id;
            $subscription->created = $user->created;
332

333
            $result = $subscription->insert();
334

335 336 337 338 339 340 341 342 343 344 345 346
            if (!$result) {
                common_log_db_error($subscription, 'INSERT', __FILE__);
                return false;
            }

            if (!empty($email) && !$user->email) {

                $confirm = new Confirm_address();
                $confirm->code = common_confirmation_code(128);
                $confirm->user_id = $user->id;
                $confirm->address = $email;
                $confirm->address_type = 'email';
347

348
                $result = $confirm->insert();
349 350

                if (!$result) {
351
                    common_log_db_error($confirm, 'INSERT', __FILE__);
352 353 354 355
                    return false;
                }
            }

356 357 358
            if (!empty($code) && $user->email) {
                $user->emailChanged();
            }
Evan Prodromou's avatar
Evan Prodromou committed
359

360
            // Default system subscription
Evan Prodromou's avatar
Evan Prodromou committed
361

362
            $defnick = common_config('newuser', 'default');
363

364 365 366 367 368 369
            if (!empty($defnick)) {
                $defuser = User::staticGet('nickname', $defnick);
                if (empty($defuser)) {
                    common_log(LOG_WARNING, sprintf("Default user %s does not exist.", $defnick),
                               __FILE__);
                } else {
370
                    Subscription::start($user, $defuser);
371
                }
372
            }
373

374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
            $profile->query('COMMIT');

            if (!empty($email) && !$user->email) {
                mail_confirm_address($user, $confirm->code, $profile->nickname, $email);
            }

            // Welcome message

            $welcome = common_config('newuser', 'welcome');

            if (!empty($welcome)) {
                $welcomeuser = User::staticGet('nickname', $welcome);
                if (empty($welcomeuser)) {
                    common_log(LOG_WARNING, sprintf("Welcome user %s does not exist.", $defnick),
                               __FILE__);
                } else {
                    $notice = Notice::saveNew($welcomeuser->id,
391 392
                                              // TRANS: Notice given on user registration.
                                              // TRANS: %1$s is the sitename, $2$s is the registering user's nickname.
393 394 395 396 397
                                              sprintf(_('Welcome to %1$s, @%2$s!'),
                                                      common_config('site', 'name'),
                                                      $user->nickname),
                                              'system');
                }
398
            }
399 400

            Event::handle('EndUserRegister', array(&$profile, &$user));
401 402
        }

403 404
        return $user;
    }
405

Evan Prodromou's avatar
Evan Prodromou committed
406
    // Things we do when the email changes
407 408
    function emailChanged()
    {
Evan Prodromou's avatar
Evan Prodromou committed
409

410 411 412
        $invites = new Invitation();
        $invites->address = $this->email;
        $invites->address_type = 'email';
Evan Prodromou's avatar
Evan Prodromou committed
413

414 415 416 417 418 419 420
        if ($invites->find()) {
            while ($invites->fetch()) {
                $other = User::staticGet($invites->user_id);
                subs_subscribe_to($other, $this);
            }
        }
    }
Evan Prodromou's avatar
Evan Prodromou committed
421

422 423
    function hasFave($notice)
    {
Zach Copley's avatar
Zach Copley committed
424 425
        $profile = $this->getProfile();
        return $profile->hasFave($notice);
426
    }
Evan Prodromou's avatar
Evan Prodromou committed
427

428 429
    function mutuallySubscribed($other)
    {
430 431
        $profile = $this->getProfile();
        return $profile->mutuallySubscribed($other);
432
    }
433

Evan Prodromou's avatar
Evan Prodromou committed
434 435 436 437
    function mutuallySubscribedUsers()
    {
        // 3-way join; probably should get cached
        $UT = common_config('db','type')=='pgsql'?'"user"':'user';
438 439 440
        $qry = "SELECT $UT.* " .
          "FROM subscription sub1 JOIN $UT ON sub1.subscribed = $UT.id " .
          "JOIN subscription sub2 ON $UT.id = sub2.subscriber " .
441
          'WHERE sub1.subscriber = %d and sub2.subscribed = %d ' .
442
          "ORDER BY $UT.nickname";
443 444 445 446 447 448
        $user = new User();
        $user->query(sprintf($qry, $this->id, $this->id));

        return $user;
    }

449
    function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
450
    {
451
        return Reply::stream($this->id, $offset, $limit, $since_id, $before_id);
452
    }
453

454
    function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) {
455
        $profile = $this->getProfile();
456
        return $profile->getTaggedNotices($tag, $offset, $limit, $since_id, $before_id);
457 458
    }

459
    function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
Evan Prodromou's avatar
Evan Prodromou committed
460
    {
461
        $profile = $this->getProfile();
462
        return $profile->getNotices($offset, $limit, $since_id, $before_id);
463
    }
464

465
    function favoriteNotices($own=false, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
Evan Prodromou's avatar
Evan Prodromou committed
466
    {
467
        return Fave::stream($this->id, $offset, $limit, $own, $since_id, $max_id);
468
    }
469

470
    function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
Evan Prodromou's avatar
Evan Prodromou committed
471
    {
472
        return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, false);
473 474
    }

475 476 477 478 479
    function noticesWithFriendsThreaded($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
    {
        return Inbox::streamNoticesThreaded($this->id, $offset, $limit, $since_id, $before_id, false);
    }

480
    function noticeInbox($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
481
    {
482
        return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, true);
483
    }
484

485 486 487 488 489
    function noticeInboxThreaded($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
    {
        return Inbox::streamNoticesThreaded($this->id, $offset, $limit, $since_id, $before_id, true);
    }

490
    function friendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
491
    {
492
        return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, false);
493 494
    }

495
    function ownFriendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
496
    {
497
        return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, true);
498 499
    }

Evan Prodromou's avatar
Evan Prodromou committed
500 501
    function blowFavesCache()
    {
502
        $profile = $this->getProfile();
503
        $profile->blowFavesCache();
504
    }
505

Evan Prodromou's avatar
Evan Prodromou committed
506 507
    function getSelfTags()
    {
508 509
        return Profile_tag::getTags($this->id, $this->id);
    }
510

Evan Prodromou's avatar
Evan Prodromou committed
511 512
    function setSelfTags($newtags)
    {
513 514
        return Profile_tag::setTags($this->id, $this->id, $newtags);
    }
Evan Prodromou's avatar
Evan Prodromou committed
515

516 517
    function block($other)
    {
Evan Prodromou's avatar
Evan Prodromou committed
518
        // Add a new block record
Evan Prodromou's avatar
Evan Prodromou committed
519

520 521 522 523 524
        // no blocking (and thus unsubbing from) yourself

        if ($this->id == $other->id) {
            common_log(LOG_WARNING,
                sprintf(
525
                    "Profile ID %d (%s) tried to block themself.",
526 527
                    $this->id,
                    $this->nickname
528 529 530 531 532
                )
            );
            return false;
        }

Evan Prodromou's avatar
Evan Prodromou committed
533 534
        $block = new Profile_block();

Evan Prodromou's avatar
Evan Prodromou committed
535
        // Begin a transaction
Evan Prodromou's avatar
Evan Prodromou committed
536 537 538 539 540 541 542 543 544 545 546 547 548

        $block->query('BEGIN');

        $block->blocker = $this->id;
        $block->blocked = $other->id;

        $result = $block->insert();

        if (!$result) {
            common_log_db_error($block, 'INSERT', __FILE__);
            return false;
        }

549 550 551 552
        $self = $this->getProfile();
        if (Subscription::exists($other, $self)) {
            Subscription::cancel($other, $self);
        }
553 554 555
        if (Subscription::exists($self, $other)) {
            Subscription::cancel($self, $other);
        }
Evan Prodromou's avatar
Evan Prodromou committed
556 557 558 559 560 561

        $block->query('COMMIT');

        return true;
    }

562 563
    function unblock($other)
    {
Evan Prodromou's avatar
Evan Prodromou committed
564
        // Get the block record
Evan Prodromou's avatar
Evan Prodromou committed
565

566
        $block = Profile_block::get($this->id, $other->id);
Evan Prodromou's avatar
Evan Prodromou committed
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581

        if (!$block) {
            return false;
        }

        $result = $block->delete();

        if (!$result) {
            common_log_db_error($block, 'DELETE', __FILE__);
            return false;
        }

        return true;
    }

582 583
    function isMember($group)
    {
584 585
        $profile = $this->getProfile();
        return $profile->isMember($group);
586 587 588 589
    }

    function isAdmin($group)
    {
590 591
        $profile = $this->getProfile();
        return $profile->isAdmin($group);
592
    }
593

Evan Prodromou's avatar
Evan Prodromou committed
594
    function getGroups($offset=0, $limit=null)
595
    {
596 597
        $profile = $this->getProfile();
        return $profile->getGroups($offset, $limit);
Evan Prodromou's avatar
Evan Prodromou committed
598 599
    }

600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623
    /**
     * Request to join the given group.
     * May throw exceptions on failure.
     *
     * @param User_group $group
     * @return Group_member
     */
    function joinGroup(User_group $group)
    {
        $profile = $this->getProfile();
        return $profile->joinGroup($group);
    }

    /**
     * Leave a group that this user is a member of.
     *
     * @param User_group $group
     */
    function leaveGroup(User_group $group)
    {
        $profile = $this->getProfile();
        return $profile->leaveGroup($group);
    }

Evan Prodromou's avatar
Evan Prodromou committed
624 625
    function getSubscriptions($offset=0, $limit=null)
    {
626 627
        $profile = $this->getProfile();
        return $profile->getSubscriptions($offset, $limit);
Evan Prodromou's avatar
Evan Prodromou committed
628 629 630 631
    }

    function getSubscribers($offset=0, $limit=null)
    {
632 633
        $profile = $this->getProfile();
        return $profile->getSubscribers($offset, $limit);
634
    }
635 636 637 638 639 640 641 642 643 644

    function getTaggedSubscribers($tag, $offset=0, $limit=null)
    {
        $qry =
          'SELECT profile.* ' .
          'FROM profile JOIN subscription ' .
          'ON profile.id = subscription.subscriber ' .
          'JOIN profile_tag ON (profile_tag.tagged = subscription.subscriber ' .
          'AND profile_tag.tagger = subscription.subscribed) ' .
          'WHERE subscription.subscribed = %d ' .
645
          "AND profile_tag.tag = '%s' " .
646 647 648 649
          'AND subscription.subscribed != subscription.subscriber ' .
          'ORDER BY subscription.created DESC ';

        if ($offset) {
650
            $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668
        }

        $profile = new Profile();

        $cnt = $profile->query(sprintf($qry, $this->id, $tag));

        return $profile;
    }

    function getTaggedSubscriptions($tag, $offset=0, $limit=null)
    {
        $qry =
          'SELECT profile.* ' .
          'FROM profile JOIN subscription ' .
          'ON profile.id = subscription.subscribed ' .
          'JOIN profile_tag on (profile_tag.tagged = subscription.subscribed ' .
          'AND profile_tag.tagger = subscription.subscriber) ' .
          'WHERE subscription.subscriber = %d ' .
669
          "AND profile_tag.tag = '%s' " .
670 671 672
          'AND subscription.subscribed != subscription.subscriber ' .
          'ORDER BY subscription.created DESC ';

673
        $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
674 675 676 677 678 679 680

        $profile = new Profile();

        $profile->query(sprintf($qry, $this->id, $tag));

        return $profile;
    }
681

Evan Prodromou's avatar
Evan Prodromou committed
682 683 684 685
    function getDesign()
    {
        return Design::staticGet('id', $this->design_id);
    }
686

Evan Prodromou's avatar
Evan Prodromou committed
687 688
    function hasRight($right)
    {
689 690
        $profile = $this->getProfile();
        return $profile->hasRight($right);
Evan Prodromou's avatar
Evan Prodromou committed
691
    }
692 693 694

    function delete()
    {
695 696 697 698 699 700
        try {
            $profile = $this->getProfile();
            $profile->delete();
        } catch (UserNoProfileException $unp) {
            common_log(LOG_INFO, "User {$this->nickname} has no profile; continuing deletion.");
        }
701 702 703 704 705 706 707

        $related = array('Fave',
                         'Confirm_address',
                         'Remember_me',
                         'Foreign_link',
                         'Invitation',
                         );
708

709
        Event::handle('UserDeleteRelated', array($this, &$related));
710 711 712 713 714 715 716 717

        foreach ($related as $cls) {
            $inst = new $cls();
            $inst->user_id = $this->id;
            $inst->delete();
        }

        $this->_deleteTags();
Evan Prodromou's avatar
Evan Prodromou committed
718
        $this->_deleteBlocks();
719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736

        parent::delete();
    }

    function _deleteTags()
    {
        $tag = new Profile_tag();
        $tag->tagger = $this->id;
        $tag->delete();
    }

    function _deleteBlocks()
    {
        $block = new Profile_block();
        $block->blocker = $this->id;
        $block->delete();
        // XXX delete group block? Reset blocker?
    }
737

738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755
    function hasRole($name)
    {
        $profile = $this->getProfile();
        return $profile->hasRole($name);
    }

    function grantRole($name)
    {
        $profile = $this->getProfile();
        return $profile->grantRole($name);
    }

    function revokeRole($name)
    {
        $profile = $this->getProfile();
        return $profile->revokeRole($name);
    }

756 757
    function isSandboxed()
    {
758 759
        $profile = $this->getProfile();
        return $profile->isSandboxed();
760 761 762 763
    }

    function isSilenced()
    {
764 765
        $profile = $this->getProfile();
        return $profile->isSilenced();
766
    }
767 768 769

    function repeatedByMe($offset=0, $limit=20, $since_id=null, $max_id=null)
    {
770
        $stream = new RepeatedByMeNoticeStream($this);
771
        return $stream->getNotices($offset, $limit, $since_id, $max_id);
772 773
    }

774 775 776

    function repeatsOfMe($offset=0, $limit=20, $since_id=null, $max_id=null)
    {
777
        $stream = new RepeatsOfMeNoticeStream($this);
778

779
        return $stream->getNotices($offset, $limit, $since_id, $max_id);
780 781
    }

782 783 784

    function repeatedToMe($offset=0, $limit=20, $since_id=null, $max_id=null)
    {
785
        throw new Exception("Not implemented since inbox change.");
786
    }
787 788 789

    function shareLocation()
    {
790
        $cfg = common_config('location', 'share');
791

792 793 794 795 796 797
        if ($cfg == 'always') {
            return true;
        } else if ($cfg == 'never') {
            return false;
        } else { // user
            $share = true;
798

799
            $prefs = User_location_prefs::staticGet('user_id', $this->id);
800

801 802 803 804 805 806 807 808 809
            if (empty($prefs)) {
                $share = common_config('location', 'sharedefault');
            } else {
                $share = $prefs->share_location;
                $prefs->free();
            }

            return $share;
        }
810
    }
811 812 813 814 815 816 817 818 819 820 821 822 823

    static function siteOwner()
    {
        $owner = self::cacheGet('user:site_owner');

        if ($owner === false) { // cache miss

            $pr = new Profile_role();

            $pr->role = Profile_role::OWNER;

            $pr->orderBy('created');

Evan Prodromou's avatar
Evan Prodromou committed
824
            $pr->limit(1);
825

Evan Prodromou's avatar
Evan Prodromou committed
826
            if ($pr->find(true)) {
827 828 829 830 831 832 833 834 835 836
                $owner = User::staticGet('id', $pr->profile_id);
            } else {
                $owner = null;
            }

            self::cacheSet('user:site_owner', $owner);
        }

        return $owner;
    }
837 838 839 840 841 842 843 844 845 846 847 848 849 850

    /**
     * Pull the primary site account to use in single-user mode.
     * If a valid user nickname is listed in 'singleuser':'nickname'
     * in the config, this will be used; otherwise the site owner
     * account is taken by default.
     *
     * @return User
     * @throws ServerException if no valid single user account is present
     * @throws ServerException if called when not in single-user mode
     */
    static function singleUser()
    {
        if (common_config('singleuser', 'enabled')) {
851 852 853

            $user = null;

854
            $nickname = common_config('singleuser', 'nickname');
855 856

            if (!empty($nickname)) {
857
                $user = User::staticGet('nickname', $nickname);
858 859 860 861 862 863
            }

            // if there was no nickname or no user by that nickname,
            // try the site owner.

            if (empty($user)) {
864 865
                $user = User::siteOwner();
            }
866 867

            if (!empty($user)) {
868 869
                return $user;
            } else {
870 871
                // TRANS: Server exception.
                throw new ServerException(_('No single user defined for single-user mode.'));
872 873
            }
        } else {
874
            // TRANS: Server exception.
875 876 877
            throw new ServerException(_('Single-user mode code called when not enabled.'));
        }
    }
878

879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909
    /**
     * This is kind of a hack for using external setup code that's trying to
     * build single-user sites.
     *
     * Will still return a username if the config singleuser/nickname is set
     * even if the account doesn't exist, which normally indicates that the
     * site is horribly misconfigured.
     *
     * At the moment, we need to let it through so that router setup can
     * complete, otherwise we won't be able to create the account.
     *
     * This will be easier when we can more easily create the account and
     * *then* switch the site to 1user mode without jumping through hoops.
     *
     * @return string
     * @throws ServerException if no valid single user account is present
     * @throws ServerException if called when not in single-user mode
     */
    static function singleUserNickname()
    {
        try {
            $user = User::singleUser();
            return $user->nickname;
        } catch (Exception $e) {
            if (common_config('singleuser', 'enabled') && common_config('singleuser', 'nickname')) {
                common_log(LOG_WARN, "Warning: code attempting to pull single-user nickname when the account does not exist. If this is not setup time, this is probably a bug.");
                return common_config('singleuser', 'nickname');
            }
            throw $e;
        }
    }
910

911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928
    /**
     * Find and shorten links in the given text using this user's URL shortening
     * settings.
     *
     * By default, links will be left untouched if the text is shorter than the
     * configured maximum notice length. Pass true for the $always parameter
     * to force all links to be shortened regardless.
     *
     * Side effects: may save file and file_redirection records for referenced URLs.
     *
     * @param string $text
     * @param boolean $always
     * @return string
     */
    public function shortenLinks($text, $always=false)
    {
        return common_shorten_links($text, $always, $this);
    }
929 930

    /*
931
     * Get a list of OAuth client applications that have access to this
932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958
     * user's account.
     */
    function getConnectedApps($offset = 0, $limit = null)
    {
        $qry =
          'SELECT u.* ' .
          'FROM oauth_application_user u, oauth_application a ' .
          'WHERE u.profile_id = %d ' .
          'AND a.id = u.application_id ' .
          'AND u.access_type > 0 ' .
          'ORDER BY u.created DESC ';

        if ($offset > 0) {
            if (common_config('db','type') == 'pgsql') {
                $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
            } else {
                $qry .= ' LIMIT ' . $offset . ', ' . $limit;
            }
        }

        $apps = new Oauth_application_user();

        $cnt = $apps->query(sprintf($qry, $this->id));

        return $apps;
    }

Evan Prodromou's avatar
Evan Prodromou committed
959
}