gnu-social/classes/Profile.php
Zach Copley 6f0bd73e6c Squashed commit of the following:
Move OMB to a plugin

commit 75d21f00246bcc56d7f854936be1e28395e079a2
Merge: cea0199 d594d07
Author: Zach Copley <zach@status.net>
Date:   Fri Jul 15 11:16:54 2011 -0700

    Merge branch 'kill-omb2' of gitorious.org:~zcopley/statusnet/zcopleys-clone into kill-omb2

    * 'kill-omb2' of gitorious.org:~zcopley/statusnet/zcopleys-clone:
      Fix paths
      Oops, I left out the ability to authorize a token in ApiStatusNetOAuthDataStore
      Some odds and ends
      Remove omb stuff from queuemanager defaults
      Add check to make sure we're not untagging an OMB profile to OMB plugin
      Move some more subscription stuff and peopletag checks to OMB plugin
      Move some OMB-specific unsubscribe stuff to OMB plugin
      Finish removing libomb from core extlibs
      Fix more conflicts
      Fix queuing/queuehandling
      Move some stuff around; fix references
      Fix conflicts
      Move OMB-specific files to OMB plugin
      Move some stuff around; fix references
      Add OMB plugin README and rm references to OMB in mail StatusNet README
      Update paths
      Fix define
      Basic plugin finished
      Move OMB-specific files to OMB plugin
      Remove OMB stuff from router

commit cea019967f343042ebaea14b7bbb0d54289bcc1a
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 13 14:38:40 2011 -0700

    Fix paths

commit d412aa3c0ea0e21e65a72a16c7b9edd64ff373e1
Author: Zach Copley <zach@status.net>
Date:   Tue Jul 12 18:17:06 2011 -0700

    Oops, I left out the ability to authorize a token in ApiStatusNetOAuthDataStore

commit b459c9f10ac283d6e774ef13f3293fc8a6948143
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 6 19:02:08 2011 -0700

    Some odds and ends

commit 895cfbfce58ffb3a05beebf48a90c549e00f1cce
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 6 19:01:23 2011 -0700

    Remove omb stuff from queuemanager defaults

commit b41b9e994f291ff83afb2460d9b37aee8ec1ec2b
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 6 18:46:44 2011 -0700

    Add check to make sure we're not untagging an OMB profile to OMB plugin

commit 94374d26ddd428dac8e4cd4541fd56db748c248b
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 6 18:34:20 2011 -0700

    Move some more subscription stuff and peopletag checks to OMB plugin

commit b91043b7820d5cd8b0ba4e9ee2a9d03c99248f11
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 6 16:37:25 2011 -0700

    Move some OMB-specific unsubscribe stuff to OMB plugin

commit d9430fe52975d9497b4a0d3d54da35b222e207ad
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 6 15:26:30 2011 -0700

    Finish removing libomb from core extlibs

commit bb6257eb85cc7ba392e91468c01503f51faeb989
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 13 12:47:32 2011 -0700

    Fix more conflicts

commit 3c760d0a4b4a083ae5fca2530d22aad5f4a9fdae
Author: Zach Copley <zach@status.net>
Date:   Tue Jul 5 15:49:22 2011 -0700

    Fix queuing/queuehandling

commit ed635fa0c20e150673709c04ecc7f285d12e0ce2
Author: Zach Copley <zach@status.net>
Date:   Tue Jul 5 15:29:35 2011 -0700

    Move some stuff around; fix references

commit cbc553a147941cad16e205a6b66ab4b32a5e3d3d
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 13 12:46:05 2011 -0700

    Fix conflicts

commit 5d77c81f75b57f5d5357d6b46d503650a4b3225d
Author: Zach Copley <zach@status.net>
Date:   Thu Jun 30 19:10:38 2011 -0700

    Move OMB-specific files to OMB plugin

commit 2ed051dbce0ce9b44723b14922026849c39ed603
Author: Zach Copley <zach@status.net>
Date:   Tue Jul 5 15:29:35 2011 -0700

    Move some stuff around; fix references

commit 8809b5e35b1aacb67d70ae3e55a43003b6f591b7
Author: Zach Copley <zach@status.net>
Date:   Tue Jul 5 15:28:59 2011 -0700

    Add OMB plugin README and rm references to OMB in mail StatusNet README

commit 35ced4067c1915baca0b3e184f9533a91a951d2d
Author: Zach Copley <zach@status.net>
Date:   Thu Jun 30 23:50:09 2011 -0700

    Update paths

commit 0ee5bafbce95fc9b8db98c1e828d33d26d08bc73
Author: Zach Copley <zach@status.net>
Date:   Thu Jun 30 23:38:03 2011 -0700

    Fix define

commit e309dd22ffb9087d7fcf9180ede4f531dbd88c3c
Author: Zach Copley <zach@status.net>
Date:   Thu Jun 30 23:30:43 2011 -0700

    Basic plugin finished

commit 00f1e930f27e080b04d1e82952f7886c84e01d97
Author: Zach Copley <zach@status.net>
Date:   Thu Jun 30 19:10:38 2011 -0700

    Move OMB-specific files to OMB plugin

commit 39dcd031a79b49da0b4fe25f1594d2e406b5eb65
Author: Zach Copley <zach@status.net>
Date:   Thu Jun 30 19:10:01 2011 -0700

    Remove OMB stuff from router

commit d594d071be1ec42518dd5465db61e01e7e8ec036
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 13 14:38:40 2011 -0700

    Fix paths

commit 48c1064b4b50e89cf51d2cab388f708f60601247
Author: Zach Copley <zach@status.net>
Date:   Tue Jul 12 18:17:06 2011 -0700

    Oops, I left out the ability to authorize a token in ApiStatusNetOAuthDataStore

commit 1e1168978f38c31dbf0206b3493b2b6dcbe61589
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 6 19:02:08 2011 -0700

    Some odds and ends

commit ac43af2b497d8b9286c49a9469a1dff950e41650
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 6 19:01:23 2011 -0700

    Remove omb stuff from queuemanager defaults

commit 2471af2f8800515a3db544b3a186a18f3e8a43af
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 6 18:46:44 2011 -0700

    Add check to make sure we're not untagging an OMB profile to OMB plugin

commit df974646459ac6d5d97a40d008f1aab66f998226
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 6 18:34:20 2011 -0700

    Move some more subscription stuff and peopletag checks to OMB plugin

commit 8a1427b759e791c14a7a7a22128ba05f0b4b6d12
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 6 16:37:25 2011 -0700

    Move some OMB-specific unsubscribe stuff to OMB plugin

commit bd24220dbb5170af22ea0dea8a3062e6d1aeb6a2
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 6 15:26:30 2011 -0700

    Finish removing libomb from core extlibs

commit 4c3c6f1fabb0f2c92635ccc5e8f38db2293f5456
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 13 12:47:32 2011 -0700

    Fix more conflicts

commit db44deefd731a412685c5669c4c6fa69833de922
Author: Zach Copley <zach@status.net>
Date:   Tue Jul 5 15:49:22 2011 -0700

    Fix queuing/queuehandling

commit ea2d84d2f3d518950d3aa1956ddc8f3a25ca55f3
Author: Zach Copley <zach@status.net>
Date:   Tue Jul 5 15:29:35 2011 -0700

    Move some stuff around; fix references

commit 8ac3e010444b41bd9a78766f5e37e49dff023b45
Author: Zach Copley <zach@status.net>
Date:   Wed Jul 13 12:46:05 2011 -0700

    Fix conflicts

commit 0aad6e10e3637b3189a87b42c24c1d6de1b346bc
Author: Zach Copley <zach@status.net>
Date:   Thu Jun 30 19:10:38 2011 -0700

    Move OMB-specific files to OMB plugin

commit d982d7076c5cb28c7b8e4b1dde8d07d7e58e278f
Author: Zach Copley <zach@status.net>
Date:   Tue Jul 5 15:29:35 2011 -0700

    Move some stuff around; fix references

commit 4b9d39c93562ff4c45c37c940013e8b78197dec1
Author: Zach Copley <zach@status.net>
Date:   Tue Jul 5 15:28:59 2011 -0700

    Add OMB plugin README and rm references to OMB in mail StatusNet README

commit dab0fb6647a85e6835298496d7127a398b6b9293
Author: Zach Copley <zach@status.net>
Date:   Thu Jun 30 23:50:09 2011 -0700

    Update paths

commit 2cb73dac8ad971f1545dcf6ba57746c777e232ef
Author: Zach Copley <zach@status.net>
Date:   Thu Jun 30 23:38:03 2011 -0700

    Fix define

commit 6f226b18a059f175b1bdd3abcb8cb95eedc22ee7
Author: Zach Copley <zach@status.net>
Date:   Thu Jun 30 23:30:43 2011 -0700

    Basic plugin finished

commit 7be304beaa0f39755c3978e0b852fde768950da4
Author: Zach Copley <zach@status.net>
Date:   Thu Jun 30 19:10:38 2011 -0700

    Move OMB-specific files to OMB plugin

commit 5b30da01cfa2802d6e7a4a4a4f39b8043c54f472
Author: Zach Copley <zach@status.net>
Date:   Thu Jun 30 19:10:01 2011 -0700

    Remove OMB stuff from router
2011-07-15 12:13:57 -07:00

1364 lines
38 KiB
PHP

<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2008-2011, StatusNet, Inc.
*
* 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.
*
* 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.
*
* 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/>.
*/
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
/**
* Table Definition for profile
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Profile extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'profile'; // table name
public $id; // int(4) primary_key not_null
public $nickname; // varchar(64) multiple_key not_null
public $fullname; // varchar(255) multiple_key
public $profileurl; // varchar(255)
public $homepage; // varchar(255) multiple_key
public $bio; // text() multiple_key
public $location; // varchar(255) multiple_key
public $lat; // decimal(10,7)
public $lon; // decimal(10,7)
public $location_id; // int(4)
public $location_ns; // int(4)
public $created; // datetime() not_null
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
/* Static get */
function staticGet($k,$v=NULL) {
return Memcached_DataObject::staticGet('Profile',$k,$v);
}
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
protected $_user = -1; // Uninitialized value distinct from null
function getUser()
{
if (is_int($this->_user) && $this->_user == -1) {
$this->_user = User::staticGet('id', $this->id);
}
return $this->_user;
}
function getAvatar($width, $height=null)
{
if (is_null($height)) {
$height = $width;
}
$avatar = null;
if (Event::handle('StartProfileGetAvatar', array($this, $width, &$avatar))) {
$avatar = Avatar::pkeyGet(array('profile_id' => $this->id,
'width' => $width,
'height' => $height));
Event::handle('EndProfileGetAvatar', array($this, $width, &$avatar));
}
return $avatar;
}
function getOriginalAvatar()
{
$avatar = DB_DataObject::factory('avatar');
$avatar->profile_id = $this->id;
$avatar->original = true;
if ($avatar->find(true)) {
return $avatar;
} else {
return null;
}
}
function setOriginal($filename)
{
$imagefile = new ImageFile($this->id, Avatar::path($filename));
$avatar = new Avatar();
$avatar->profile_id = $this->id;
$avatar->width = $imagefile->width;
$avatar->height = $imagefile->height;
$avatar->mediatype = image_type_to_mime_type($imagefile->type);
$avatar->filename = $filename;
$avatar->original = true;
$avatar->url = Avatar::url($filename);
$avatar->created = DB_DataObject_Cast::dateTime(); # current time
// XXX: start a transaction here
if (!$this->delete_avatars() || !$avatar->insert()) {
@unlink(Avatar::path($filename));
return null;
}
foreach (array(AVATAR_PROFILE_SIZE, AVATAR_STREAM_SIZE, AVATAR_MINI_SIZE) as $size) {
// We don't do a scaled one if original is our scaled size
if (!($avatar->width == $size && $avatar->height == $size)) {
$scaled_filename = $imagefile->resize($size);
//$scaled = DB_DataObject::factory('avatar');
$scaled = new Avatar();
$scaled->profile_id = $this->id;
$scaled->width = $size;
$scaled->height = $size;
$scaled->original = false;
$scaled->mediatype = image_type_to_mime_type($imagefile->type);
$scaled->filename = $scaled_filename;
$scaled->url = Avatar::url($scaled_filename);
$scaled->created = DB_DataObject_Cast::dateTime(); # current time
if (!$scaled->insert()) {
return null;
}
}
}
return $avatar;
}
/**
* Delete attached avatars for this user from the database and filesystem.
* This should be used instead of a batch delete() to ensure that files
* get removed correctly.
*
* @param boolean $original true to delete only the original-size file
* @return <type>
*/
function delete_avatars($original=true)
{
$avatar = new Avatar();
$avatar->profile_id = $this->id;
$avatar->find();
while ($avatar->fetch()) {
if ($avatar->original) {
if ($original == false) {
continue;
}
}
$avatar->delete();
}
return true;
}
/**
* Gets either the full name (if filled) or the nickname.
*
* @return string
*/
function getBestName()
{
return ($this->fullname) ? $this->fullname : $this->nickname;
}
/**
* Gets the full name (if filled) with nickname as a parenthetical, or the nickname alone
* if no fullname is provided.
*
* @return string
*/
function getFancyName()
{
if ($this->fullname) {
// TRANS: Full name of a profile or group (%1$s) followed by nickname (%2$s) in parentheses.
return sprintf(_m('FANCYNAME','%1$s (%2$s)'), $this->fullname, $this->nickname);
} else {
return $this->nickname;
}
}
/**
* Get the most recent notice posted by this user, if any.
*
* @return mixed Notice or null
*/
function getCurrentNotice()
{
$notice = $this->getNotices(0, 1);
if ($notice->fetch()) {
if ($notice instanceof ArrayWrapper) {
// hack for things trying to work with single notices
return $notice->_items[0];
}
return $notice;
} else {
return null;
}
}
function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
{
$stream = new TaggedProfileNoticeStream($this, $tag);
return $stream->getNotices($offset, $limit, $since_id, $max_id);
}
function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
{
$stream = new ProfileNoticeStream($this);
return $stream->getNotices($offset, $limit, $since_id, $max_id);
}
function isMember($group)
{
$gm = Group_member::pkeyGet(array('profile_id' => $this->id,
'group_id' => $group->id));
return (!empty($gm));
}
function isAdmin($group)
{
$gm = Group_member::pkeyGet(array('profile_id' => $this->id,
'group_id' => $group->id));
return (!empty($gm) && $gm->is_admin);
}
function isPendingMember($group)
{
$request = Group_join_queue::pkeyGet(array('profile_id' => $this->id,
'group_id' => $group->id));
return !empty($request);
}
function getGroups($offset=0, $limit=PROFILES_PER_PAGE)
{
$ids = array();
$keypart = sprintf('profile:groups:%d', $this->id);
$idstring = self::cacheGet($keypart);
if ($idstring !== false) {
$ids = explode(',', $idstring);
} else {
$gm = new Group_member();
$gm->profile_id = $this->id;
if ($gm->find()) {
while ($gm->fetch()) {
$ids[] = $gm->group_id;
}
}
self::cacheSet($keypart, implode(',', $ids));
}
$groups = array();
foreach ($ids as $id) {
$group = User_group::staticGet('id', $id);
if (!empty($group)) {
$groups[] = $group;
}
}
return new ArrayWrapper($groups);
}
function isTagged($peopletag)
{
$tag = Profile_tag::pkeyGet(array('tagger' => $peopletag->tagger,
'tagged' => $this->id,
'tag' => $peopletag->tag));
return !empty($tag);
}
function canTag($tagged)
{
if (empty($tagged)) {
return false;
}
if ($tagged->id == $this->id) {
return true;
}
$all = common_config('peopletag', 'allow_tagging', 'all');
$local = common_config('peopletag', 'allow_tagging', 'local');
$remote = common_config('peopletag', 'allow_tagging', 'remote');
$subs = common_config('peopletag', 'allow_tagging', 'subs');
if ($all) {
return true;
}
$tagged_user = $tagged->getUser();
if (!empty($tagged_user)) {
if ($local) {
return true;
}
} else if ($subs) {
return (Subscription::exists($this, $tagged) ||
Subscription::exists($tagged, $this));
} else if ($remote) {
return true;
}
return false;
}
function getLists($auth_user, $offset=0, $limit=null, $since_id=0, $max_id=0)
{
$ids = array();
$keypart = sprintf('profile:lists:%d', $this->id);
$idstr = self::cacheGet($keypart);
if ($idstr !== false) {
$ids = explode(',', $idstr);
} else {
$list = new Profile_list();
$list->selectAdd();
$list->selectAdd('id');
$list->tagger = $this->id;
$list->selectAdd('id as "cursor"');
if ($since_id>0) {
$list->whereAdd('id > '.$since_id);
}
if ($max_id>0) {
$list->whereAdd('id <= '.$max_id);
}
if($offset>=0 && !is_null($limit)) {
$list->limit($offset, $limit);
}
$list->orderBy('id DESC');
if ($list->find()) {
while ($list->fetch()) {
$ids[] = $list->id;
}
}
self::cacheSet($keypart, implode(',', $ids));
}
$showPrivate = (($auth_user instanceof User ||
$auth_user instanceof Profile) &&
$auth_user->id === $this->id);
$lists = array();
foreach ($ids as $id) {
$list = Profile_list::staticGet('id', $id);
if (!empty($list) &&
($showPrivate || !$list->private)) {
if (!isset($list->cursor)) {
$list->cursor = $list->id;
}
$lists[] = $list;
}
}
return new ArrayWrapper($lists);
}
function getOtherTags($auth_user=null, $offset=0, $limit=null, $since_id=0, $max_id=0)
{
$lists = new Profile_list();
$tags = new Profile_tag();
$tags->tagged = $this->id;
$lists->joinAdd($tags);
#@fixme: postgres (round(date_part('epoch', my_date)))
$lists->selectAdd('unix_timestamp(profile_tag.modified) as "cursor"');
if ($auth_user instanceof User || $auth_user instanceof Profile) {
$lists->whereAdd('( ( profile_list.private = false ) ' .
'OR ( profile_list.tagger = ' . $auth_user->id . ' AND ' .
'profile_list.private = true ) )');
} else {
$lists->private = false;
}
if ($since_id>0) {
$lists->whereAdd('cursor > '.$since_id);
}
if ($max_id>0) {
$lists->whereAdd('cursor <= '.$max_id);
}
if($offset>=0 && !is_null($limit)) {
$lists->limit($offset, $limit);
}
$lists->orderBy('profile_tag.modified DESC');
$lists->find();
return $lists;
}
function getPrivateTags($offset=0, $limit=null, $since_id=0, $max_id=0)
{
$tags = new Profile_list();
$tags->private = true;
$tags->tagger = $this->id;
if ($since_id>0) {
$tags->whereAdd('id > '.$since_id);
}
if ($max_id>0) {
$tags->whereAdd('id <= '.$max_id);
}
if($offset>=0 && !is_null($limit)) {
$tags->limit($offset, $limit);
}
$tags->orderBy('id DESC');
$tags->find();
return $tags;
}
function hasLocalTags()
{
$tags = new Profile_tag();
$tags->joinAdd(array('tagger', 'user:id'));
$tags->whereAdd('tagged = '.$this->id);
$tags->whereAdd('tagger != '.$this->id);
$tags->limit(0, 1);
$tags->fetch();
return ($tags->N == 0) ? false : true;
}
function getTagSubscriptions($offset=0, $limit=null, $since_id=0, $max_id=0)
{
$lists = new Profile_list();
$subs = new Profile_tag_subscription();
$lists->joinAdd($subs);
#@fixme: postgres (round(date_part('epoch', my_date)))
$lists->selectAdd('unix_timestamp(profile_tag_subscription.created) as "cursor"');
$lists->whereAdd('profile_tag_subscription.profile_id = '.$this->id);
if ($since_id>0) {
$lists->whereAdd('cursor > '.$since_id);
}
if ($max_id>0) {
$lists->whereAdd('cursor <= '.$max_id);
}
if($offset>=0 && !is_null($limit)) {
$lists->limit($offset, $limit);
}
$lists->orderBy('"cursor" DESC');
$lists->find();
return $lists;
}
/**
* Request to join the given group.
* May throw exceptions on failure.
*
* @param User_group $group
* @return mixed: Group_member on success, Group_join_queue if pending approval, null on some cancels?
*/
function joinGroup(User_group $group)
{
$join = null;
if ($group->join_policy == User_group::JOIN_POLICY_MODERATE) {
$join = Group_join_queue::saveNew($this, $group);
} else {
if (Event::handle('StartJoinGroup', array($group, $this))) {
$join = Group_member::join($group->id, $this->id);
self::blow('profile:groups:%d', $this->id);
Event::handle('EndJoinGroup', array($group, $this));
}
}
if ($join) {
// Send any applicable notifications...
$join->notify();
}
return $join;
}
/**
* Leave a group that this profile is a member of.
*
* @param User_group $group
*/
function leaveGroup(User_group $group)
{
if (Event::handle('StartLeaveGroup', array($group, $this))) {
Group_member::leave($group->id, $this->id);
self::blow('profile:groups:%d', $this->id);
Event::handle('EndLeaveGroup', array($group, $this));
}
}
function avatarUrl($size=AVATAR_PROFILE_SIZE)
{
$avatar = $this->getAvatar($size);
if ($avatar) {
return $avatar->displayUrl();
} else {
return Avatar::defaultImage($size);
}
}
function getSubscriptions($offset=0, $limit=null)
{
$subs = Subscription::bySubscriber($this->id,
$offset,
$limit);
$profiles = array();
while ($subs->fetch()) {
$profile = Profile::staticGet($subs->subscribed);
if ($profile) {
$profiles[] = $profile;
}
}
return new ArrayWrapper($profiles);
}
function getSubscribers($offset=0, $limit=null)
{
$subs = Subscription::bySubscribed($this->id,
$offset,
$limit);
$profiles = array();
while ($subs->fetch()) {
$profile = Profile::staticGet($subs->subscriber);
if ($profile) {
$profiles[] = $profile;
}
}
return new ArrayWrapper($profiles);
}
function getTaggedSubscribers($tag)
{
$qry =
'SELECT profile.* ' .
'FROM profile JOIN (subscription, profile_tag, profile_list) ' .
'ON profile.id = subscription.subscriber ' .
'AND profile.id = profile_tag.tagged ' .
'AND profile_tag.tagger = profile_list.tagger AND profile_tag.tag = profile_list.tag ' .
'WHERE subscription.subscribed = %d ' .
'AND subscription.subscribed != subscription.subscriber ' .
'AND profile_tag.tagger = %d AND profile_tag.tag = "%s" ' .
'AND profile_list.private = false ' .
'ORDER BY subscription.created DESC';
$profile = new Profile();
$tagged = array();
$cnt = $profile->query(sprintf($qry, $this->id, $this->id, $tag));
while ($profile->fetch()) {
$tagged[] = clone($profile);
}
return $tagged;
}
/**
* Get pending subscribers, who have not yet been approved.
*
* @param int $offset
* @param int $limit
* @return Profile
*/
function getRequests($offset=0, $limit=null)
{
$qry =
'SELECT profile.* ' .
'FROM profile JOIN subscription_queue '.
'ON profile.id = subscription_queue.subscriber ' .
'WHERE subscription_queue.subscribed = %d ' .
'ORDER BY subscription_queue.created DESC ';
if ($limit != null) {
if (common_config('db','type') == 'pgsql') {
$qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
} else {
$qry .= ' LIMIT ' . $offset . ', ' . $limit;
}
}
$members = new Profile();
$members->query(sprintf($qry, $this->id));
return $members;
}
function subscriptionCount()
{
$c = Cache::instance();
if (!empty($c)) {
$cnt = $c->get(Cache::key('profile:subscription_count:'.$this->id));
if (is_integer($cnt)) {
return (int) $cnt;
}
}
$sub = new Subscription();
$sub->subscriber = $this->id;
$cnt = (int) $sub->count('distinct subscribed');
$cnt = ($cnt > 0) ? $cnt - 1 : $cnt;
if (!empty($c)) {
$c->set(Cache::key('profile:subscription_count:'.$this->id), $cnt);
}
return $cnt;
}
function subscriberCount()
{
$c = Cache::instance();
if (!empty($c)) {
$cnt = $c->get(Cache::key('profile:subscriber_count:'.$this->id));
if (is_integer($cnt)) {
return (int) $cnt;
}
}
$sub = new Subscription();
$sub->subscribed = $this->id;
$sub->whereAdd('subscriber != subscribed');
$cnt = (int) $sub->count('distinct subscriber');
if (!empty($c)) {
$c->set(Cache::key('profile:subscriber_count:'.$this->id), $cnt);
}
return $cnt;
}
/**
* Is this profile subscribed to another profile?
*
* @param Profile $other
* @return boolean
*/
function isSubscribed($other)
{
return Subscription::exists($this, $other);
}
/**
* Check if a pending subscription request is outstanding for this...
*
* @param Profile $other
* @return boolean
*/
function hasPendingSubscription($other)
{
return Subscription_queue::exists($this, $other);
}
/**
* Are these two profiles subscribed to each other?
*
* @param Profile $other
* @return boolean
*/
function mutuallySubscribed($other)
{
return $this->isSubscribed($other) &&
$other->isSubscribed($this);
}
function hasFave($notice)
{
$fave = Fave::pkeyGet(array('user_id' => $this->id,
'notice_id' => $notice->id));
return ((is_null($fave)) ? false : true);
}
function faveCount()
{
$c = Cache::instance();
if (!empty($c)) {
$cnt = $c->get(Cache::key('profile:fave_count:'.$this->id));
if (is_integer($cnt)) {
return (int) $cnt;
}
}
$faves = new Fave();
$faves->user_id = $this->id;
$cnt = (int) $faves->count('distinct notice_id');
if (!empty($c)) {
$c->set(Cache::key('profile:fave_count:'.$this->id), $cnt);
}
return $cnt;
}
function noticeCount()
{
$c = Cache::instance();
if (!empty($c)) {
$cnt = $c->get(Cache::key('profile:notice_count:'.$this->id));
if (is_integer($cnt)) {
return (int) $cnt;
}
}
$notices = new Notice();
$notices->profile_id = $this->id;
$cnt = (int) $notices->count('distinct id');
if (!empty($c)) {
$c->set(Cache::key('profile:notice_count:'.$this->id), $cnt);
}
return $cnt;
}
function blowFavesCache()
{
$cache = Cache::instance();
if ($cache) {
// Faves don't happen chronologically, so we need to blow
// ;last cache, too
$cache->delete(Cache::key('fave:ids_by_user:'.$this->id));
$cache->delete(Cache::key('fave:ids_by_user:'.$this->id.';last'));
$cache->delete(Cache::key('fave:ids_by_user_own:'.$this->id));
$cache->delete(Cache::key('fave:ids_by_user_own:'.$this->id.';last'));
}
$this->blowFaveCount();
}
function blowSubscriberCount()
{
$c = Cache::instance();
if (!empty($c)) {
$c->delete(Cache::key('profile:subscriber_count:'.$this->id));
}
}
function blowSubscriptionCount()
{
$c = Cache::instance();
if (!empty($c)) {
$c->delete(Cache::key('profile:subscription_count:'.$this->id));
}
}
function blowFaveCount()
{
$c = Cache::instance();
if (!empty($c)) {
$c->delete(Cache::key('profile:fave_count:'.$this->id));
}
}
function blowNoticeCount()
{
$c = Cache::instance();
if (!empty($c)) {
$c->delete(Cache::key('profile:notice_count:'.$this->id));
}
}
static function maxBio()
{
$biolimit = common_config('profile', 'biolimit');
// null => use global limit (distinct from 0!)
if (is_null($biolimit)) {
$biolimit = common_config('site', 'textlimit');
}
return $biolimit;
}
static function bioTooLong($bio)
{
$biolimit = self::maxBio();
return ($biolimit > 0 && !empty($bio) && (mb_strlen($bio) > $biolimit));
}
function delete()
{
$this->_deleteNotices();
$this->_deleteSubscriptions();
$this->_deleteMessages();
$this->_deleteTags();
$this->_deleteBlocks();
$this->delete_avatars();
// Warning: delete() will run on the batch objects,
// not on individual objects.
$related = array('Reply',
'Group_member',
);
Event::handle('ProfileDeleteRelated', array($this, &$related));
foreach ($related as $cls) {
$inst = new $cls();
$inst->profile_id = $this->id;
$inst->delete();
}
parent::delete();
}
function _deleteNotices()
{
$notice = new Notice();
$notice->profile_id = $this->id;
if ($notice->find()) {
while ($notice->fetch()) {
$other = clone($notice);
$other->delete();
}
}
}
function _deleteSubscriptions()
{
$sub = new Subscription();
$sub->subscriber = $this->id;
$sub->find();
while ($sub->fetch()) {
$other = Profile::staticGet('id', $sub->subscribed);
if (empty($other)) {
continue;
}
if ($other->id == $this->id) {
continue;
}
Subscription::cancel($this, $other);
}
$subd = new Subscription();
$subd->subscribed = $this->id;
$subd->find();
while ($subd->fetch()) {
$other = Profile::staticGet('id', $subd->subscriber);
if (empty($other)) {
continue;
}
if ($other->id == $this->id) {
continue;
}
Subscription::cancel($other, $this);
}
$self = new Subscription();
$self->subscriber = $this->id;
$self->subscribed = $this->id;
$self->delete();
}
function _deleteMessages()
{
$msg = new Message();
$msg->from_profile = $this->id;
$msg->delete();
$msg = new Message();
$msg->to_profile = $this->id;
$msg->delete();
}
function _deleteTags()
{
$tag = new Profile_tag();
$tag->tagged = $this->id;
$tag->delete();
}
function _deleteBlocks()
{
$block = new Profile_block();
$block->blocked = $this->id;
$block->delete();
$block = new Group_block();
$block->blocked = $this->id;
$block->delete();
}
// XXX: identical to Notice::getLocation.
function getLocation()
{
$location = null;
if (!empty($this->location_id) && !empty($this->location_ns)) {
$location = Location::fromId($this->location_id, $this->location_ns);
}
if (is_null($location)) { // no ID, or Location::fromId() failed
if (!empty($this->lat) && !empty($this->lon)) {
$location = Location::fromLatLon($this->lat, $this->lon);
}
}
if (is_null($location)) { // still haven't found it!
if (!empty($this->location)) {
$location = Location::fromName($this->location);
}
}
return $location;
}
function hasRole($name)
{
$has_role = false;
if (Event::handle('StartHasRole', array($this, $name, &$has_role))) {
$role = Profile_role::pkeyGet(array('profile_id' => $this->id,
'role' => $name));
$has_role = !empty($role);
Event::handle('EndHasRole', array($this, $name, $has_role));
}
return $has_role;
}
function grantRole($name)
{
if (Event::handle('StartGrantRole', array($this, $name))) {
$role = new Profile_role();
$role->profile_id = $this->id;
$role->role = $name;
$role->created = common_sql_now();
$result = $role->insert();
if (!$result) {
throw new Exception("Can't save role '$name' for profile '{$this->id}'");
}
if ($name == 'owner') {
User::blow('user:site_owner');
}
Event::handle('EndGrantRole', array($this, $name));
}
return $result;
}
function revokeRole($name)
{
if (Event::handle('StartRevokeRole', array($this, $name))) {
$role = Profile_role::pkeyGet(array('profile_id' => $this->id,
'role' => $name));
if (empty($role)) {
// TRANS: Exception thrown when trying to revoke an existing role for a user that does not exist.
// TRANS: %1$s is the role name, %2$s is the user ID (number).
throw new Exception(sprintf(_('Cannot revoke role "%1$s" for user #%2$d; does not exist.'),$name, $this->id));
}
$result = $role->delete();
if (!$result) {
common_log_db_error($role, 'DELETE', __FILE__);
// TRANS: Exception thrown when trying to revoke a role for a user with a failing database query.
// TRANS: %1$s is the role name, %2$s is the user ID (number).
throw new Exception(sprintf(_('Cannot revoke role "%1$s" for user #%2$d; database error.'),$name, $this->id));
}
if ($name == 'owner') {
User::blow('user:site_owner');
}
Event::handle('EndRevokeRole', array($this, $name));
return true;
}
}
function isSandboxed()
{
return $this->hasRole(Profile_role::SANDBOXED);
}
function isSilenced()
{
return $this->hasRole(Profile_role::SILENCED);
}
function sandbox()
{
$this->grantRole(Profile_role::SANDBOXED);
}
function unsandbox()
{
$this->revokeRole(Profile_role::SANDBOXED);
}
function silence()
{
$this->grantRole(Profile_role::SILENCED);
}
function unsilence()
{
$this->revokeRole(Profile_role::SILENCED);
}
/**
* Does this user have the right to do X?
*
* With our role-based authorization, this is merely a lookup for whether the user
* has a particular role. The implementation currently uses a switch statement
* to determine if the user has the pre-defined role to exercise the right. Future
* implementations may allow per-site roles, and different mappings of roles to rights.
*
* @param $right string Name of the right, usually a constant in class Right
* @return boolean whether the user has the right in question
*/
function hasRight($right)
{
$result = false;
if ($this->hasRole(Profile_role::DELETED)) {
return false;
}
if (Event::handle('UserRightsCheck', array($this, $right, &$result))) {
switch ($right)
{
case Right::DELETEOTHERSNOTICE:
case Right::MAKEGROUPADMIN:
case Right::SANDBOXUSER:
case Right::SILENCEUSER:
case Right::DELETEUSER:
case Right::DELETEGROUP:
$result = $this->hasRole(Profile_role::MODERATOR);
break;
case Right::CONFIGURESITE:
$result = $this->hasRole(Profile_role::ADMINISTRATOR);
break;
case Right::GRANTROLE:
case Right::REVOKEROLE:
$result = $this->hasRole(Profile_role::OWNER);
break;
case Right::NEWNOTICE:
case Right::NEWMESSAGE:
case Right::SUBSCRIBE:
case Right::CREATEGROUP:
$result = !$this->isSilenced();
break;
case Right::PUBLICNOTICE:
case Right::EMAILONREPLY:
case Right::EMAILONSUBSCRIBE:
case Right::EMAILONFAVE:
$result = !$this->isSandboxed();
break;
case Right::WEBLOGIN:
$result = !$this->isSilenced();
break;
case Right::API:
$result = !$this->isSilenced();
break;
case Right::BACKUPACCOUNT:
$result = common_config('profile', 'backup');
break;
case Right::RESTOREACCOUNT:
$result = common_config('profile', 'restore');
break;
case Right::DELETEACCOUNT:
$result = common_config('profile', 'delete');
break;
case Right::MOVEACCOUNT:
$result = common_config('profile', 'move');
break;
default:
$result = false;
break;
}
}
return $result;
}
function hasRepeated($notice_id)
{
// XXX: not really a pkey, but should work
$notice = Memcached_DataObject::pkeyGet('Notice',
array('profile_id' => $this->id,
'repeat_of' => $notice_id));
return !empty($notice);
}
/**
* Returns an XML string fragment with limited profile information
* as an Atom <author> element.
*
* Assumes that Atom has been previously set up as the base namespace.
*
* @param Profile $cur the current authenticated user
*
* @return string
*/
function asAtomAuthor($cur = null)
{
$xs = new XMLStringer(true);
$xs->elementStart('author');
$xs->element('name', null, $this->nickname);
$xs->element('uri', null, $this->getUri());
if ($cur != null) {
$attrs = Array();
$attrs['following'] = $cur->isSubscribed($this) ? 'true' : 'false';
$attrs['blocking'] = $cur->hasBlocked($this) ? 'true' : 'false';
$xs->element('statusnet:profile_info', $attrs, null);
}
$xs->elementEnd('author');
return $xs->getString();
}
/**
* Extra profile info for atom entries
*
* Clients use some extra profile info in the atom stream.
* This gives it to them.
*
* @param User $cur Current user
*
* @return array representation of <statusnet:profile_info> element or null
*/
function profileInfo($cur)
{
$profileInfoAttr = array('local_id' => $this->id);
if ($cur != null) {
// Whether the current user is a subscribed to this profile
$profileInfoAttr['following'] = $cur->isSubscribed($this) ? 'true' : 'false';
// Whether the current user is has blocked this profile
$profileInfoAttr['blocking'] = $cur->hasBlocked($this) ? 'true' : 'false';
}
return array('statusnet:profile_info', $profileInfoAttr, null);
}
/**
* Returns an XML string fragment with profile information as an
* Activity Streams <activity:actor> element.
*
* Assumes that 'activity' namespace has been previously defined.
*
* @return string
*/
function asActivityActor()
{
return $this->asActivityNoun('actor');
}
/**
* Returns an XML string fragment with profile information as an
* Activity Streams noun object with the given element type.
*
* Assumes that 'activity', 'georss', and 'poco' namespace has been
* previously defined.
*
* @param string $element one of 'actor', 'subject', 'object', 'target'
*
* @return string
*/
function asActivityNoun($element)
{
$noun = ActivityObject::fromProfile($this);
return $noun->asString('activity:' . $element);
}
/**
* Returns the best URI for a profile. Plugins may override.
*
* @return string $uri
*/
function getUri()
{
$uri = null;
// give plugins a chance to set the URI
if (Event::handle('StartGetProfileUri', array($this, &$uri))) {
// check for a local user first
$user = User::staticGet('id', $this->id);
if (!empty($user)) {
$uri = $user->uri;
}
Event::handle('EndGetProfileUri', array($this, &$uri));
}
return $uri;
}
function hasBlocked($other)
{
$block = Profile_block::get($this->id, $other->id);
if (empty($block)) {
$result = false;
} else {
$result = true;
}
return $result;
}
function getAtomFeed()
{
$feed = null;
if (Event::handle('StartProfileGetAtomFeed', array($this, &$feed))) {
$user = User::staticGet('id', $this->id);
if (!empty($user)) {
$feed = common_local_url('ApiTimelineUser', array('id' => $user->id,
'format' => 'atom'));
}
Event::handle('EndProfileGetAtomFeed', array($this, $feed));
}
return $feed;
}
static function fromURI($uri)
{
$profile = null;
if (Event::handle('StartGetProfileFromURI', array($uri, &$profile))) {
// Get a local user or remote (OMB 0.1) profile
$user = User::staticGet('uri', $uri);
if (!empty($user)) {
$profile = $user->getProfile();
}
Event::handle('EndGetProfileFromURI', array($uri, $profile));
}
return $profile;
}
function canRead(Notice $notice)
{
if ($notice->scope & Notice::SITE_SCOPE) {
$user = $this->getUser();
if (empty($user)) {
return false;
}
}
if ($notice->scope & Notice::ADDRESSEE_SCOPE) {
$replies = $notice->getReplies();
if (!in_array($this->id, $replies)) {
$groups = $notice->getGroups();
$foundOne = false;
foreach ($groups as $group) {
if ($this->isMember($group)) {
$foundOne = true;
break;
}
}
if (!$foundOne) {
return false;
}
}
}
if ($notice->scope & Notice::FOLLOWER_SCOPE) {
$author = $notice->getProfile();
if (!Subscription::exists($this, $author)) {
return false;
}
}
return true;
}
static function current()
{
$user = common_current_user();
if (empty($user)) {
$profile = null;
} else {
$profile = $user->getProfile();
}
return $profile;
}
/**
* Magic function called at serialize() time.
*
* We use this to drop a couple process-specific references
* from DB_DataObject which can cause trouble in future
* processes.
*
* @return array of variable names to include in serialization.
*/
function __sleep()
{
$vars = parent::__sleep();
$skip = array('_user');
return array_diff($vars, $skip);
}
}