<?php
/**
* @file include/identity.php
*/
require_once('include/zot.php');
require_once('include/crypto.php');
/**
* @brief Called when creating a new channel.
*
* Checks the account's service class and number of current channels to determine
* whether creating a new channel is within the current service class constraints.
*
* @param int $account_id
* Account_id used for this request
*
* @returns assoziative array with:
* * \e boolean \b success boolean true if creating a new channel is allowed for this account
* * \e string \b message (optional) if success is false, optional error text
* * \e int \b total_identities
*/
function identity_check_service_class($account_id) {
$ret = array('success' => false, 'message' => '');
$r = q("select count(channel_id) as total from channel where channel_account_id = %d and channel_removed = 0 ",
intval($account_id)
);
if(! ($r && count($r))) {
$ret['total_identities'] = 0;
$ret['message'] = t('Unable to obtain identity information from database');
return $ret;
}
$ret['total_identities'] = intval($r[0]['total']);
if (! account_service_class_allows($account_id, 'total_identities', $r[0]['total'])) {
$ret['message'] .= upgrade_message();
return $ret;
}
$ret['success'] = true;
return $ret;
}
/**
* @brief Determine if the channel name is allowed when creating a new channel.
*
* This action is pluggable.
* We're currently only checking for an empty name or one that exceeds our
* storage limit (255 chars). 255 chars is probably going to create a mess on
* some pages.
* Plugins can set additional policies such as full name requirements, character
* sets, multi-byte length, etc.
*
* @param string $name
*
* @returns nil return if name is valid, or string describing the error state.
*/
function validate_channelname($name) {
if (! $name)
return t('Empty name');
if (strlen($name) > 255)
return t('Name too long');
$arr = array('name' => $name);
call_hooks('validate_channelname', $arr);
if (x($arr, 'message'))
return $arr['message'];
}
/**
* @brief Create a system channel - which has no account attached.
*
*/
function create_sys_channel() {
if (get_sys_channel())
return;
// Ensure that there is a host keypair.
if ((! get_config('system', 'pubkey')) && (! get_config('system', 'prvkey'))) {
require_once('include/crypto.php');
$hostkey = new_keypair(4096);
set_config('system', 'pubkey', $hostkey['pubkey']);
set_config('system', 'prvkey', $hostkey['prvkey']);
}
create_identity(array(
'account_id' => 'xxx', // This will create an identity with an (integer) account_id of 0, but account_id is required
'nickname' => 'sys',
'name' => 'System',
'pageflags' => 0,
'publish' => 0,
'system' => 1
));
}
/**
* @brief Returns the sys channel.
*
* @return array|boolean
*/
function get_sys_channel() {
$r = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_system = 1 limit 1");
if ($r)
return $r[0];
return false;
}
/**
* @brief Checks if $channel_id is sys channel.
*
* @param int $channel_id
* @return boolean
*/
function is_sys_channel($channel_id) {
$r = q("select channel_system from channel where channel_id = %d and channel_system = 1 limit 1",
intval($channel_id)
);
if($r)
return true;
return false;
}
/**
* @brief Return the total number of channels on this site.
*
* No filtering is performed except to check PAGE_REMOVED.
*
* @returns int|booleean
* on error returns boolean false
*/
function channel_total() {
$r = q("select channel_id from channel where channel_removed = 0");
if (is_array($r))
return count($r);
return false;
}
/**
* @brief Create a new channel.
*
* Also creates the related xchan, hubloc, profile, and "self" abook records,
* and an empty "Friends" group/collection for the new channel.
*
* @param array $arr assoziative array with:
* * \e string \b name full name of channel
* * \e string \b nickname "email/url-compliant" nickname
* * \e int \b account_id to attach with this channel
* * [other identity fields as desired]
*
* @returns array
* 'success' => boolean true or false
* 'message' => optional error text if success is false
* 'channel' => if successful the created channel array
*/
function create_identity($arr) {
$a = get_app();
$ret = array('success' => false);
if(! $arr['account_id']) {
$ret['message'] = t('No account identifier');
return $ret;
}
$ret = identity_check_service_class($arr['account_id']);
if (!$ret['success']) {
return $ret;
}
// save this for auto_friending
$total_identities = $ret['total_identities'];
$nick = mb_strtolower(trim($arr['nickname']));
if(! $nick) {
$ret['message'] = t('Nickname is required.');
return $ret;
}
$name = escape_tags($arr['name']);
$pageflags = ((x($arr,'pageflags')) ? intval($arr['pageflags']) : PAGE_NORMAL);
$system = ((x($arr,'system')) ? intval($arr['system']) : 0);
$name_error = validate_channelname($arr['name']);
if($name_error) {
$ret['message'] = $name_error;
return $ret;
}
if($nick === 'sys' && (! $system)) {
$ret['message'] = t('Reserved nickname. Please choose another.');
return $ret;
}
if(check_webbie(array($nick)) !== $nick) {
$ret['message'] = t('Nickname has unsupported characters or is already being used on this site.');
return $ret;
}
$guid = zot_new_uid($nick);
$key = new_keypair(4096);
$sig = base64url_encode(rsa_sign($guid,$key['prvkey']));
$hash = make_xchan_hash($guid,$sig);
// Force a few things on the short term until we can provide a theme or app with choice
$publish = 1;
if(array_key_exists('publish', $arr))
$publish = intval($arr['publish']);
$primary = true;
if(array_key_exists('primary', $arr))
$primary = intval($arr['primary']);
$role_permissions = null;
$global_perms = get_perms();
if(array_key_exists('permissions_role',$arr) && $arr['permissions_role']) {
$role_permissions = get_role_perms($arr['permissions_role']);
if($role_permissions) {
foreach($role_permissions as $p => $v) {
if(strpos($p,'channel_') !== false) {
$perms_keys .= ', ' . $p;
$perms_vals .= ', ' . intval($v);
}
if($p === 'directory_publish')
$publish = intval($v);
}
}
}
else {
$defperms = site_default_perms();
foreach($defperms as $p => $v) {
$perms_keys .= ', ' . $global_perms[$p][0];
$perms_vals .= ', ' . intval($v);
}
}
$expire = 0;
$r = q("insert into channel ( channel_account_id, channel_primary,
channel_name, channel_address, channel_guid, channel_guid_sig,
channel_hash, channel_prvkey, channel_pubkey, channel_pageflags, channel_system, channel_expire_days, channel_timezone $perms_keys )
values ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s' $perms_vals ) ",
intval($arr['account_id']),
intval($primary),
dbesc($name),
dbesc($nick),
dbesc($guid),
dbesc($sig),
dbesc($hash),
dbesc($key['prvkey']),
dbesc($key['pubkey']),
intval($pageflags),
intval($system),
intval($expire),
dbesc($a->timezone)
);
$r = q("select * from channel where channel_account_id = %d
and channel_guid = '%s' limit 1",
intval($arr['account_id']),
dbesc($guid)
);
if(! $r) {
$ret['message'] = t('Unable to retrieve created identity');
return $ret;
}
$ret['channel'] = $r[0];
if(intval($arr['account_id']))
set_default_login_identity($arr['account_id'],$ret['channel']['channel_id'],false);
// Create a verified hub location pointing to this site.
$r = q("insert into hubloc ( hubloc_guid, hubloc_guid_sig, hubloc_hash, hubloc_addr, hubloc_primary,
hubloc_url, hubloc_url_sig, hubloc_host, hubloc_callback, hubloc_sitekey, hubloc_network )
values ( '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s' )",
dbesc($guid),
dbesc($sig),
dbesc($hash),
dbesc($ret['channel']['channel_address'] . '@' . get_app()->get_hostname()),
intval($primary),
dbesc(z_root()),
dbesc(base64url_encode(rsa_sign(z_root(),$ret['channel']['channel_prvkey']))),
dbesc(get_app()->get_hostname()),
dbesc(z_root() . '/post'),
dbesc(get_config('system','pubkey')),
dbesc('zot')
);
if(! $r)
logger('create_identity: Unable to store hub location');
$newuid = $ret['channel']['channel_id'];
$r = q("insert into xchan ( xchan_hash, xchan_guid, xchan_guid_sig, xchan_pubkey, xchan_photo_l, xchan_photo_m, xchan_photo_s, xchan_addr, xchan_url, xchan_follow, xchan_connurl, xchan_name, xchan_network, xchan_photo_date, xchan_name_date, xchan_system ) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d)",
dbesc($hash),
dbesc($guid),
dbesc($sig),
dbesc($key['pubkey']),
dbesc($a->get_baseurl() . "/photo/profile/l/{$newuid}"),
dbesc($a->get_baseurl() . "/photo/profile/m/{$newuid}"),
dbesc($a->get_baseurl() . "/photo/profile/s/{$newuid}"),
dbesc($ret['channel']['channel_address'] . '@' . get_app()->get_hostname()),
dbesc(z_root() . '/channel/' . $ret['channel']['channel_address']),
dbesc(z_root() . '/follow?f=&url=%s'),
dbesc(z_root() . '/poco/' . $ret['channel']['channel_address']),
dbesc($ret['channel']['channel_name']),
dbesc('zot'),
dbesc(datetime_convert()),
dbesc(datetime_convert()),
intval($system)
);
// Not checking return value.
// It's ok for this to fail if it's an imported channel, and therefore the hash is a duplicate
$r = q("INSERT INTO profile ( aid, uid, profile_guid, profile_name, is_default, publish, name, photo, thumb)
VALUES ( %d, %d, '%s', '%s', %d, %d, '%s', '%s', '%s') ",
intval($ret['channel']['channel_account_id']),
intval($newuid),
dbesc(random_string()),
t('Default Profile'),
1,
$publish,
dbesc($ret['channel']['channel_name']),
dbesc($a->get_baseurl() . "/photo/profile/l/{$newuid}"),
dbesc($a->get_baseurl() . "/photo/profile/m/{$newuid}")
);
if($role_permissions) {
$myperms = ((array_key_exists('perms_auto',$role_permissions) && $role_permissions['perms_auto']) ? intval($role_permissions['perms_accept']) : 0);
}
else
$myperms = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_PHOTOS|PERMS_R_ABOOK
|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_W_LIKE;
$r = q("insert into abook ( abook_account, abook_channel, abook_xchan, abook_closeness, abook_created, abook_updated, abook_self, abook_my_perms )
values ( %d, %d, '%s', %d, '%s', '%s', %d, %d ) ",
intval($ret['channel']['channel_account_id']),
intval($newuid),
dbesc($hash),
intval(0),
dbesc(datetime_convert()),
dbesc(datetime_convert()),
intval(1),
intval($myperms)
);
if(intval($ret['channel']['channel_account_id'])) {
// Save our permissions role so we can perhaps call it up and modify it later.
if($role_permissions) {
set_pconfig($newuid,'system','permissions_role',$arr['permissions_role']);
if(array_key_exists('online',$role_permissions))
set_pconfig($newuid,'system','hide_presence',1-intval($role_permissions['online']));
if(array_key_exists('perms_auto',$role_permissions))
set_pconfig($newuid,'system','autoperms',(($role_permissions['perms_auto']) ? $role_permissions['perms_accept'] : 0));
}
// Create a group with yourself as a member. This allows somebody to use it
// right away as a default group for new contacts.
require_once('include/group.php');
group_add($newuid, t('Friends'));
group_add_member($newuid,t('Friends'),$ret['channel']['channel_hash']);
// if our role_permissions indicate that we're using a default collection ACL, add it.
if(is_array($role_permissions) && $role_permissions['default_collection']) {
$r = q("select hash from groups where uid = %d and name = '%s' limit 1",
intval($newuid),
dbesc( t('Friends') )
);
if($r) {
q("update channel set channel_default_group = '%s', channel_allow_gid = '%s' where channel_id = %d",
dbesc($r[0]['hash']),
dbesc('<' . $r[0]['hash'] . '>'),
intval($newuid)
);
}
}
if(! $system) {
set_pconfig($ret['channel']['channel_id'],'system','photo_path', '%Y-%m');
set_pconfig($ret['channel']['channel_id'],'system','attach_path','%Y-%m');
}
// auto-follow any of the hub's pre-configured channel choices.
// Only do this if it's the first channel for this account;
// otherwise it could get annoying. Don't make this list too big
// or it will impact registration time.
$accts = get_config('system','auto_follow');
if(($accts) && (! $total_identities)) {
require_once('include/follow.php');
if(! is_array($accts))
$accts = array($accts);
foreach($accts as $acct) {
if(trim($acct))
new_contact($newuid,trim($acct),$ret['channel'],false);
}
}
call_hooks('create_identity', $newuid);
proc_run('php','include/directory.php', $ret['channel']['channel_id']);
}
$ret['success'] = true;
return $ret;
}
/**
* @brief Set default channel to be used on login.
*
* @param int $account_id
* login account
* @param int $channel_id
* channel id to set as default for this account
* @param boolean $force
* if true, set this default unconditionally
* if $force is false only do this if there is no existing default
*/
function set_default_login_identity($account_id, $channel_id, $force = true) {
$r = q("select account_default_channel from account where account_id = %d limit 1",
intval($account_id)
);
if ($r) {
if ((intval($r[0]['account_default_channel']) == 0) || ($force)) {
$r = q("update account set account_default_channel = %d where account_id = %d",
intval($channel_id),
intval($account_id)
);
}
}
}
/**
* @brief Create an array representing the important channel information
* which would be necessary to create a nomadic identity clone. This includes
* most channel resources and connection information with the exception of content.
*
* @param int $channel_id
* Channel_id to export
* @param boolean $items
* Include channel posts (wall items), default false
*
* @returns array
* See function for details
*/
function identity_basic_export($channel_id, $items = false) {
/*
* Red basic channel export
*/
$ret = array();
$ret['compatibility'] = array('project' => PLATFORM_NAME, 'version' => RED_VERSION, 'database' => DB_UPDATE_VERSION);
$r = q("select * from channel where channel_id = %d limit 1",
intval($channel_id)
);
if($r)
$ret['channel'] = $r[0];
$r = q("select * from profile where uid = %d",
intval($channel_id)
);
if($r)
$ret['profile'] = $r;
$xchans = array();
$r = q("select * from abook where abook_channel = %d ",
intval($channel_id)
);
if($r) {
$ret['abook'] = $r;
foreach($r as $rr)
$xchans[] = $rr['abook_xchan'];
stringify_array_elms($xchans);
}
if($xchans) {
$r = q("select * from xchan where xchan_hash in ( " . implode(',',$xchans) . " ) ");
if($r)
$ret['xchan'] = $r;
$r = q("select * from hubloc where hubloc_hash in ( " . implode(',',$xchans) . " ) ");
if($r)
$ret['hubloc'] = $r;
}
$r = q("select * from `groups` where uid = %d ",
intval($channel_id)
);
if($r)
$ret['group'] = $r;
$r = q("select * from group_member where uid = %d ",
intval($channel_id)
);
if($r)
$ret['group_member'] = $r;
$r = q("select * from pconfig where uid = %d",
intval($channel_id)
);
if($r)
$ret['config'] = $r;
$r = q("select type, data, os_storage from photo where scale = 4 and profile = 1 and uid = %d limit 1",
intval($channel_id)
);
if($r) {
$ret['photo'] = array('type' => $r[0]['type'], 'data' => (($r[0]['os_storage']) ? base64url_encode(file_get_contents($r[0]['data'])) : base64url_encode($r[0]['data'])));
}
// All other term types will be included in items, if requested.
$r = q("select * from term where type in (%d,%d) and uid = %d",
intval(TERM_SAVEDSEARCH),
intval(TERM_THING),
intval($channel_id)
);
if($r)
$ret['term'] = $r;
$r = q("select * from obj where obj_channel = %d",
intval($channel_id)
);
if($r)
$ret['obj'] = $r;
if(! $items)
return $ret;
$r = q("select * from likes where channel_id = %d",
intval($channel_id)
);
if($r)
$ret['likes'] = $r;
$r = q("select item_id.*, item.mid from item_id left join item on item_id.iid = item.id where item_id.uid = %d",
intval($channel_id)
);
if($r)
$ret['item_id'] = $r;
//$key = get_config('system','prvkey');
/** @warning this may run into memory limits on smaller systems */
/** export one year of posts. If you want to export and import all posts you have to start with
* the first year and export/import them in ascending order.
*/
$r = q("select * from item where item_wall = 1 and item_deleted = 0 and uid = %d and created > %s - INTERVAL %s",
intval($channel_id),
db_utcnow(),
db_quoteinterval('1 YEAR')
);
if($r) {
$ret['item'] = array();
xchan_query($r);
$r = fetch_post_tags($r,true);
foreach($r as $rr)
$ret['item'][] = encode_item($rr,true);
}
return $ret;
}
function identity_export_year($channel_id,$year,$month = 0) {
if(! $year)
return array();
if($month && $month <= 12) {
$target_month = sprintf('%02d',$month);
$target_month_plus = sprintf('%02d',$month+1);
}
else
$target_month = '01';
$ret = array();
$mindate = datetime_convert('UTC','UTC',$year . '-' . $target_month . '-01 00:00:00');
if($month && $month < 12)
$maxdate = datetime_convert('UTC','UTC',$year . '-' . $target_month_plus . '-01 00:00:00');
else
$maxdate = datetime_convert('UTC','UTC',$year+1 . '-01-01 00:00:00');
$r = q("select * from item where item_wall = 1 and item_deleted = 0 and uid = %d and created >= '%s' and created < '%s' order by created",
intval($channel_id),
dbesc($mindate),
dbesc($maxdate)
);
if($r) {
$ret['item'] = array();
xchan_query($r);
$r = fetch_post_tags($r,true);
foreach($r as $rr)
$ret['item'][] = encode_item($rr,true);
}
$r = q("select item_id.*, item.mid from item_id left join item on item_id.iid = item.id where item_id.uid = %d
and item.created >= '%s' and item.created < '%s' order by created ",
intval($channel_id),
dbesc($mindate),
dbesc($maxdate)
);
if($r)
$ret['item_id'] = $r;
return $ret;
}
/**
* @brief Loads a profile into the App structure.
*
* The function requires a writeable copy of the main App structure, and the
* nickname of a valid channel.
*
* Permissions of the current observer are checked. If a restricted profile is available
* to the current observer, that will be loaded instead of the channel default profile.
*
* The channel owner can set $profile to a valid profile_guid to preview that profile.
*
* The channel default theme is also selected for use, unless over-riden elsewhere.
*
* @param[in,out] App &$a
* @param string $nickname
* @param string $profile
*/
function profile_load(&$a, $nickname, $profile = '') {
logger('profile_load: ' . $nickname . (($profile) ? ' profile: ' . $profile : ''));
$user = q("select channel_id from channel where channel_address = '%s' and channel_removed = 0 limit 1",
dbesc($nickname)
);
if(! $user) {
logger('profile error: ' . $a->query_string, LOGGER_DEBUG);
notice( t('Requested channel is not available.') . EOL );
$a->error = 404;
return;
}
// get the current observer
$observer = $a->get_observer();
$can_view_profile = true;
// Can the observer see our profile?
require_once('include/permissions.php');
if(! perm_is_allowed($user[0]['channel_id'],$observer['xchan_hash'],'view_profile')) {
$can_view_profile = false;
}
if(! $profile) {
$r = q("SELECT abook_profile FROM abook WHERE abook_xchan = '%s' and abook_channel = '%d' limit 1",
dbesc($observer['xchan_hash']),
intval($user[0]['channel_id'])
);
if($r)
$profile = $r[0]['abook_profile'];
}
$p = null;
if($profile) {
$p = q("SELECT profile.uid AS profile_uid, profile.*, channel.* FROM profile
LEFT JOIN channel ON profile.uid = channel.channel_id
WHERE channel.channel_address = '%s' AND profile.profile_guid = '%s' LIMIT 1",
dbesc($nickname),
dbesc($profile)
);
}
if(! $p) {
$p = q("SELECT profile.uid AS profile_uid, profile.*, channel.* FROM profile
LEFT JOIN channel ON profile.uid = channel.channel_id
WHERE channel.channel_address = '%s' and channel_removed = 0
AND profile.is_default = 1 LIMIT 1",
dbesc($nickname)
);
}
if(! $p) {
logger('profile error: ' . $a->query_string, LOGGER_DEBUG);
notice( t('Requested profile is not available.') . EOL );
$a->error = 404;
return;
}
$q = q("select * from profext where hash = '%s' and channel_id = %d",
dbesc($p[0]['profile_guid']),
intval($p[0]['profile_uid'])
);
if($q) {
$extra_fields = array();
require_once('include/identity.php');
$profile_fields_basic = get_profile_fields_basic();
$profile_fields_advanced = get_profile_fields_advanced();
$advanced = ((feature_enabled(local_channel(),'advanced_profiles')) ? true : false);
if($advanced)
$fields = $profile_fields_advanced;
else
$fields = $profile_fields_basic;
foreach($q as $qq) {
foreach($fields as $k => $f) {
if($k == $qq['k']) {
$p[0][$k] = $qq['v'];
$extra_fields[] = $k;
break;
}
}
}
}
$p[0]['extra_fields'] = $extra_fields;
$z = q("select xchan_photo_date, xchan_addr from xchan where xchan_hash = '%s' limit 1",
dbesc($p[0]['channel_hash'])
);
if($z) {
$p[0]['picdate'] = $z[0]['xchan_photo_date'];
$p[0]['reddress'] = str_replace('@','@',$z[0]['xchan_addr']);
}
// fetch user tags if this isn't the default profile
if(! $p[0]['is_default']) {
/** @BUG $profile_uid is undefinded for this query, so should not work. */
$x = q("select `keywords` from `profile` where uid = %d and `is_default` = 1 limit 1",
intval($profile_uid)
);
if($x && $can_view_profile)
$p[0]['keywords'] = $x[0]['keywords'];
}
if($p[0]['keywords']) {
$keywords = str_replace(array('#',',',' ',',,'),array('',' ',',',','),$p[0]['keywords']);
if(strlen($keywords) && $can_view_profile)
$a->page['htmlhead'] .= '<meta name="keywords" content="' . htmlentities($keywords,ENT_COMPAT,'UTF-8') . '" />' . "\r\n" ;
}
$a->profile = $p[0];
$a->profile_uid = $p[0]['profile_uid'];
$a->page['title'] = $a->profile['channel_name'] . " - " . $a->profile['channel_address'] . "@" . $a->get_hostname();
$a->profile['permission_to_view'] = $can_view_profile;
if($can_view_profile) {
$online = get_online_status($nickname);
$a->profile['online_status'] = $online['result'];
}
if(local_channel()) {
$a->profile['channel_mobile_theme'] = get_pconfig(local_channel(),'system', 'mobile_theme');
$_SESSION['mobile_theme'] = $a->profile['channel_mobile_theme'];
}
/*
* load/reload current theme info
*/
$_SESSION['theme'] = $p[0]['channel_theme'];
// $a->set_template_engine(); // reset the template engine to the default in case the user's theme doesn't specify one
// $theme_info_file = "view/theme/".current_theme()."/php/theme.php";
// if (file_exists($theme_info_file)){
// require_once($theme_info_file);
// }
}
/**
* @brief
*
* @param App &$a
* @param boolean $connect
*/
function profile_create_sidebar(&$a, $connect = true) {
$block = (((get_config('system', 'block_public')) && (! local_channel()) && (! remote_channel())) ? true : false);
$a->set_widget('profile', profile_sidebar($a->profile, $block, $connect));
}
/**
* @brief Formats a profile for display in the sidebar.
*
* It is very difficult to templatise the HTML completely
* because of all the conditional logic.
*
* @param array $profile
* @param int $block
* @param boolean $show_connect
*
* @return HTML string suitable for sidebar inclusion
* Exceptions: Returns empty string if passed $profile is wrong type or not populated
*/
function profile_sidebar($profile, $block = 0, $show_connect = true) {
$a = get_app();
$observer = $a->get_observer();
$o = '';
$location = false;
$pdesc = true;
$reddress = true;
if((! is_array($profile)) && (! count($profile)))
return $o;
head_set_icon($profile['thumb']);
$is_owner = (($profile['uid'] == local_channel()) ? true : false);
if(is_sys_channel($profile['uid']))
$show_connect = false;
$profile['picdate'] = urlencode($profile['picdate']);
call_hooks('profile_sidebar_enter', $profile);
require_once('include/Contact.php');
if($show_connect) {
// This will return an empty string if we're already connected.
$connect_url = rconnect_url($profile['uid'],get_observer_hash());
$connect = (($connect_url) ? t('Connect') : '');
if($connect_url)
$connect_url = sprintf($connect_url,urlencode($profile['channel_address'] . '@' . $a->get_hostname()));
// premium channel - over-ride
if($profile['channel_pageflags'] & PAGE_PREMIUM)
$connect_url = z_root() . '/connect/' . $profile['channel_address'];
}
// show edit profile to yourself
if($is_owner) {
$profile['menu'] = array(
'chg_photo' => t('Change profile photo'),
'entries' => array(),
);
$multi_profiles = feature_enabled(local_channel(), 'multi_profiles');
if($multi_profiles) {
$profile['edit'] = array($a->get_baseurl(). '/profiles', t('Profiles'),"", t('Manage/edit profiles'));
$profile['menu']['cr_new'] = t('Create New Profile');
}
else
$profile['edit'] = array($a->get_baseurl() . '/profiles/' . $profile['id'], t('Edit Profile'),'',t('Edit Profile'));
$r = q("SELECT * FROM `profile` WHERE `uid` = %d",
local_channel());
if($r) {
foreach($r as $rr) {
if(!($multi_profiles || $rr['is_default']))
continue;
$profile['menu']['entries'][] = array(
'photo' => $rr['thumb'],
'id' => $rr['id'],
'alt' => t('Profile Image'),
'profile_name' => $rr['profile_name'],
'isdefault' => $rr['is_default'],
'visible_to_everybody' => t('visible to everybody'),
'edit_visibility' => t('Edit visibility'),
);
}
}
}
if((x($profile,'address') == 1)
|| (x($profile,'locality') == 1)
|| (x($profile,'region') == 1)
|| (x($profile,'postal_code') == 1)
|| (x($profile,'country_name') == 1))
$location = t('Location:');
$profile['homepage'] = linkify($profile['homepage'],true);
$gender = ((x($profile,'gender') == 1) ? t('Gender:') : False);
$marital = ((x($profile,'marital') == 1) ? t('Status:') : False);
$homepage = ((x($profile,'homepage') == 1) ? t('Homepage:') : False);
$profile['online'] = (($profile['online_status'] === 'online') ? t('Online Now') : False);
logger('online: ' . $profile['online']);
if(! perm_is_allowed($profile['uid'],((is_array($observer)) ? $observer['xchan_hash'] : ''),'view_profile')) {
$block = true;
}
if(($profile['hidewall'] && (! local_channel()) && (! remote_channel())) || $block ) {
$location = $reddress = $pdesc = $gender = $marital = $homepage = False;
}
$firstname = ((strpos($profile['channel_name'],' '))
? trim(substr($profile['channel_name'],0,strpos($profile['channel_name'],' '))) : $profile['channel_name']);
$lastname = (($firstname === $profile['channel_name']) ? '' : trim(substr($profile['channel_name'],strlen($firstname))));
$diaspora = array(
'podloc' => z_root(),
'searchable' => (($block) ? 'false' : 'true'),
'nickname' => $profile['channel_address'],
'fullname' => $profile['channel_name'],
'firstname' => $firstname,
'lastname' => $lastname,
'photo300' => z_root() . '/photo/profile/300/' . $profile['uid'] . '.jpg',
'photo100' => z_root() . '/photo/profile/100/' . $profile['uid'] . '.jpg',
'photo50' => z_root() . '/photo/profile/50/' . $profile['uid'] . '.jpg',
);
$contact_block = contact_block();
$channel_menu = false;
$menu = get_pconfig($profile['uid'],'system','channel_menu');
if($menu && ! $block) {
require_once('include/menu.php');
$m = menu_fetch($menu,$profile['uid'],$observer['xchan_hash']);
if($m)
$channel_menu = menu_render($m);
}
$menublock = get_pconfig($profile['uid'],'system','channel_menublock');
if ($menublock && (! $block)) {
require_once('include/comanche.php');
$channel_menu .= comanche_block($menublock);
}
$tpl = get_markup_template('profile_vcard.tpl');
require_once('include/widgets.php');
$z = widget_rating(array('target' => $profile['channel_hash']));
$o .= replace_macros($tpl, array(
'$profile' => $profile,
'$connect' => $connect,
'$connect_url' => $connect_url,
'$location' => $location,
'$gender' => $gender,
'$pdesc' => $pdesc,
'$marital' => $marital,
'$homepage' => $homepage,
'$chanmenu' => $channel_menu,
'$diaspora' => $diaspora,
'$reddress' => $reddress,
'$rating' => $z,
'$contact_block' => $contact_block,
));
$arr = array('profile' => &$profile, 'entry' => &$o);
call_hooks('profile_sidebar', $arr);
return $o;
}
/**
* @FIXME or remove
*/
function get_birthdays() {
$a = get_app();
$o = '';
if(! local_channel())
return $o;
$bd_format = t('g A l F d') ; // 8 AM Friday January 18
$bd_short = t('F d');
$r = q("SELECT `event`.*, `event`.`id` AS `eid`, `contact`.* FROM `event`
LEFT JOIN `contact` ON `contact`.`id` = `event`.`cid`
WHERE `event`.`uid` = %d AND `type` = 'birthday' AND `start` < '%s' AND `finish` > '%s'
ORDER BY `start` ASC ",
intval(local_channel()),
dbesc(datetime_convert('UTC','UTC','now + 6 days')),
dbesc(datetime_convert('UTC','UTC','now'))
);
if($r && count($r)) {
$total = 0;
$now = strtotime('now');
$cids = array();
$istoday = false;
foreach($r as $rr) {
if(strlen($rr['name']))
$total ++;
if((strtotime($rr['start'] . ' +00:00') < $now) && (strtotime($rr['finish'] . ' +00:00') > $now))
$istoday = true;
}
$classtoday = $istoday ? ' birthday-today ' : '';
if($total) {
foreach($r as &$rr) {
if(! strlen($rr['name']))
continue;
// avoid duplicates
if(in_array($rr['cid'],$cids))
continue;
$cids[] = $rr['cid'];
$today = (((strtotime($rr['start'] . ' +00:00') < $now) && (strtotime($rr['finish'] . ' +00:00') > $now)) ? true : false);
$sparkle = '';
$url = $rr['url'];
if($rr['network'] === NETWORK_DFRN) {
$sparkle = " sparkle";
$url = $a->get_baseurl() . '/redir/' . $rr['cid'];
}
$rr['link'] = $url;
$rr['title'] = $rr['name'];
$rr['date'] = day_translate(datetime_convert('UTC', $a->timezone, $rr['start'], $rr['adjust'] ? $bd_format : $bd_short)) . (($today) ? ' ' . t('[today]') : '');
$rr['startime'] = Null;
$rr['today'] = $today;
}
}
}
$tpl = get_markup_template("birthdays_reminder.tpl");
return replace_macros($tpl, array(
'$baseurl' => $a->get_baseurl(),
'$classtoday' => $classtoday,
'$count' => $total,
'$event_reminders' => t('Birthday Reminders'),
'$event_title' => t('Birthdays this week:'),
'$events' => $r,
'$lbr' => '{', // raw brackets mess up if/endif macro processing
'$rbr' => '}'
));
}
/**
* @FIXME
*/
function get_events() {
require_once('include/bbcode.php');
$a = get_app();
if(! local_channel())
return $o;
$bd_format = t('g A l F d') ; // 8 AM Friday January 18
$bd_short = t('F d');
$r = q("SELECT `event`.* FROM `event`
WHERE `event`.`uid` = %d AND `type` != 'birthday' AND `start` < '%s' AND `start` > '%s'
ORDER BY `start` ASC ",
intval(local_channel()),
dbesc(datetime_convert('UTC','UTC','now + 6 days')),
dbesc(datetime_convert('UTC','UTC','now - 1 days'))
);
if($r && count($r)) {
$now = strtotime('now');
$istoday = false;
foreach($r as $rr) {
if(strlen($rr['name']))
$total ++;
$strt = datetime_convert('UTC',$rr['convert'] ? $a->timezone : 'UTC',$rr['start'],'Y-m-d');
if($strt === datetime_convert('UTC',$a->timezone,'now','Y-m-d'))
$istoday = true;
}
$classtoday = (($istoday) ? 'event-today' : '');
foreach($r as &$rr) {
if($rr['adjust'])
$md = datetime_convert('UTC',$a->timezone,$rr['start'],'Y/m');
else
$md = datetime_convert('UTC','UTC',$rr['start'],'Y/m');
$md .= "/#link-".$rr['id'];
$title = substr(strip_tags(bbcode($rr['desc'])),0,32) . '... ';
if(! $title)
$title = t('[No description]');
$strt = datetime_convert('UTC',$rr['convert'] ? $a->timezone : 'UTC',$rr['start']);
$today = ((substr($strt,0,10) === datetime_convert('UTC',$a->timezone,'now','Y-m-d')) ? true : false);
$rr['link'] = $md;
$rr['title'] = $title;
$rr['date'] = day_translate(datetime_convert('UTC', $rr['adjust'] ? $a->timezone : 'UTC', $rr['start'], $bd_format)) . (($today) ? ' ' . t('[today]') : '');
$rr['startime'] = $strt;
$rr['today'] = $today;
}
}
$tpl = get_markup_template("events_reminder.tpl");
return replace_macros($tpl, array(
'$baseurl' => $a->get_baseurl(),
'$classtoday' => $classtoday,
'$count' => count($r),
'$event_reminders' => t('Event Reminders'),
'$event_title' => t('Events this week:'),
'$events' => $r,
));
}
function advanced_profile(&$a) {
require_once('include/text.php');
if(! perm_is_allowed($a->profile['profile_uid'],get_observer_hash(),'view_profile'))
return '';
$o = '';
$o .= '<h2>' . t('Profile') . '</h2>';
if($a->profile['name']) {
$tpl = get_markup_template('profile_advanced.tpl');
$profile = array();
$profile['fullname'] = array( t('Full Name:'), $a->profile['name'] ) ;
if($a->profile['gender']) $profile['gender'] = array( t('Gender:'), $a->profile['gender'] );
$ob_hash = get_observer_hash();
if($ob_hash && perm_is_allowed($a->profile['profile_uid'],$ob_hash,'post_like')) {
$profile['canlike'] = true;
$profile['likethis'] = t('Like this channel');
$profile['profile_guid'] = $a->profile['profile_guid'];
}
$likers = q("select liker, xchan.* from likes left join xchan on liker = xchan_hash where channel_id = %d and target_type = '%s' and verb = '%s'",
intval($a->profile['profile_uid']),
dbesc(ACTIVITY_OBJ_PROFILE),
dbesc(ACTIVITY_LIKE)
);
$profile['likers'] = array();
$profile['like_count'] = count($likers);
$profile['like_button_label'] = tt('Like','Likes',$profile['like_count'],'noun');
if($likers) {
foreach($likers as $l)
$profile['likers'][] = array('name' => $l['xchan_name'],'url' => zid($l['xchan_url']));
}
if(($a->profile['dob']) && ($a->profile['dob'] != '0000-00-00')) {
$val = '';
if((substr($a->profile['dob'],5,2) === '00') || (substr($a->profile['dob'],8,2) === '00'))
$val = substr($a->profile['dob'],0,4);
$year_bd_format = t('j F, Y');
$short_bd_format = t('j F');
if(! $val) {
$val = ((intval($a->profile['dob']))
? day_translate(datetime_convert('UTC','UTC',$a->profile['dob'] . ' 00:00 +00:00',$year_bd_format))
: day_translate(datetime_convert('UTC','UTC','2001-' . substr($a->profile['dob'],5) . ' 00:00 +00:00',$short_bd_format)));
}
$profile['birthday'] = array( t('Birthday:'), $val);
}
if($age = age($a->profile['dob'],$a->profile['timezone'],''))
$profile['age'] = array( t('Age:'), $age );
if($a->profile['marital'])
$profile['marital'] = array( t('Status:'), $a->profile['marital']);
if($a->profile['with'])
$profile['marital']['with'] = bbcode($a->profile['with']);
if(strlen($a->profile['howlong']) && $a->profile['howlong'] !== NULL_DATE) {
$profile['howlong'] = relative_date($a->profile['howlong'], t('for %1$d %2$s'));
}
if($a->profile['sexual']) $profile['sexual'] = array( t('Sexual Preference:'), $a->profile['sexual'] );
if($a->profile['homepage']) $profile['homepage'] = array( t('Homepage:'), linkify($a->profile['homepage']) );
if($a->profile['hometown']) $profile['hometown'] = array( t('Hometown:'), linkify($a->profile['hometown']) );
if($a->profile['keywords']) $profile['keywords'] = array( t('Tags:'), $a->profile['keywords']);
if($a->profile['politic']) $profile['politic'] = array( t('Political Views:'), $a->profile['politic']);
if($a->profile['religion']) $profile['religion'] = array( t('Religion:'), $a->profile['religion']);
if($txt = prepare_text($a->profile['about'])) $profile['about'] = array( t('About:'), $txt );
if($txt = prepare_text($a->profile['interest'])) $profile['interest'] = array( t('Hobbies/Interests:'), $txt);
if($txt = prepare_text($a->profile['likes'])) $profile['likes'] = array( t('Likes:'), $txt);
if($txt = prepare_text($a->profile['dislikes'])) $profile['dislikes'] = array( t('Dislikes:'), $txt);
if($txt = prepare_text($a->profile['contact'])) $profile['contact'] = array( t('Contact information and Social Networks:'), $txt);
if($txt = prepare_text($a->profile['channels'])) $profile['channels'] = array( t('My other channels:'), $txt);
if($txt = prepare_text($a->profile['music'])) $profile['music'] = array( t('Musical interests:'), $txt);
if($txt = prepare_text($a->profile['book'])) $profile['book'] = array( t('Books, literature:'), $txt);
if($txt = prepare_text($a->profile['tv'])) $profile['tv'] = array( t('Television:'), $txt);
if($txt = prepare_text($a->profile['film'])) $profile['film'] = array( t('Film/dance/culture/entertainment:'), $txt);
if($txt = prepare_text($a->profile['romance'])) $profile['romance'] = array( t('Love/Romance:'), $txt);
if($txt = prepare_text($a->profile['work'])) $profile['work'] = array( t('Work/employment:'), $txt);
if($txt = prepare_text($a->profile['education'])) $profile['education'] = array( t('School/education:'), $txt );
if($a->profile['extra_fields']) {
foreach($a->profile['extra_fields'] as $f) {
$x = q("select * from profdef where field_name = '%s' limit 1",
dbesc($f)
);
if($x && $txt = prepare_text($a->profile[$f]))
$profile[$f] = array( $x[0]['field_desc'] . ':',$txt);
}
$profile['extra_fields'] = $a->profile['extra_fields'];
}
$things = get_things($a->profile['profile_guid'],$a->profile['profile_uid']);
// logger('mod_profile: things: ' . print_r($things,true), LOGGER_DATA);
return replace_macros($tpl, array(
'$title' => t('Profile'),
'$canlike' => (($profile['canlike'])? true : false),
'$likethis' => t('Like this thing'),
'$profile' => $profile,
'$things' => $things
));
}
return '';
}
function get_my_url() {
if(x($_SESSION, 'zrl_override'))
return $_SESSION['zrl_override'];
if(x($_SESSION, 'my_url'))
return $_SESSION['my_url'];
return false;
}
function get_my_address() {
if(x($_SESSION, 'zid_override'))
return $_SESSION['zid_override'];
if(x($_SESSION, 'my_address'))
return $_SESSION['my_address'];
return false;
}
/**
* @brief
*
* If somebody arrives at our site using a zid, add their xchan to our DB if we don't have it already.
* And if they aren't already authenticated here, attempt reverse magic auth.
*
* @param App &$a
*
* @hooks 'zid_init'
* string 'zid' - their zid
* string 'url' - the destination url
*/
function zid_init(&$a) {
$tmp_str = get_my_address();
if(validate_email($tmp_str)) {
proc_run('php','include/gprobe.php',bin2hex($tmp_str));
$arr = array('zid' => $tmp_str, 'url' => $a->cmd);
call_hooks('zid_init',$arr);
if(! local_channel()) {
$r = q("select * from hubloc where hubloc_addr = '%s' order by hubloc_connected desc limit 1",
dbesc($tmp_str)
);
if($r && remote_channel() && remote_channel() === $r[0]['hubloc_hash'])
return;
logger('zid_init: not authenticated. Invoking reverse magic-auth for ' . $tmp_str);
// try to avoid recursion - but send them home to do a proper magic auth
$query = $a->query_string;
$query = str_replace(array('?zid=','&zid='),array('?rzid=','&rzid='),$query);
$dest = '/' . urlencode($query);
if($r && ($r[0]['hubloc_url'] != z_root()) && (! strstr($dest,'/magic')) && (! strstr($dest,'/rmagic'))) {
goaway($r[0]['hubloc_url'] . '/magic' . '?f=&rev=1&dest=' . z_root() . $dest);
}
else
logger('zid_init: no hubloc found.');
}
}
}
/**
* @brief Adds a zid parameter to a url.
*
* @param string $s
* The url to accept the zid
* @param boolean $address
* $address to use instead of session environment
* @return string
*
* @hooks 'zid'
* string url - url to accept zid
* string zid - urlencoded zid
* string result - the return string we calculated, change it if you want to return something else
*/
function zid($s,$address = '') {
if (! strlen($s) || strpos($s,'zid='))
return $s;
$has_params = ((strpos($s,'?')) ? true : false);
$num_slashes = substr_count($s, '/');
if (! $has_params)
$has_params = ((strpos($s, '&')) ? true : false);
$achar = strpos($s,'?') ? '&' : '?';
$mine = get_my_url();
$myaddr = (($address) ? $address : get_my_address());
/** @FIXME checking against our own channel url is no longer reliable. We may have a lot
* of urls attached to out channel. Should probably match against our site, since we
* will not need to remote authenticate on our own site anyway.
*/
if ($mine && $myaddr && (! link_compare($mine,$s)))
$zurl = $s . (($num_slashes >= 3) ? '' : '/') . $achar . 'zid=' . urlencode($myaddr);
else
$zurl = $s;
$arr = array('url' => $s, 'zid' => urlencode($myaddr), 'result' => $zurl);
call_hooks('zid', $arr);
return $arr['result'];
}
// Used from within PCSS themes to set theme parameters. If there's a
// puid request variable, that is the "page owner" and normally their theme
// settings take precedence; unless a local user sets the "always_my_theme"
// system pconfig, which means they don't want to see anybody else's theme
// settings except their own while on this site.
function get_theme_uid() {
$uid = (($_REQUEST['puid']) ? intval($_REQUEST['puid']) : 0);
if(local_channel()) {
if((get_pconfig(local_channel(),'system','always_my_theme')) || (! $uid))
return local_channel();
}
if(! $uid) {
$x = get_sys_channel();
if($x)
return $x['channel_id'];
}
return $uid;
}
/**
* @brief Retrieves the path of the default_profile_photo for this system
* with the specified size.
*
* @param int $size
* one of (300, 80, 48)
* @returns string
*/
function get_default_profile_photo($size = 300) {
$scheme = get_config('system','default_profile_photo');
if(! $scheme)
$scheme = 'rainbow_man';
return 'images/default_profile_photos/' . $scheme . '/' . $size . '.png';
}
/**
* @brief Test whether a given identity is NOT a member of the Hubzilla.
*
* @param string $s;
* xchan_hash of the identity in question
* @returns boolean true or false
*/
function is_foreigner($s) {
return((strpbrk($s, '.:@')) ? true : false);
}
/**
* @brief Test whether a given identity is a member of the Hubzilla.
*
* @param string $s;
* xchan_hash of the identity in question
* @returns boolean true or false
*/
function is_member($s) {
return((is_foreigner($s)) ? false : true);
}
function get_online_status($nick) {
$ret = array('result' => false);
if(get_config('system','block_public') && ! local_channel() && ! remote_channel())
return $ret;
$r = q("select channel_id, channel_hash from channel where channel_address = '%s' limit 1",
dbesc(argv(1))
);
if($r) {
$hide = get_pconfig($r[0]['channel_id'],'system','hide_online_status');
if($hide)
return $ret;
$x = q("select cp_status from chatpresence where cp_xchan = '%s' and cp_room = 0 limit 1",
dbesc($r[0]['channel_hash'])
);
if($x)
$ret['result'] = $x[0]['cp_status'];
}
return $ret;
}
function remote_online_status($webbie) {
$result = false;
$r = q("select * from hubloc where hubloc_addr = '%s' limit 1",
dbesc($webbie)
);
if(! $r)
return $result;
$url = $r[0]['hubloc_url'] . '/online/' . substr($webbie,0,strpos($webbie,'@'));
$x = z_fetch_url($url);
if($x['success']) {
$j = json_decode($x['body'],true);
if($j)
$result = (($j['result']) ? $j['result'] : false);
}
return $result;
}
function get_channel_by_nick($nick) {
$r = q("select * from channel where channel_address = '%s' limit 1",
dbesc($nick)
);
return(($r) ? $r[0] : false);
}
/**
* @brief
*
* @return string
*/
function identity_selector() {
if (local_channel()) {
$r = q("select channel.*, xchan.* from channel left join xchan on channel.channel_hash = xchan.xchan_hash where channel.channel_account_id = %d and channel_removed = 0 order by channel_name ",
intval(get_account_id())
);
if (count($r) > 1) {
//$account = get_app()->get_account();
$o = replace_macros(get_markup_template('channel_id_select.tpl'), array(
'$channels' => $r,
'$selected' => local_channel()
));
return $o;
}
}
return '';
}
function is_public_profile() {
if(! local_channel())
return false;
if(intval(get_config('system','block_public')))
return false;
$channel = get_app()->get_channel();
if($channel && $channel['channel_r_profile'] == PERMS_PUBLIC)
return true;
return false;
}
function get_profile_fields_basic($filter = 0) {
$profile_fields_basic = (($filter == 0) ? get_config('system','profile_fields_basic') : null);
if(! $profile_fields_basic)
$profile_fields_basic = array('name','pdesc','chandesc','gender','dob','dob_tz','address','locality','region','postal_code','country_name','marital','sexual','homepage','hometown','keywords','about','contact');
$x = array();
if($profile_fields_basic)
foreach($profile_fields_basic as $f)
$x[$f] = 1;
return $x;
}
function get_profile_fields_advanced($filter = 0) {
$basic = get_profile_fields_basic($filter);
$profile_fields_advanced = (($filter == 0) ? get_config('system','profile_fields_advanced') : null);
if(! $profile_fields_advanced)
$profile_fields_advanced = array('with','howlong','politic','religion','likes','dislikes','interest','channels','music','book','film','tv','romance','work','education');
$x = array();
if($basic)
foreach($basic as $f => $v)
$x[$f] = $v;
if($profile_fields_advanced)
foreach($profile_fields_advanced as $f)
$x[$f] = 1;
return $x;
}
/**
* @brief Clear notifyflags for a channel.
*
* Most likely during bulk import of content or other activity that is likely
* to generate huge amounts of undesired notifications.
*
* @param int $channel_id
* The channel to disable notifications for
* @returns int
* Current notification flag value. Send this to notifications_on() to restore the channel settings when finished
* with the activity requiring notifications_off();
*/
function notifications_off($channel_id) {
$r = q("select channel_notifyflags from channel where channel_id = %d limit 1",
intval($channel_id)
);
q("update channel set channel_notifyflags = 0 where channel_id = %d",
intval($channel_id)
);
return intval($r[0]['channel_notifyflags']);
}
function notifications_on($channel_id,$value) {
$x = q("update channel set channel_notifyflags = %d where channel_id = %d",
intval($value),
intval($channel_id)
);
return $x;
}
function get_channel_default_perms($uid) {
$r = q("select abook_my_perms from abook where abook_channel = %d and abook_self = 1 limit 1",
intval($uid)
);
if($r)
return $r[0]['abook_my_perms'];
return 0;
}