diff options
Diffstat (limited to 'include')
90 files changed, 18972 insertions, 9182 deletions
diff --git a/include/BaseObject.php b/include/BaseObject.php index 200831e15..4bfac5fa0 100644 --- a/include/BaseObject.php +++ b/include/BaseObject.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ if(class_exists('BaseObject')) return; diff --git a/include/Contact.php b/include/Contact.php index e52b5c9cf..787612c83 100644 --- a/include/Contact.php +++ b/include/Contact.php @@ -1,8 +1,102 @@ -<?php +<?php /** @file */ -function vcard_from_xchan($xchan) { +function rconnect_url($channel_id,$xchan) { + + if(! $xchan) + return ''; + + $r = q("select abook_id from abook where abook_channel = %d and abook_xchan = '%s' limit 1", + intval($channel_id), + dbesc($xchan) + ); + + if($r) + return ''; + + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($xchan) + ); + + if(($r) && ($r[0]['xchan_follow'])) + return $r[0]['xchan_follow']; + + $r = q("select hubloc_url from hubloc where hubloc_hash = '%s' and ( hubloc_flags & %d ) limit 1", + dbesc($xchan), + intval(HUBLOC_FLAGS_PRIMARY) + ); + + if($r) + return $r[0]['hubloc_url'] . '/follow?f=&url=%s'; + return ''; + +} + +function abook_connections($channel_id, $sql_conditions = '') { + $r = q("select * from abook left join xchan on abook_xchan = xchan_hash where abook_channel = %d + and not ( abook_flags & %d ) $sql_conditions", + intval($channel_id), + intval(ABOOK_FLAG_SELF) + ); + return(($r) ? $r : array()); +} + +function abook_self($channel_id) { + $r = q("select * from abook left join xchan on abook_xchan = xchan_hash where abook_channel = %d + and ( abook_flags & %d ) limit 1", + intval($channel_id), + intval(ABOOK_FLAG_SELF) + ); + return(($r) ? $r[0] : array()); +} + +function channelx_by_nick($nick) { + $r = q("SELECT * FROM channel left join xchan on channel_hash = xchan_hash WHERE channel_address = '%s' and not ( channel_pageflags & %d ) LIMIT 1", + dbesc($nick), + intval(PAGE_REMOVED) + ); + return(($r) ? $r[0] : false); +} + +function channelx_by_hash($hash) { + $r = q("SELECT * FROM channel left join xchan on channel_hash = xchan_hash WHERE channel_hash = '%s' and not ( channel_pageflags & %d ) LIMIT 1", + dbesc($hash), + intval(PAGE_REMOVED) + ); + return(($r) ? $r[0] : false); +} + +function channelx_by_n($id) { + $r = q("SELECT * FROM channel left join xchan on channel_hash = xchan_hash WHERE channel_id = %d and not ( channel_pageflags & %d ) LIMIT 1", + dbesc($id), + intval(PAGE_REMOVED) + ); + return(($r) ? $r[0] : false); +} + + +function vcard_from_xchan($xchan, $observer = null, $mode = '') { + + $a = get_app(); + + if(! $xchan) { + if($a->poi) { + $xchan = $a->poi; + } + elseif(is_array($a->profile) && $a->profile['channel_hash']) { + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($a->profile['channel_hash']) + ); + if($r) + $xchan = $r[0]; + } + } + + if(! $xchan) + return; + +// FIXME - show connect button to observer if appropriate $connect = false; if(local_user()) { $r = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", @@ -12,12 +106,23 @@ function vcard_from_xchan($xchan) { if(! $r) $connect = t('Connect'); } - + + if(array_key_exists('channel_id',$xchan)) + $a->profile_uid = $xchan['channel_id']; + + $url = (($observer) + ? z_root() . '/magic?f=&dest=' . $xchan['xchan_url'] . '&addr=' . $xchan['xchan_addr'] + : $xchan['xchan_url'] + ); + return replace_macros(get_markup_template('xchan_vcard.tpl'),array( '$name' => $xchan['xchan_name'], - '$photo' => $xchan['xchan_photo_l'], - '$url' => $xchan['xchan_addr'], - '$connect' => $connect + '$photo' => ((is_array($a->profile) && array_key_exists('photo',$a->profile)) ? $a->profile['photo'] : $xchan['xchan_photo_l']), + '$follow' => $xchan['xchan_addr'], + '$connect' => $connect, + '$newwin' => (($mode === 'chanview') ? t('New window') : ''), + '$newtit' => t('Open the selected location in a different window or browser tab'), + '$url' => $url, )); } @@ -28,113 +133,376 @@ function abook_toggle_flag($abook,$flag) { intval($abook['abook_id']), intval($abook['abook_channel']) ); - return $r; -} + // if unsetting the archive bit, update the timestamps so we'll try to connect for an additional 30 days. + if(($flag === ABOOK_FLAG_ARCHIVED) && ($abook['abook_flags'] & ABOOK_FLAG_ARCHIVED)) { + $r = q("update abook set abook_connected = '%s', abook_updated = '%s' + where abook_id = %d and abook_channel = %d limit 1", + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($abook['abook_id']), + intval($abook['abook_channel']) + ); + } + $a = get_app(); + if($a->data['abook']) + $a->data['abook']['abook_flags'] = $a->data['abook']['abook_flags'] ^ $flag; + return $r; +} +// Included here for completeness, but this is a very dangerous operation. +// It is the caller's responsibility to confirm the requestor's intent and +// authorisation to do this. +function user_remove($uid) { +} +function account_remove($account_id,$local = true) { + logger('account_remove: ' . $account_id); + if(! intval($account_id)) { + logger('account_remove: no account.'); + return false; + } + // Don't let anybody nuke the only admin account. + $r = q("select account_id from account where (account_roles & %d)", + intval(ACCOUNT_ROLE_ADMIN) + ); + if($r !== false && count($r) == 1 && $r[0]['account_id'] == $account_id) { + logger("Unable to remove the only remaining admin account"); + return false; + } + $r = q("select * from account where account_id = %d limit 1", + intval($account_id) + ); + if(! $r) { + logger('account_remove: No account with id: ' . $account_id); + return false; + } + $x = q("select channel_id from channel where channel_account_id = %d", + intval($account_id) + ); + if($x) { + foreach($x as $xx) { + channel_remove($xx['channel_id'],$local); + } + } + $r = q("delete from account where account_id = %d limit 1", + intval($account_id) + ); + return $r; +} -// Included here for completeness, but this is a very dangerous operation. -// It is the caller's responsibility to confirm the requestor's intent and -// authorisation to do this. +function channel_remove($channel_id, $local = true) { -function user_remove($uid) { - if(! $uid) + if(! $channel_id) return; $a = get_app(); - logger('Removing user: ' . $uid); + logger('Removing channel: ' . $channel_id); + logger('channel_remove: local only: ' . intval($local)); - $r = q("select * from user where uid = %d limit 1", intval($uid)); + $r = q("select * from channel where channel_id = %d limit 1", intval($channel_id)); + if(! $r) { + logger('channel_remove: channel not found: ' . $channel_id); + return; + } - call_hooks('remove_user',$r[0]); + $channel = $r[0]; - // save username (actually the nickname as it is guaranteed - // unique), so it cannot be re-registered in the future. + call_hooks('channel_remove',$r[0]); + + if(! $local) { + + $r = q("update channel set channel_deleted = '%s', channel_pageflags = (channel_pageflags | %d), channel_r_stream = 0, channel_r_profile = 0, + channel_r_photos = 0, channel_r_abook = 0, channel_w_stream = 0, channel_w_wall = 0, channel_w_tagwall = 0, + channel_w_comment = 0, channel_w_mail = 0, channel_w_photos = 0, channel_w_chat = 0, channel_a_delegate = 0, + channel_r_storage = 0, channel_w_storage = 0, channel_r_pages = 0, channel_w_pages = 0, channel_a_republish = 0 + where channel_id = %d limit 1", + dbesc(datetime_convert()), + intval(PAGE_REMOVED), + intval($channel_id) + ); - q("insert into userd ( username ) values ( '%s' )", - $r[0]['nickname'] + $r = q("update hubloc set hubloc_flags = hubloc_flags | %d where hubloc_hash = '%s'", + intval(HUBLOC_FLAGS_DELETED), + dbesc($channel['channel_hash']) + ); + + $r = q("update xchan set xchan_flags = xchan_flags | %d where xchan_hash = '%s'", + intval(XCHAN_FLAGS_DELETED), + dbesc($channel['channel_hash']) + ); + + proc_run('php','include/notifier.php','purge_all',$channel_id); + + + } + + q("DELETE FROM `groups` WHERE `uid` = %d", intval($channel_id)); + q("DELETE FROM `group_member` WHERE `uid` = %d", intval($channel_id)); + q("DELETE FROM `event` WHERE `uid` = %d", intval($channel_id)); + q("DELETE FROM `item` WHERE `uid` = %d", intval($channel_id)); + q("DELETE FROM `item_id` WHERE `uid` = %d", intval($channel_id)); + q("DELETE FROM `mail` WHERE `channel_id` = %d", intval($channel_id)); + q("DELETE FROM `notify` WHERE `uid` = %d", intval($channel_id)); + q("DELETE FROM `photo` WHERE `uid` = %d", intval($channel_id)); + q("DELETE FROM `attach` WHERE `uid` = %d", intval($channel_id)); + q("DELETE FROM `profile` WHERE `uid` = %d", intval($channel_id)); + q("DELETE FROM `pconfig` WHERE `uid` = %d", intval($channel_id)); + q("DELETE FROM `spam` WHERE `uid` = %d", intval($channel_id)); + + + q("delete from abook where abook_xchan = '%s' and abook_flags & %d limit 1", + dbesc($channel['channel_hash']), + dbesc(ABOOK_FLAG_SELF) + ); + + $r = q("update channel set channel_deleted = '%s', channel_pageflags = (channel_pageflags | %d) where channel_id = %d limit 1", + dbesc(datetime_convert()), + intval(PAGE_REMOVED), + intval($channel_id) + ); + + $r = q("update hubloc set hubloc_flags = hubloc_flags | %d where hubloc_hash = '%s' and hubloc_url = '%s' ", + intval(HUBLOC_FLAGS_DELETED), + dbesc($channel['channel_hash']), + dbesc(z_root()) ); - q("DELETE FROM `contact` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `gcign` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `group` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `group_member` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `intro` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `event` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `item` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `item_id` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `mail` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `mailacct` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `manage` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `notify` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `photo` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `attach` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `profile` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `profile_check` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `pconfig` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `search` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `spam` WHERE `uid` = %d", intval($uid)); - q("DELETE FROM `user` WHERE `uid` = %d", intval($uid)); - if($uid == local_user()) { + $r = q("update xchan set xchan_flags = xchan_flags | %d where xchan_hash = '%s' ", + intval(XCHAN_FLAGS_DELETED), + dbesc($channel['channel_hash']) + ); + + + proc_run('php','include/directory.php',$channel_id); + + if($channel_id == local_user()) { unset($_SESSION['authenticated']); unset($_SESSION['uid']); goaway($a->get_baseurl()); } + } +/** + * mark any hubs "offline" that haven't been heard from in more than 30 days + * Allow them to redeem themselves if they come back later. + * Then go through all those that are newly marked and see if any other hubs + * are attached to the controlling xchan that are still alive. + * If not, they're dead (although they could come back some day). + */ -function contact_remove($id) { - $r = q("select uid from contact where id = %d limit 1", - intval($id) - ); - if((! count($r)) || (! intval($r[0]['uid']))) +function mark_orphan_hubsxchans() { + + $dirmode = intval(get_config('system','directory_mode')); + if($dirmode == DIRECTORY_MODE_NORMAL) return; - $archive = get_pconfig($r[0]['uid'], 'system','archive_removed_contacts'); + $r = q("update hubloc set hubloc_status = (hubloc_status | %d) where not (hubloc_status & %d) + and hubloc_connected < utc_timestamp() - interval 36 day", + intval(HUBLOC_OFFLINE), + intval(HUBLOC_OFFLINE) + ); + + $r = q("select hubloc_id, hubloc_hash from hubloc where (hubloc_status & %d) and not (hubloc_flags & %d)", + intval(HUBLOC_OFFLINE), + intval(HUBLOC_FLAGS_ORPHANCHECK) + ); + + if($r) { + foreach($r as $rr) { + + // see if any other hublocs are still alive for this channel + + $x = q("select * from hubloc where hubloc_hash = '%s' and not (hubloc_status & %d)", + dbesc($rr['hubloc_hash']), + intval(HUBLOC_OFFLINE) + ); + if($x) { + + // yes - if the xchan was marked as an orphan, undo it + + $y = q("update xchan set xchan_flags = (xchan_flags ^ %d) where (xchan_flags & %d) and xchan_hash = '%s' limit 1", + intval(XCHAN_FLAGS_ORPHAN), + intval(XCHAN_FLAGS_ORPHAN), + dbesc($rr['hubloc_hash']) + ); + + } + else { + + // nope - mark the xchan as an orphan + + $y = q("update xchan set xchan_flags = (xchan_flags | %d) where xchan_hash = '%s' limit 1", + intval(XCHAN_FLAGS_ORPHAN), + dbesc($rr['hubloc_hash']) + ); + } + + // mark that we've checked this entry so we don't need to do it again + + $y = q("update hubloc set hubloc_flags = (hubloc_flags | %d) where hubloc_id = %d limit 1", + intval(HUBLOC_FLAGS_ORPHANCHECK), + dbesc($rr['hubloc_id']) + ); + } + } + +} + + + + +function remove_all_xchan_resources($xchan, $channel_id = 0) { + + if(intval($channel_id)) { + + + + } + else { + + $dirmode = intval(get_config('system','directory_mode')); + + + $r = q("delete from photo where xchan = '%s'", + dbesc($xchan) + ); + $r = q("select resource_id, resource_type, uid, id from item where ( author_xchan = '%s' or owner_xchan = '%s' ) ", + dbesc($xchan), + dbesc($xchan) + ); + if($r) { + foreach($r as $rr) { + drop_item($rr,false); + } + } + $r = q("delete from event where event_xchan = '%s'", + dbesc($xchan) + ); + $r = q("delete from group_member where xchan = '%s'", + dbesc($xchan) + ); + $r = q("delete from mail where ( from_xchan = '%s' or to_xchan = '%s' )", + dbesc($xchan), + dbesc($xchan) + ); + $r = q("delete from xlink where ( xlink_xchan = '%s' or xlink_link = '%s' )", + dbesc($xchan), + dbesc($xchan) + ); + + $r = q("delete from abook where abook_xchan = '%s'", + dbesc($xchan) + ); + + + if($dirmode === false || $dirmode == DIRECTORY_MODE_NORMAL) { + + $r = q("delete from xchan where xchan_hash = '%s' limit 1", + dbesc($xchan) + ); + $r = q("delete from hubloc where hubloc_hash = '%s'", + dbesc($xchan) + ); + + } + else { + + // directory servers need to keep the record around for sync purposes - mark it deleted + + $r = q("update hubloc set hubloc_flags = hubloc_flags | %d where hubloc_hash = '%s'", + intval(HUBLOC_FLAGS_DELETED), + dbesc($xchan) + ); + + $r = q("update xchan set xchan_flags = xchan_flags | %d where xchan_hash = '%s'", + intval(XCHAN_FLAGS_DELETED), + dbesc($xchan) + ); + } + } +} + + +function contact_remove($channel_id, $abook_id) { + + if((! $channel_id) || (! $abook_id)) + return false; + + $archive = get_pconfig($channel_id, 'system','archive_removed_contacts'); if($archive) { - q("update contact set `archive` = 1, `network` = 'none', `writable` = 0 where id = %d limit 1", - intval($id) + q("update abook set abook_flags = ( abook_flags | %d ) where abook_id = %d and abook_channel = %d limit 1", + intval(ABOOK_FLAG_ARCHIVED), + intval($abook_id), + intval($channel_id) ); - return; + return true; } - q("DELETE FROM `contact` WHERE `id` = %d LIMIT 1", - intval($id) + $r = q("select * from abook where abook_id = %d and abook_channel = %d limit 1", + intval($abook_id), + intval($channel_id) ); - q("DELETE FROM `item` WHERE `contact-id` = %d ", - intval($id) + + if(! $r) + return false; + + $abook = $r[0]; + + if($abook['abook_flags'] & ABOOK_FLAG_SELF) + return false; + + + $r = q("select * from item where author_xchan = '%s' and uid = %d", + dbesc($abook['abook_xchan']), + intval($channel_id) ); - q("DELETE FROM `photo` WHERE `contact-id` = %d ", - intval($id) + if($r) { + foreach($r as $rr) { + drop_item($rr,false); + } + } + + q("delete from abook where abook_id = %d and abook_channel = %d limit 1", + intval($abook['abook_id']), + intval($channel_id) ); - q("DELETE FROM `mail` WHERE `contact-id` = %d ", - intval($id) + + $r = q("delete from event where event_xchan = '%s' and uid = %d", + dbesc($abook['abook_xchan']), + intval($channel_id) ); - q("DELETE FROM `event` WHERE `cid` = %d ", - intval($id) + + $r = q("delete from group_member where xchan = '%s' and uid = %d", + dbesc($abook['abook_xchan']), + intval($channel_id) ); - q("DELETE FROM `queue` WHERE `cid` = %d ", - intval($id) + + $r = q("delete from mail where ( from_xchan = '%s' or to_xchan = '%s' ) and channel_id = %d ", + dbesc($abook['abook_xchan']), + dbesc($abook['abook_xchan']), + intval($channel_id) ); + return true; } @@ -206,90 +574,11 @@ function unmark_for_death($contact) { ); }} -if(! function_exists('contact_photo_menu')){ -function contact_photo_menu($contact) { - - $a = get_app(); - - $contact_url=""; - $pm_url=""; - $status_link=""; - $photos_link=""; - $posts_link=""; - $poke_link=""; - - $sparkle = false; - if($contact['xchan_network'] === NETWORK_ZOT) { - $sparkle = true; - $profile_link = $a->get_baseurl() . '/magic?f=&id=' . $contact['abook_id']; - } - else - $profile_link = $contact['xchan_url']; - - if($sparkle) { - $status_link = $profile_link . "&url=status"; - $photos_link = $profile_link . "&url=photos"; - $profile_link = $profile_link . "&url=profile"; - $pm_url = $a->get_baseurl() . '/message/new/' . $contact['xchan_hash']; - } - - $poke_link = $a->get_baseurl() . '/poke/?f=&c=' . $contact['abook_id']; - $contact_url = $a->get_baseurl() . '/connections/' . $contact['abook_id']; - $posts_link = $a->get_baseurl() . '/network/?cid=' . $contact['abook_id']; - - $menu = Array( - t("Poke") => $poke_link, - t("View Status") => $status_link, - t("View Profile") => $profile_link, - t("View Photos") => $photos_link, - t("Network Posts") => $posts_link, - t("Edit Contact") => $contact_url, - t("Send PM") => $pm_url, - ); - - - $args = array('contact' => $contact, 'menu' => &$menu); - - call_hooks('contact_photo_menu', $args); - - $o = ""; - foreach($menu as $k=>$v){ - if ($v!="") { - $o .= "<li><a href=\"$v\">$k</a></li>\n"; - } - } - return $o; -}} - - function random_profile() { - $r = q("select xchan_url from xchan where xchan_network = 'zot' order by rand() limit 1"); - if($r && count($r)) + $r = q("select xchan_url from xchan left join hubloc on hubloc_hash = xchan_hash where hubloc_connected > UTC_TIMESTAMP() - interval 30 day order by rand() limit 1"); + if($r) return $r[0]['xchan_url']; return ''; } -function contacts_not_grouped($uid,$start = 0,$count = 0) { - - if(! $count) { - $r = q("select count(*) as total from contact where uid = %d and self = 0 and id not in (select distinct(`contact-id`) from group_member where uid = %d) ", - intval($uid), - intval($uid) - ); - - return $r; - - - } - - $r = q("select * from contact where uid = %d and self = 0 and id not in (select distinct(`contact-id`) from group_member where uid = %d) and blocked = 0 and pending = 0 limit %d, %d", - intval($uid), - intval($uid), - intval($start), - intval($count) - ); - - return $r; -} - diff --git a/include/ConversationObject.php b/include/ConversationObject.php index 033ce7f76..9bf410358 100644 --- a/include/ConversationObject.php +++ b/include/ConversationObject.php @@ -1,4 +1,5 @@ -<?php +<?php /** @file */ + if(class_exists('Conversation')) return; @@ -6,22 +7,35 @@ require_once('boot.php'); require_once('include/BaseObject.php'); require_once('include/ItemObject.php'); require_once('include/text.php'); +require_once('include/items.php'); /** * A list of threads * - * We should think about making this a SPL Iterator */ + class Conversation extends BaseObject { private $threads = array(); private $mode = null; + private $observer = null; private $writable = false; + private $commentable = false; private $profile_owner = 0; private $preview = false; + private $prepared_item = ''; + private $cipher = 'aes256'; - public function __construct($mode, $preview) { + // $prepared_item is for use by alternate conversation structures such as photos + // wherein we've already prepared a top level item which doesn't look anything like + // a normal "post" item + + public function __construct($mode, $preview, $prepared_item = '') { $this->set_mode($mode); $this->preview = $preview; + $this->prepared_item = $prepared_item; + $c = ((local_user()) ? get_pconfig(local_user(),'system','default_cipher') : ''); + if($c) + $this->cipher = $c; } /** @@ -33,19 +47,32 @@ class Conversation extends BaseObject { $a = $this->get_app(); - $observer = $a->get_observer(); - $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); + $this->observer = $a->get_observer(); + $ob_hash = (($this->observer) ? $this->observer['xchan_hash'] : ''); switch($mode) { case 'network': - $this->profile_owner = local_user(); - $this->writable = true; + if(array_key_exists('firehose',$a->data) && intval($a->data['firehose'])) { + $this->profile_owner = intval($a->data['firehose']); + $this->writable = false; + } + else { + $this->profile_owner = local_user(); + $this->writable = true; + } break; case 'channel': $this->profile_owner = $a->profile['profile_uid']; $this->writable = perm_is_allowed($this->profile_owner,$ob_hash,'post_comments'); break; case 'display': + // in this mode we set profile_owner after initialisation (from conversation()) and then + // pull some trickery which allows us to re-invoke this function afterward + // it's an ugly hack so FIXME +// $this->profile_owner = $a->profile['uid']; + $this->writable = perm_is_allowed($this->profile_owner,$ob_hash,'post_comments'); + break; + case 'page': $this->profile_owner = $a->profile['uid']; $this->writable = perm_is_allowed($this->profile_owner,$ob_hash,'post_comments'); break; @@ -71,6 +98,10 @@ class Conversation extends BaseObject { return $this->writable; } + public function is_commentable() { + return $this->commentable; + } + /** * Check if page is a preview */ @@ -85,6 +116,22 @@ class Conversation extends BaseObject { return $this->profile_owner; } + public function set_profile_owner($uid) { + $this->profile_owner = $uid; + $mode = $this->get_mode(); + $this->mode = null; + $this->set_mode($mode); + } + + public function get_observer() { + return $this->observer; + } + + public function get_cipher() { + return $this->cipher; + } + + /** * Add a thread to the conversation * @@ -104,12 +151,43 @@ class Conversation extends BaseObject { } /* - * Only add will be displayed + * Only add things that will be displayed */ - if(activity_match($item->get_data_value('verb'),ACTIVITY_LIKE) || activity_match($item->get_data_value('verb'),ACTIVITY_DISLIKE)) { + + if(($item->get_data_value('id') != $item->get_data_value('parent')) && (activity_match($item->get_data_value('verb'),ACTIVITY_LIKE) || activity_match($item->get_data_value('verb'),ACTIVITY_DISLIKE))) { return false; } + +// if(local_user() && $item->get_data_value('uid') == local_user()) +// $this->commentable = true; + +// if($this->writable) +// $this->commentable = true; + + $item->set_commentable(false); + $ob_hash = (($this->observer) ? $this->observer['xchan_hash'] : ''); + + if(($item->get_data_value('author_xchan') === $ob_hash) || ($item->get_data_value('owner_xchan') === $ob_hash)) + $item->set_commentable(true); + + if($item->get_data_value('item_flags') & ITEM_NOCOMMENT) { + $item->set_commentable(false); + } + elseif(($this->observer) && (! $item->is_commentable())) { + if((array_key_exists('owner',$item->data)) && ($item->data['owner']['abook_flags'] & ABOOK_FLAG_SELF)) + $item->set_commentable(perm_is_allowed($this->profile_owner,$this->observer['xchan_hash'],'post_comments')); + else + $item->set_commentable(can_comment_on_post($this->observer['xchan_hash'],$item->data)); + } + + require_once('include/identity.php'); + $sys = get_sys_channel(); + + if($sys && $item->get_data_value('uid') == $sys['channel_id']) { + $item->set_commentable(false); + } + $item->set_conversation($this); $this->threads[] = $item; return end($this->threads); @@ -129,7 +207,12 @@ class Conversation extends BaseObject { foreach($this->threads as $item) { - $item_data = $item->get_template_data($alike, $dlike); + if(($item->get_data_value('id') == $item->get_data_value('parent')) && $this->prepared_item) { + $item_data = $this->prepared_item; + } + else { + $item_data = $item->get_template_data($alike, $dlike); + } if(!$item_data) { logger('[ERROR] Conversation::get_template_data : Failed to get item template data ('. $item->get_id() .').', LOGGER_DEBUG); return false; diff --git a/include/ITemplateEngine.php b/include/ITemplateEngine.php new file mode 100755 index 000000000..53c1845f4 --- /dev/null +++ b/include/ITemplateEngine.php @@ -0,0 +1,11 @@ +<?php
+require_once 'boot.php';
+
+
+/**
+ * Interface for template engines
+ */
+interface ITemplateEngine {
+ public function replace_macros($s,$v);
+ public function get_markup_template($file, $root='');
+}
diff --git a/include/ItemObject.php b/include/ItemObject.php index 45ea6860a..a5870ef91 100644 --- a/include/ItemObject.php +++ b/include/ItemObject.php @@ -1,20 +1,22 @@ -<?php +<?php /** @file */ + if(class_exists('Item')) return; -require_once('object/BaseObject.php'); +require_once('include/BaseObject.php'); require_once('include/text.php'); require_once('boot.php'); /** * An item */ + class Item extends BaseObject { - private $data = array(); + public $data = array(); private $template = 'conv_item.tpl'; private $comment_box_template = 'comment_item.tpl'; + private $commentable = false; private $toplevel = false; - private $writable = false; private $children = array(); private $parent = null; private $conversation = null; @@ -25,46 +27,27 @@ class Item extends BaseObject { private $wall_to_wall = false; private $threaded = false; private $visiting = false; - private $observer = null; private $channel = null; + public function __construct($data) { $a = $this->get_app(); $this->data = $data; - $this->channel = $a->get_channel(); - $this->observer = $a->get_observer(); - $this->toplevel = ($this->get_id() == $this->get_data_value('parent')); -// if(is_array($_SESSION['remote'])) { -// foreach($_SESSION['remote'] as $visitor) { -// if($visitor['cid'] == $this->get_data_value('contact-id')) { -// $this->visiting = true; -// break; -// } -// } -// } - -// fixme - $this->writable = ($this->get_data_value('writable') || $this->get_data_value('self')); -// FIXME - base this on observer permissions - $this->writable = ((local_user() && $channel['channel_hash'] === $item['owner_xchan']) ? true : false); - - - - if(get_config('system','thread_allow') && $a->theme_thread_allow && !$this->is_toplevel()) - $this->threaded = true; - // Prepare the children if(count($data['children'])) { foreach($data['children'] as $item) { + /* - * Only add will be displayed + * Only add those that will be displayed */ - if(! visible_activity($item)) { + + if((! visible_activity($item)) || array_key_exists('author_blocked',$item)) { continue; } + $child = new Item($item); $this->add_child($child); } @@ -80,10 +63,12 @@ class Item extends BaseObject { */ public function get_template_data($alike, $dlike, $thread_level=1) { + + $t1 = dba_timer(); + $result = array(); $a = $this->get_app(); - $observer = $this->observer; $item = $this->get_data(); $commentww = ''; @@ -91,28 +76,30 @@ class Item extends BaseObject { $buttons = ''; $dropping = false; $star = false; - $isstarred = "unstarred"; + $isstarred = "unstarred icon-star-empty"; $indent = ''; $osparkle = ''; $total_children = $this->count_descendants(); $conv = $this->get_conversation(); + $observer = $conv->get_observer(); - $lock = ((($item['private'] == 1) || (($item['uid'] == local_user()) && (strlen($item['allow_cid']) || strlen($item['allow_gid']) + $lock = ((($item['item_private'] == 1) || (($item['uid'] == local_user()) && (strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])))) ? t('Private Message') : false); - $shareable = ((($conv->get_profile_owner() == local_user()) && ($item['private'] != 1)) ? true : false); + $shareable = ((($conv->get_profile_owner() == local_user()) && ($item['item_private'] != 1)) ? true : false); + + $mode = $conv->get_mode(); - if(local_user() && link_compare($a->contact['url'],$item['author-link'])) + if(local_user() && $observer['xchan_hash'] === $item['author_xchan']) $edpost = array($a->get_baseurl($ssl_state)."/editpost/".$item['id'], t("Edit")); else $edpost = false; -// FIXME - this is wrong. -// if(($this->get_data_value('uid') == local_user()) || $this->is_visiting()) - - if($this->get_data_value('uid') == local_user()) + if($observer['xchan_hash'] == $this->get_data_value('author_xchan') + || $observer['xchan_hash'] == $this->get_data_value('owner_xchan') + || $this->get_data_value('uid') == local_user()) $dropping = true; if($dropping) { @@ -121,131 +108,185 @@ class Item extends BaseObject { 'delete' => t('Delete'), ); } - +// FIXME if($observer_is_pageowner) { $multidrop = array( 'select' => t('Select'), ); } - $filer = (($conv->get_profile_owner() == local_user()) ? t("save to folder") : false); - - $diff_author = ((link_compare($item['url'],$item['author-link'])) ? false : true); - $profile_name = (((strlen($item['author-name'])) && $diff_author) ? $item['author-name'] : $item['name']); - - $profile_avatar = $item['author']['xchan_photo_m']; - $profile_link = $a->get_baseurl() . '/chanview/?f=&url=' . $item['author']['xchan_url']; - $profile_name = $item['author']['xchan_name']; - -// if($item['author-link'] && (! $item['author-name'])) -// $profile_name = $item['author-link']; - + $filer = (($conv->get_profile_owner() == local_user()) ? t("Save to Folder") : false); $profile_avatar = $item['author']['xchan_photo_m']; + $profile_link = chanlink_url($item['author']['xchan_url']); + $profile_name = $item['author']['xchan_name']; - $locate = array('location' => $item['location'], 'coord' => $item['coord'], 'html' => ''); - call_hooks('render_location',$locate); - $location = ((strlen($locate['html'])) ? $locate['html'] : render_location_google($locate)); + $location = format_location($item); - $tags=array(); - foreach(explode(',',$item['tag']) as $tag){ - $tag = trim($tag); - if ($tag!="") $tags[] = bbcode($tag); + $like_count = ((x($alike,$item['mid'])) ? $alike[$item['mid']] : ''); + $like_list = ((x($alike,$item['mid'])) ? $alike[$item['mid'] . '-l'] : ''); + if (count($like_list) > MAX_LIKERS) { + $like_list_part = array_slice($like_list, 0, MAX_LIKERS); + array_push($like_list_part, '<a href="#" data-toggle="modal" data-target="#likeModal-' . $this->get_id() . '"><b>' . t('View all') . '</b></a>'); + } else { + $like_list_part = ''; + } + $like_button_label = tt('Like','Likes',$like_count,'noun'); + + if (feature_enabled($conv->get_profile_owner(),'dislike')) { + $dislike_count = ((x($dlike,$item['mid'])) ? $dlike[$item['mid']] : ''); + $dislike_list = ((x($dlike,$item['mid'])) ? $dlike[$item['mid'] . '-l'] : ''); + $dislike_button_label = tt('Dislike','Dislikes',$dislike_count,'noun'); + if (count($dislike_list) > MAX_LIKERS) { + $dislike_list_part = array_slice($dislike_list, 0, MAX_LIKERS); + array_push($dislike_list_part, '<a href="#" data-toggle="modal" data-target="#dislikeModal-' . $this->get_id() . '"><b>' . t('View all') . '</b></a>'); + } else { + $dislike_list_part = ''; + } } - $showlike = ((x($alike,$item['uri'])) ? format_like($alike[$item['uri']],$alike[$item['uri'] . '-l'],'like',$item['uri']) : ''); - $showdislike = ((x($dlike,$item['uri']) && feature_enabled($conv->get_profile_owner(),'dislike')) ? format_like($dlike[$item['uri']],$dlike[$item['uri'] . '-l'],'dislike',$item['uri']) : ''); + $showlike = ((x($alike,$item['mid'])) ? format_like($alike[$item['mid']],$alike[$item['mid'] . '-l'],'like',$item['mid']) : ''); + $showdislike = ((x($dlike,$item['mid']) && feature_enabled($conv->get_profile_owner(),'dislike')) + ? format_like($dlike[$item['mid']],$dlike[$item['mid'] . '-l'],'dislike',$item['mid']) : ''); /* * We should avoid doing this all the time, but it depends on the conversation mode * And the conv mode may change when we change the conv, or it changes its mode * Maybe we should establish a way to be notified about conversation changes */ + $this->check_wall_to_wall(); - if($this->is_wall_to_wall() && ($this->get_owner_url() == $this->get_redirect_url())) - $osparkle = ' sparkle'; - if($this->is_toplevel()) { + // FIXME check this permission if($conv->get_profile_owner() == local_user()) { // FIXME we don't need all this stuff, some can be done in the template $star = array( - 'do' => t("add star"), - 'undo' => t("remove star"), - 'toggle' => t("toggle star status"), + 'do' => t("Add Star"), + 'undo' => t("Remove Star"), + 'toggle' => t("Toggle Star Status"), 'classdo' => (($item['item_flags'] & ITEM_STARRED) ? "hidden" : ""), 'classundo' => (($item['item_flags'] & ITEM_STARRED) ? "" : "hidden"), - 'isstarred' => (($item['item_flags'] & ITEM_STARRED) ? "starred" : "unstarred"), + 'isstarred' => (($item['item_flags'] & ITEM_STARRED) ? "starred icon-star" : "unstarred icon-star-empty"), 'starred' => t('starred'), ); - $tagger = array( - 'tagit' => t("add tag"), - 'classtagger' => "", - ); } } else { $indent = 'comment'; } - if($conv->is_writable()) { + + $verified = (($item['item_flags'] & ITEM_VERIFIED) ? t('Message is verified') : ''); + $unverified = '' ; // (($this->is_wall_to_wall() && (! ($item['item_flags'] & ITEM_VERIFIED))) ? t('Message cannot be verified') : ''); + + + + // FIXME - check this permission + if($conv->get_profile_owner() == local_user()) { + $tagger = array( + 'tagit' => t("Add Tag"), + 'classtagger' => "", + ); + } + + $has_bookmarks = false; + if(is_array($item['term'])) { + foreach($item['term'] as $t) { + if($t['type'] == TERM_BOOKMARK) + $has_bookmarks = true; + } + } + + $has_event = false; + if(($item['obj_type'] === ACTIVITY_OBJ_EVENT) && $conv->get_profile_owner() == local_user()) + $has_event = true; + + if($this->is_commentable()) { $like = array( t("I like this \x28toggle\x29"), t("like")); $dislike = array( t("I don't like this \x28toggle\x29"), t("dislike")); if ($shareable) - $share = array( t('Share this'), t('share')); + $share = array( t('Share This'), t('share')); } if(strcmp(datetime_convert('UTC','UTC',$item['created']),datetime_convert('UTC','UTC','now - 12 hours')) > 0) $indent .= ' shiny'; + $t2 = dba_timer(); + localize_item($item); + $t3 = dba_timer(); + $body = prepare_body($item,true); + $t4 = dba_timer(); + $tmp_item = array( 'template' => $this->get_template(), - + 'mode' => $mode, 'type' => implode("",array_slice(explode("/",$item['verb']),-1)), - 'tags' => $tags, + 'tags' => array(), 'body' => $body, - 'text' => strip_tags(template_escape($body)), + 'text' => strip_tags($body), 'id' => $this->get_id(), - 'linktitle' => sprintf( t('View %s\'s profile @ %s'), $profile_name, ((strlen($item['author-link'])) ? $item['author-link'] : $item['url'])), - 'olinktitle' => sprintf( t('View %s\'s profile @ %s'), $this->get_owner_name(), ((strlen($item['owner-link'])) ? $item['owner-link'] : $item['url'])), + 'linktitle' => sprintf( t('View %s\'s profile - %s'), $profile_name, $item['author']['xchan_addr']), + 'olinktitle' => sprintf( t('View %s\'s profile - %s'), $this->get_owner_name(), $item['owner']['xchan_addr']), 'to' => t('to'), + 'via' => t('via'), 'wall' => t('Wall-to-Wall'), 'vwall' => t('via Wall-To-Wall:'), 'profile_url' => $profile_link, 'item_photo_menu' => item_photo_menu($item), - 'name' => template_escape($profile_name), + 'name' => $profile_name, 'thumb' => $profile_avatar, 'osparkle' => $osparkle, 'sparkle' => $sparkle, - 'title' => template_escape($item['title']), + 'title' => $item['title'], + 'ago' => relative_date($item['created']), + 'app' => $item['app'], + 'str_app' => sprintf( t(' from %s'), $item['app']), + 'isotime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'c'), 'localtime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'r'), - 'ago' => (($item['app']) ? sprintf( t('%s from %s'),relative_date($item['created']),$item['app']) : relative_date($item['created'])), + 'editedtime' => (($item['edited'] != $item['created']) ? sprintf( t('last edited: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['edited'], 'r')) : ''), + 'expiretime' => (($item['expires'] !== '0000-00-00 00:00:00') ? sprintf( t('Expires: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['expires'], 'r')):''), 'lock' => $lock, - 'location' => template_escape($location), + 'verified' => $verified, + 'unverified' => $unverified, + 'location' => $location, 'indent' => $indent, 'owner_url' => $this->get_owner_url(), 'owner_photo' => $this->get_owner_photo(), - 'owner_name' => template_escape($this->get_owner_name()), + 'owner_name' => $this->get_owner_name(), // Item toolbar buttons 'like' => $like, 'dislike' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike : ''), 'share' => $share, + 'rawmid' => $item['mid'], 'plink' => get_plink($item), 'edpost' => ((feature_enabled($conv->get_profile_owner(),'edit_posts')) ? $edpost : ''), 'star' => ((feature_enabled($conv->get_profile_owner(),'star_posts')) ? $star : ''), 'tagger' => ((feature_enabled($conv->get_profile_owner(),'commtag')) ? $tagger : ''), 'filer' => ((feature_enabled($conv->get_profile_owner(),'filing')) ? $filer : ''), + 'bookmark' => (($conv->get_profile_owner() == local_user() && $has_bookmarks) ? t('Save Bookmarks') : ''), + 'addtocal' => (($has_event) ? t('Add to Calendar') : ''), 'drop' => $drop, 'multidrop' => ((feature_enabled($conv->get_profile_owner(),'multi_delete')) ? $multidrop : ''), // end toolbar buttons - + 'like_count' => $like_count, + 'like_list' => $like_list, + 'like_list_part' => $like_list_part, + 'like_button_label' => $like_button_label, + 'like_modal_title' => t('Likes','noun'), + 'dislike_modal_title' => t('Dislikes','noun'), + 'dislike_count' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike_count : ''), + 'dislike_list' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike_list : ''), + 'dislike_list_part' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike_list_part : ''), + 'dislike_button_label' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike_button_label : ''), + 'modal_dismiss' => t('Close'), 'showlike' => $showlike, 'showdislike' => $showdislike, 'comment' => $this->get_comment_box($indent), @@ -254,6 +295,8 @@ class Item extends BaseObject { 'thread_level' => $thread_level ); + $t5 = dba_timer(); + $arr = array('item' => $item, 'output' => $tmp_item); call_hooks('display_item', $arr); @@ -270,7 +313,7 @@ class Item extends BaseObject { if(($nb_children > 2) || ($thread_level > 1)) { $result['children'][0]['comment_firstcollapsed'] = true; $result['children'][0]['num_comments'] = sprintf( tt('%d comment','%d comments',$total_children),$total_children ); - $result['children'][0]['hide_text'] = t('show more'); + $result['children'][0]['hide_text'] = t('[+] show all'); if($thread_level > 1) { $result['children'][$nb_children - 1]['comment_lastcollapsed'] = true; } @@ -280,7 +323,7 @@ class Item extends BaseObject { } } - $result['private'] = $item['private']; + $result['private'] = $item['item_private']; $result['toplevel'] = ($this->is_toplevel() ? 'toplevel_item' : ''); if($this->is_threaded()) { @@ -291,6 +334,14 @@ class Item extends BaseObject { $result['flatten'] = true; $result['threaded'] = false; } + $t6 = dba_timer(); + +// profiler($t1,$t2,'t2'); +// profiler($t2,$t3,'t3'); +// profiler($t3,$t4,'t4'); +// profiler($t4,$t5,'t5'); +// profiler($t5,$t6,'t6'); +// profiler($t1,$t6,'item total'); return $result; } @@ -303,6 +354,16 @@ class Item extends BaseObject { return $this->threaded; } + public function set_commentable($val) { + $this->commentable = $val; + foreach($this->get_children() as $child) + $child->set_commentable($val); + } + + public function is_commentable() { + return $this->commentable; + } + /** * Add a child item */ @@ -444,6 +505,11 @@ class Item extends BaseObject { return $this->template; } + + private function set_template($t) { + $this->template = $t; + } + /** * Check if this is a toplevel post */ @@ -452,22 +518,6 @@ class Item extends BaseObject { } /** - * Check if this is writable - */ - private function is_writable() { - $conv = $this->get_conversation(); - - return true; - - if($conv) { - // This will allow us to comment on wall-to-wall items owned by our friends - // and community forums even if somebody else wrote the post. - return ($this->writable || ($this->is_visiting() && $conv->get_mode() == 'channel')); - } - return $this->writable; - } - - /** * Count the total of our descendants */ private function count_descendants() { @@ -496,57 +546,57 @@ class Item extends BaseObject { * _ false on failure */ private function get_comment_box($indent) { + if(!$this->is_toplevel() && !get_config('system','thread_allow')) { return ''; } $comment_box = ''; $conv = $this->get_conversation(); + +// logger('Commentable conv: ' . $conv->is_commentable()); + + if(! $this->is_commentable()) + return; + $template = get_markup_template($this->get_comment_box_template()); - $ww = ''; - if( ($conv->get_mode() === 'network') && $this->is_wall_to_wall() ) - $ww = 'ww'; - - if($conv->is_writable() && $this->is_writable()) { - $a = $this->get_app(); - $qc = $qcomment = null; - - /* - * Hmmm, code depending on the presence of a particular plugin? - * This should be better if done by a hook - */ - if(in_array('qcomment',$a->plugins)) { - $qc = ((local_user()) ? get_pconfig(local_user(),'qcomment','words') : null); - $qcomment = (($qc) ? explode("\n",$qc) : null); - } - $comment_box = replace_macros($template,array( - '$return_path' => '', - '$threaded' => $this->is_threaded(), - '$jsreload' => (($conv->get_mode() === 'display') ? $_SESSION['return_url'] : ''), - '$type' => (($conv->get_mode() === 'channel') ? 'wall-comment' : 'net-comment'), - '$id' => $this->get_id(), - '$parent' => $this->get_id(), - '$qcomment' => $qcomment, - '$profile_uid' => $conv->get_profile_owner(), - '$mylink' => $this->observer['xchan_url'], - '$mytitle' => t('This is you'), - '$myphoto' => $this->observer['xchan_photo_s'], - '$comment' => t('Comment'), - '$submit' => t('Submit'), - '$edbold' => t('Bold'), - '$editalic' => t('Italic'), - '$eduline' => t('Underline'), - '$edquote' => t('Quote'), - '$edcode' => t('Code'), - '$edimg' => t('Image'), - '$edurl' => t('Link'), - '$edvideo' => t('Video'), - '$preview' => ((feature_enabled($conv->get_profile_owner(),'preview')) ? t('Preview') : ''), - '$indent' => $indent, - '$sourceapp' => t($a->sourcename), - '$ww' => (($conv->get_mode() === 'network') ? $ww : '') - )); - } + + $a = $this->get_app(); + $observer = $conv->get_observer(); + + $qc = ((local_user()) ? get_pconfig(local_user(),'system','qcomment') : null); + $qcomment = (($qc) ? explode("\n",$qc) : null); + + $comment_box = replace_macros($template,array( + '$return_path' => '', + '$threaded' => $this->is_threaded(), + '$jsreload' => (($conv->get_mode() === 'display') ? $_SESSION['return_url'] : ''), + '$type' => (($conv->get_mode() === 'channel') ? 'wall-comment' : 'net-comment'), + '$id' => $this->get_id(), + '$parent' => $this->get_id(), + '$qcomment' => $qcomment, + '$profile_uid' => $conv->get_profile_owner(), + '$mylink' => $observer['xchan_url'], + '$mytitle' => t('This is you'), + '$myphoto' => $observer['xchan_photo_s'], + '$comment' => t('Comment'), + '$submit' => t('Submit'), + '$edbold' => t('Bold'), + '$editalic' => t('Italic'), + '$eduline' => t('Underline'), + '$edquote' => t('Quote'), + '$edcode' => t('Code'), + '$edimg' => t('Image'), + '$edurl' => t('Link'), + '$edvideo' => t('Video'), + '$preview' => ((feature_enabled($conv->get_profile_owner(),'preview')) ? t('Preview') : ''), + '$indent' => $indent, + '$feature_encrypt' => ((feature_enabled($conv->get_profile_owner(),'content_encrypt')) ? true : false), + '$encrypt' => t('Encrypt text'), + '$cipher' => $conv->get_cipher(), + '$sourceapp' => get_app()->sourcename + + )); return $comment_box; } @@ -559,59 +609,20 @@ class Item extends BaseObject { * Check if we are a wall to wall item and set the relevant properties */ protected function check_wall_to_wall() { - $a = $this->get_app(); $conv = $this->get_conversation(); $this->wall_to_wall = false; - - if($this->is_toplevel()) { - if( (! $this->get_data_value('self')) && ($conv->get_mode() !== 'channel')) { - if($this->get_data_value('wall')) { - - // On the network page, I am the owner. On the display page it will be the profile owner. - // This will have been stored in $a->page_contact by our calling page. - // Put this person as the wall owner of the wall-to-wall notice. + $this->owner_url = ''; + $this->owner_photo = ''; + $this->owner_name = ''; - $this->owner_url = zid($a->page_contact['url']); - $this->owner_photo = $a->page_contact['thumb']; - $this->owner_name = $a->page_contact['name']; - $this->wall_to_wall = true; - } - else if($this->get_data_value('owner-link')) { - - $owner_linkmatch = (($this->get_data_value('owner-link')) && link_compare($this->get_data_value('owner-link'),$this->get_data_value('author-link'))); - $alias_linkmatch = (($this->get_data_value('alias')) && link_compare($this->get_data_value('alias'),$this->get_data_value('author-link'))); - $owner_namematch = (($this->get_data_value('owner-name')) && $this->get_data_value('owner-name') == $this->get_data_value('author-name')); - if((! $owner_linkmatch) && (! $alias_linkmatch) && (! $owner_namematch)) { - - // The author url doesn't match the owner (typically the contact) - // and also doesn't match the contact alias. - // The name match is a hack to catch several weird cases where URLs are - // all over the park. It can be tricked, but this prevents you from - // seeing "Bob Smith to Bob Smith via Wall-to-wall" and you know darn - // well that it's the same Bob Smith. - - // But it could be somebody else with the same name. It just isn't highly likely. - - - $this->owner_photo = $this->get_data_value('owner-avatar'); - $this->owner_name = $this->get_data_value('owner-name'); - $this->wall_to_wall = true; - // If it is our contact, use a friendly redirect link - if((link_compare($this->get_data_value('owner-link'),$this->get_data_value('url'))) - && ($this->get_data_value('network') === NETWORK_DFRN)) { - $this->owner_url = $this->get_redirect_url(); - } - else - $this->owner_url = zid($this->get_data_value('owner-link')); - } - } - } - } - - if(!$this->wall_to_wall) { - $this->owner_url = ''; - $this->owner_photo = ''; - $this->owner_name = ''; + if($conv->get_mode() === 'channel') + return; + + if($this->is_toplevel() && ($this->get_data_value('author_xchan') != $this->get_data_value('owner_xchan'))) { + $this->owner_url = chanlink_url($this->data['owner']['xchan_url']); + $this->owner_photo = $this->data['owner']['xchan_photo_m']; + $this->owner_name = $this->data['owner']['xchan_name']; + $this->wall_to_wall = true; } } diff --git a/include/Photo.php b/include/Photo.php deleted file mode 100644 index c3165cc9f..000000000 --- a/include/Photo.php +++ /dev/null @@ -1,707 +0,0 @@ -<?php - -if(! class_exists("Photo")) { -class Photo { - - private $image; - - /** - * Put back gd stuff, not everybody have Imagick - */ - private $imagick; - private $width; - private $height; - private $valid; - private $type; - private $types; - - /** - * supported mimetypes and corresponding file extensions - */ - static function supportedTypes() { - if(class_exists('Imagick')) { - /** - * Imagick::queryFormats won't help us a lot there... - * At least, not yet, other parts of friendica uses this array - */ - $t = array( - 'image/jpeg' => 'jpg', - 'image/png' => 'png', - 'image/gif' => 'gif' - ); - } else { - $t = array(); - $t['image/jpeg'] ='jpg'; - if (imagetypes() & IMG_PNG) $t['image/png'] = 'png'; - } - - return $t; - } - - public function __construct($data, $type=null) { - $this->imagick = class_exists('Imagick'); - $this->types = $this->supportedTypes(); - if (!array_key_exists($type,$this->types)){ - $type='image/jpeg'; - } - $this->type = $type; - - if($this->is_imagick()) { - $this->image = new Imagick(); - $this->image->readImageBlob($data); - - /** - * Setup the image to the format it will be saved to - */ - $map = $this->get_FormatsMap(); - $format = $map[$type]; - $this->image->setFormat($format); - - // Always coalesce, if it is not a multi-frame image it won't hurt anyway - $this->image = $this->image->coalesceImages(); - - /** - * setup the compression here, so we'll do it only once - */ - switch($this->getType()){ - case "image/png": - $quality = get_config('system','png_quality'); - if((! $quality) || ($quality > 9)) - $quality = PNG_QUALITY; - /** - * From http://www.imagemagick.org/script/command-line-options.php#quality: - * - * 'For the MNG and PNG image formats, the quality value sets - * the zlib compression level (quality / 10) and filter-type (quality % 10). - * The default PNG "quality" is 75, which means compression level 7 with adaptive PNG filtering, - * unless the image has a color map, in which case it means compression level 7 with no PNG filtering' - */ - $quality = $quality * 10; - $this->image->setCompressionQuality($quality); - break; - case "image/jpeg": - $quality = get_config('system','jpeg_quality'); - if((! $quality) || ($quality > 100)) - $quality = JPEG_QUALITY; - $this->image->setCompressionQuality($quality); - } - } else { - $this->valid = false; - $this->image = @imagecreatefromstring($data); - if($this->image !== FALSE) { - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - $this->valid = true; - imagealphablending($this->image, false); - imagesavealpha($this->image, true); - } - } - } - - public function __destruct() { - if($this->image) { - if($this->is_imagick()) { - $this->image->clear(); - $this->image->destroy(); - return; - } - imagedestroy($this->image); - } - } - - public function is_imagick() { - return $this->imagick; - } - - /** - * Maps Mime types to Imagick formats - */ - public function get_FormatsMap() { - $m = array( - 'image/jpeg' => 'JPG', - 'image/png' => 'PNG', - 'image/gif' => 'GIF' - ); - return $m; - } - - public function is_valid() { - if($this->is_imagick()) - return ($this->image !== FALSE); - return $this->valid; - } - - public function getWidth() { - if(!$this->is_valid()) - return FALSE; - - if($this->is_imagick()) - return $this->image->getImageWidth(); - return $this->width; - } - - public function getHeight() { - if(!$this->is_valid()) - return FALSE; - - if($this->is_imagick()) - return $this->image->getImageHeight(); - return $this->height; - } - - public function getImage() { - if(!$this->is_valid()) - return FALSE; - - if($this->is_imagick()) { - /* Clean it */ - $this->image = $this->image->deconstructImages(); - return $this->image; - } - return $this->image; - } - - public function getType() { - if(!$this->is_valid()) - return FALSE; - - return $this->type; - } - - public function getExt() { - if(!$this->is_valid()) - return FALSE; - - return $this->types[$this->getType()]; - } - - public function scaleImage($max) { - if(!$this->is_valid()) - return FALSE; - - $width = $this->width; - $height = $this->height; - - $dest_width = $dest_height = 0; - - if((! $width)|| (! $height)) - return FALSE; - - if($width > $max && $height > $max) { - - // very tall image (greater than 16:9) - // constrain the width - let the height float. - - if((($height * 9) / 16) > $width) { - $dest_width = $max; - $dest_height = intval(( $height * $max ) / $width); - } - - // else constrain both dimensions - - elseif($width > $height) { - $dest_width = $max; - $dest_height = intval(( $height * $max ) / $width); - } - else { - $dest_width = intval(( $width * $max ) / $height); - $dest_height = $max; - } - } - else { - if( $width > $max ) { - $dest_width = $max; - $dest_height = intval(( $height * $max ) / $width); - } - else { - if( $height > $max ) { - - // very tall image (greater than 16:9) - // but width is OK - don't do anything - - if((($height * 9) / 16) > $width) { - $dest_width = $width; - $dest_height = $height; - } - else { - $dest_width = intval(( $width * $max ) / $height); - $dest_height = $max; - } - } - else { - $dest_width = $width; - $dest_height = $height; - } - } - } - - - if($this->is_imagick()) { - /** - * If it is not animated, there will be only one iteration here, - * so don't bother checking - */ - // Don't forget to go back to the first frame - $this->image->setFirstIterator(); - do { - - // FIXME - implement horizantal bias for scaling as in followin GD functions - // to allow very tall images to be constrained only horizontally. - - $this->image->scaleImage($dest_width, $dest_height); - } while ($this->image->nextImage()); - - // FIXME - also we need to copy the new dimensions to $this->height, $this->width as other functions - // may rely on it. - - return; - } - - - $dest = imagecreatetruecolor( $dest_width, $dest_height ); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height); - if($this->image) - imagedestroy($this->image); - $this->image = $dest; - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } - - public function rotate($degrees) { - if(!$this->is_valid()) - return FALSE; - - if($this->is_imagick()) { - $this->image->setFirstIterator(); - do { - $this->image->rotateImage(new ImagickPixel(), -$degrees); // ImageMagick rotates in the opposite direction of imagerotate() - } while ($this->image->nextImage()); - return; - } - - $this->image = imagerotate($this->image,$degrees,0); - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } - - public function flip($horiz = true, $vert = false) { - if(!$this->is_valid()) - return FALSE; - - if($this->is_imagick()) { - $this->image->setFirstIterator(); - do { - if($horiz) $this->image->flipImage(); - if($vert) $this->image->flopImage(); - } while ($this->image->nextImage()); - return; - } - - $w = imagesx($this->image); - $h = imagesy($this->image); - $flipped = imagecreate($w, $h); - if($horiz) { - for ($x = 0; $x < $w; $x++) { - imagecopy($flipped, $this->image, $x, 0, $w - $x - 1, 0, 1, $h); - } - } - if($vert) { - for ($y = 0; $y < $h; $y++) { - imagecopy($flipped, $this->image, 0, $y, 0, $h - $y - 1, $w, 1); - } - } - $this->image = $flipped; - } - - public function orient($filename) { - // based off comment on http://php.net/manual/en/function.imagerotate.php - - if(!$this->is_valid()) - return FALSE; - - if( (! function_exists('exif_read_data')) || ($this->getType() !== 'image/jpeg') ) - return; - - $exif = exif_read_data($filename); - $ort = $exif['Orientation']; - - switch($ort) - { - case 1: // nothing - break; - - case 2: // horizontal flip - $this->flip(); - break; - - case 3: // 180 rotate left - $this->rotate(180); - break; - - case 4: // vertical flip - $this->flip(false, true); - break; - - case 5: // vertical flip + 90 rotate right - $this->flip(false, true); - $this->rotate(-90); - break; - - case 6: // 90 rotate right - $this->rotate(-90); - break; - - case 7: // horizontal flip + 90 rotate right - $this->flip(); - $this->rotate(-90); - break; - - case 8: // 90 rotate left - $this->rotate(90); - break; - } - } - - - - public function scaleImageUp($min) { - if(!$this->is_valid()) - return FALSE; - - - $width = $this->width; - $height = $this->height; - - $dest_width = $dest_height = 0; - - if((! $width)|| (! $height)) - return FALSE; - - if($width < $min && $height < $min) { - if($width > $height) { - $dest_width = $min; - $dest_height = intval(( $height * $min ) / $width); - } - else { - $dest_width = intval(( $width * $min ) / $height); - $dest_height = $min; - } - } - else { - if( $width < $min ) { - $dest_width = $min; - $dest_height = intval(( $height * $min ) / $width); - } - else { - if( $height < $min ) { - $dest_width = intval(( $width * $min ) / $height); - $dest_height = $min; - } - else { - $dest_width = $width; - $dest_height = $height; - } - } - } - - if($this->is_imagick()) - return $this->scaleImage($dest_width,$dest_height); - - $dest = imagecreatetruecolor( $dest_width, $dest_height ); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height); - if($this->image) - imagedestroy($this->image); - $this->image = $dest; - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } - - - - public function scaleImageSquare($dim) { - if(!$this->is_valid()) - return FALSE; - - if($this->is_imagick()) { - $this->image->setFirstIterator(); - do { - $this->image->scaleImage($dim, $dim); - } while ($this->image->nextImage()); - return; - } - - $dest = imagecreatetruecolor( $dim, $dim ); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dim, $dim, $this->width, $this->height); - if($this->image) - imagedestroy($this->image); - $this->image = $dest; - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } - - - public function cropImage($max,$x,$y,$w,$h) { - if(!$this->is_valid()) - return FALSE; - - if($this->is_imagick()) { - $this->image->setFirstIterator(); - do { - $this->image->cropImage($w, $h, $x, $y); - /** - * We need to remove the canva, - * or the image is not resized to the crop: - * http://php.net/manual/en/imagick.cropimage.php#97232 - */ - $this->image->setImagePage(0, 0, 0, 0); - } while ($this->image->nextImage()); - return $this->scaleImage($max); - } - - $dest = imagecreatetruecolor( $max, $max ); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $max, $max, $w, $h); - if($this->image) - imagedestroy($this->image); - $this->image = $dest; - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } - - public function saveImage($path) { - if(!$this->is_valid()) - return FALSE; - - $string = $this->imageString(); - file_put_contents($path, $string); - } - - public function imageString() { - if(!$this->is_valid()) - return FALSE; - - if($this->is_imagick()) { - /* Clean it */ - $this->image = $this->image->deconstructImages(); - $string = $this->image->getImagesBlob(); - return $string; - } - - $quality = FALSE; - - ob_start(); - - switch($this->getType()){ - case "image/png": - $quality = get_config('system','png_quality'); - if((! $quality) || ($quality > 9)) - $quality = PNG_QUALITY; - imagepng($this->image,NULL, $quality); - break; - case "image/jpeg": - $quality = get_config('system','jpeg_quality'); - if((! $quality) || ($quality > 100)) - $quality = JPEG_QUALITY; - imagejpeg($this->image,NULL,$quality); - } - $string = ob_get_contents(); - ob_end_clean(); - - return $string; - } - - - - public function store($uid, $xchan, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '') { - - $x = q("select id from photo where `resource_id` = '%s' and uid = %d and `xchan` = '%s' and `scale` = %d limit 1", - dbesc($rid), - intval($uid), - dbesc($xchan), - intval($scale) - ); - if(count($x)) { - $r = q("UPDATE `photo` - set `uid` = %d, - `xchan` = '%s', - `resource_id` = '%s', - `created` = '%s', - `edited` = '%s', - `filename` = '%s', - `type` = '%s', - `album` = '%s', - `height` = %d, - `width` = %d, - `data` = '%s', - `scale` = %d, - `profile` = %d, - `allow_cid` = '%s', - `allow_gid` = '%s', - `deny_cid` = '%s', - `deny_gid` = '%s' - where id = %d limit 1", - - intval($uid), - dbesc($xchan), - dbesc($rid), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc(basename($filename)), - dbesc($this->getType()), - dbesc($album), - intval($this->getHeight()), - intval($this->getWidth()), - dbesc($this->imageString()), - intval($scale), - intval($profile), - dbesc($allow_cid), - dbesc($allow_gid), - dbesc($deny_cid), - dbesc($deny_gid), - intval($x[0]['id']) - ); - } - else { - $r = q("INSERT INTO `photo` - ( `uid`, `xchan`, `resource_id`, `created`, `edited`, `filename`, type, `album`, `height`, `width`, `data`, `scale`, `profile`, `allow_cid`, `allow_gid`, `deny_cid`, `deny_gid` ) - VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', %d, %d, '%s', '%s', '%s', '%s' )", - intval($uid), - dbesc($xchan), - dbesc($rid), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc(basename($filename)), - dbesc($this->getType()), - dbesc($album), - intval($this->getHeight()), - intval($this->getWidth()), - dbesc($this->imageString()), - intval($scale), - intval($profile), - dbesc($allow_cid), - dbesc($allow_gid), - dbesc($deny_cid), - dbesc($deny_gid) - ); - } - return $r; - } -}} - - -/** - * Guess image mimetype from filename or from Content-Type header - * - * @arg $filename string Image filename - * @arg $fromcurl boolean Check Content-Type header from curl request - */ -function guess_image_type($filename, $fromcurl=false) { - logger('Photo: guess_image_type: '.$filename . ($fromcurl?' from curl headers':''), LOGGER_DEBUG); - $type = null; - if ($fromcurl) { - $a = get_app(); - $headers=array(); - $h = explode("\n",$a->get_curl_headers()); - foreach ($h as $l) { - list($k,$v) = array_map("trim", explode(":", trim($l), 2)); - $headers[$k] = $v; - } - if (array_key_exists('Content-Type', $headers)) - $type = $headers['Content-Type']; - } - if (is_null($type)){ - // Guessing from extension? Isn't that... dangerous? - if(class_exists('Imagick') && file_exists($filename) && is_readable($filename)) { - /** - * Well, this not much better, - * but at least it comes from the data inside the image, - * we won't be tricked by a manipulated extension - */ - $image = new Imagick($filename); - $type = $image->getImageMimeType(); - } else { - $ext = pathinfo($filename, PATHINFO_EXTENSION); - $types = Photo::supportedTypes(); - $type = "image/jpeg"; - foreach ($types as $m=>$e){ - if ($ext==$e) $type = $m; - } - } - } - logger('Photo: guess_image_type: type='.$type, LOGGER_DEBUG); - return $type; - -} - -function import_profile_photo($photo,$xchan) { - - $a = get_app(); - - $r = q("select `resource_id` from photo where xchan = '%s' and `scale` = 4 limit 1", - dbesc($xchan) - ); - if($r) { - $hash = $r[0]['resource_id']; - } - else { - $hash = photo_new_resource(); - } - - $photo_failure = false; - - $filename = basename($photo); - $img_str = fetch_url($photo,true); - - $type = guess_image_type($photo,true); - $img = new Photo($img_str, $type); - if($img->is_valid()) { - - $img->scaleImageSquare(175); - - $r = $img->store(0, $xchan, $hash, $filename, 'Contact Photos', 4 ); - - if($r === false) - $photo_failure = true; - - $img->scaleImage(80); - - $r = $img->store(0, $xchan, $hash, $filename, 'Contact Photos', 5 ); - - if($r === false) - $photo_failure = true; - - $img->scaleImage(48); - - $r = $img->store(0, $xchan, $hash, $filename, 'Contact Photos', 6 ); - - if($r === false) - $photo_failure = true; - - $photo = $a->get_baseurl() . '/photo/' . $hash . '-4'; - $thumb = $a->get_baseurl() . '/photo/' . $hash . '-5'; - $micro = $a->get_baseurl() . '/photo/' . $hash . '-6'; - } - else - $photo_failure = true; - - if($photo_failure) { - $photo = $a->get_baseurl() . '/images/person-175.jpg'; - $thumb = $a->get_baseurl() . '/images/person-80.jpg'; - $micro = $a->get_baseurl() . '/images/person-48.jpg'; - $type = 'image/jpeg'; - } - - return(array($photo,$thumb,$micro,$type)); - -} diff --git a/include/ProtoDriver.php b/include/ProtoDriver.php new file mode 100644 index 000000000..7585a0135 --- /dev/null +++ b/include/ProtoDriver.php @@ -0,0 +1,43 @@ +<?php /** @file */ + +/* + * Abstraction class for dealing with alternate networks (which of course do not exist, hence the abstraction) + */ + + +abstract class ProtoDriver { + abstract protected function discover($channel,$location); + abstract protected function deliver($item,$channel,$recipients); + abstract protected function collect($channel,$connection); + abstract protected function change_permissions($permissions,$channel,$recipient); + abstract protected function acknowledge_permissions($permissions,$channel,$recipient); + abstract protected function deliver_private($item,$channel,$recipients); + abstract protected function collect_private($channel,$connection); + +} + +class ZotDriver extends ProtoDriver { + + protected function discover($channel,$location) { + + } + protected function deliver($item,$channel,$recipients) { + + } + protected function collect($channel,$connection) { + + } + protected function change_permissions($permissions,$channel,$recipient) { + + } + protected function acknowledge_permissions($permissions,$channel,$recipient) { + + } + protected function deliver_private($item,$channel,$recipients) { + + } + protected function collect_private($channel,$connection) { + + } + +} diff --git a/include/Scrape.php b/include/Scrape.php deleted file mode 100644 index 806106ef1..000000000 --- a/include/Scrape.php +++ /dev/null @@ -1,712 +0,0 @@ -<?php - -require_once('library/HTML5/Parser.php'); -require_once('include/crypto.php'); - -if(! function_exists('scrape_dfrn')) { -function scrape_dfrn($url) { - - $a = get_app(); - - $ret = array(); - - logger('scrape_dfrn: url=' . $url); - - $s = fetch_url($url); - - if(! $s) - return $ret; - - $headers = $a->get_curl_headers(); - logger('scrape_dfrn: headers=' . $headers, LOGGER_DEBUG); - - - $lines = explode("\n",$headers); - if(count($lines)) { - foreach($lines as $line) { - // don't try and run feeds through the html5 parser - if(stristr($line,'content-type:') && ((stristr($line,'application/atom+xml')) || (stristr($line,'application/rss+xml')))) - return ret; - } - } - - try { - $dom = HTML5_Parser::parse($s); - } catch (DOMException $e) { - logger('scrape_dfrn: parse error: ' . $e); - } - - if(! $dom) - return $ret; - - $items = $dom->getElementsByTagName('link'); - - // get DFRN link elements - - foreach($items as $item) { - $x = $item->getAttribute('rel'); - if(($x === 'alternate') && ($item->getAttribute('type') === 'application/atom+xml')) - $ret['feed_atom'] = $item->getAttribute('href'); - if(substr($x,0,5) == "dfrn-") { - $ret[$x] = $item->getAttribute('href'); - } - if($x === 'lrdd') { - $decoded = urldecode($item->getAttribute('href')); - if(preg_match('/acct:([^@]*)@/',$decoded,$matches)) - $ret['nick'] = $matches[1]; - } - } - - // Pull out hCard profile elements - - $largest_photo = 0; - - $items = $dom->getElementsByTagName('*'); - foreach($items as $item) { - if(attribute_contains($item->getAttribute('class'), 'vcard')) { - $level2 = $item->getElementsByTagName('*'); - foreach($level2 as $x) { - if(attribute_contains($x->getAttribute('class'),'fn')) { - $ret['fn'] = $x->textContent; - } - if((attribute_contains($x->getAttribute('class'),'photo')) - || (attribute_contains($x->getAttribute('class'),'avatar'))) { - $size = intval($x->getAttribute('width')); - // dfrn prefers 175, so if we find this, we set largest_size so it can't be topped. - if(($size > $largest_photo) || ($size == 175) || (! $largest_photo)) { - $ret['photo'] = $x->getAttribute('src'); - $largest_photo = (($size == 175) ? 9999 : $size); - } - } - if(attribute_contains($x->getAttribute('class'),'key')) { - $ret['key'] = $x->textContent; - } - } - } - } - - return $ret; -}} - - - - - - -if(! function_exists('validate_dfrn')) { -function validate_dfrn($a) { - $errors = 0; - if(! x($a,'key')) - $errors ++; - if(! x($a,'dfrn-request')) - $errors ++; - if(! x($a,'dfrn-confirm')) - $errors ++; - if(! x($a,'dfrn-notify')) - $errors ++; - if(! x($a,'dfrn-poll')) - $errors ++; - return $errors; -}} - -if(! function_exists('scrape_meta')) { -function scrape_meta($url) { - - $a = get_app(); - - $ret = array(); - - logger('scrape_meta: url=' . $url); - - $s = fetch_url($url); - - if(! $s) - return $ret; - - $headers = $a->get_curl_headers(); - logger('scrape_meta: headers=' . $headers, LOGGER_DEBUG); - - $lines = explode("\n",$headers); - if(count($lines)) { - foreach($lines as $line) { - // don't try and run feeds through the html5 parser - if(stristr($line,'content-type:') && ((stristr($line,'application/atom+xml')) || (stristr($line,'application/rss+xml')))) - return ret; - } - } - - try { - $dom = HTML5_Parser::parse($s); - } catch (DOMException $e) { - logger('scrape_meta: parse error: ' . $e); - } - - if(! $dom) - return $ret; - - $items = $dom->getElementsByTagName('meta'); - - // get DFRN link elements - - foreach($items as $item) { - $x = $item->getAttribute('name'); - if(substr($x,0,5) == "dfrn-") - $ret[$x] = $item->getAttribute('content'); - } - - return $ret; -}} - - -if(! function_exists('scrape_vcard')) { -function scrape_vcard($url) { - - $a = get_app(); - - $ret = array(); - - logger('scrape_vcard: url=' . $url); - - $s = fetch_url($url); - - if(! $s) - return $ret; - - $headers = $a->get_curl_headers(); - $lines = explode("\n",$headers); - if(count($lines)) { - foreach($lines as $line) { - // don't try and run feeds through the html5 parser - if(stristr($line,'content-type:') && ((stristr($line,'application/atom+xml')) || (stristr($line,'application/rss+xml')))) - return ret; - } - } - - try { - $dom = HTML5_Parser::parse($s); - } catch (DOMException $e) { - logger('scrape_vcard: parse error: ' . $e); - } - - if(! $dom) - return $ret; - - // Pull out hCard profile elements - - $largest_photo = 0; - - $items = $dom->getElementsByTagName('*'); - foreach($items as $item) { - if(attribute_contains($item->getAttribute('class'), 'vcard')) { - $level2 = $item->getElementsByTagName('*'); - foreach($level2 as $x) { - if(attribute_contains($x->getAttribute('class'),'fn')) - $ret['fn'] = $x->textContent; - if((attribute_contains($x->getAttribute('class'),'photo')) - || (attribute_contains($x->getAttribute('class'),'avatar'))) { - $size = intval($x->getAttribute('width')); - if(($size > $largest_photo) || (! $largest_photo)) { - $ret['photo'] = $x->getAttribute('src'); - $largest_photo = $size; - } - } - if((attribute_contains($x->getAttribute('class'),'nickname')) - || (attribute_contains($x->getAttribute('class'),'uid'))) { - $ret['nick'] = $x->textContent; - } - } - } - } - - return $ret; -}} - - -if(! function_exists('scrape_feed')) { -function scrape_feed($url) { - - $a = get_app(); - - $ret = array(); - $s = fetch_url($url); - - $headers = $a->get_curl_headers(); - $code = $a->get_curl_code(); - - logger('scrape_feed: returns: ' . $code . ' headers=' . $headers, LOGGER_DEBUG); - - if(! $s) { - logger('scrape_feed: no data returned for ' . $url); - return $ret; - } - - - $lines = explode("\n",$headers); - if(count($lines)) { - foreach($lines as $line) { - if(stristr($line,'content-type:')) { - if(stristr($line,'application/atom+xml') || stristr($s,'<feed')) { - $ret['feed_atom'] = $url; - return $ret; - } - if(stristr($line,'application/rss+xml') || stristr($s,'<rss')) { - $ret['feed_rss'] = $url; - return $ret; - } - } - } - // perhaps an RSS version 1 feed with a generic or incorrect content-type? - if(stristr($s,'</item>')) { - $ret['feed_rss'] = $url; - return $ret; - } - } - - try { - $dom = HTML5_Parser::parse($s); - } catch (DOMException $e) { - logger('scrape_feed: parse error: ' . $e); - } - - if(! $dom) { - logger('scrape_feed: failed to parse.'); - return $ret; - } - - - $head = $dom->getElementsByTagName('base'); - if($head) { - foreach($head as $head0) { - $basename = $head0->getAttribute('href'); - break; - } - } - if(! $basename) - $basename = implode('/', array_slice(explode('/',$url),0,3)) . '/'; - - $items = $dom->getElementsByTagName('link'); - - // get Atom/RSS link elements, take the first one of either. - - if($items) { - foreach($items as $item) { - $x = $item->getAttribute('rel'); - if(($x === 'alternate') && ($item->getAttribute('type') === 'application/atom+xml')) { - if(! x($ret,'feed_atom')) - $ret['feed_atom'] = $item->getAttribute('href'); - } - if(($x === 'alternate') && ($item->getAttribute('type') === 'application/rss+xml')) { - if(! x($ret,'feed_rss')) - $ret['feed_rss'] = $item->getAttribute('href'); - } - } - } - - // Drupal and perhaps others only provide relative URL's. Turn them into absolute. - - if(x($ret,'feed_atom') && (! strstr($ret['feed_atom'],'://'))) - $ret['feed_atom'] = $basename . $ret['feed_atom']; - if(x($ret,'feed_rss') && (! strstr($ret['feed_rss'],'://'))) - $ret['feed_rss'] = $basename . $ret['feed_rss']; - - return $ret; -}} - - -/** - * - * Probe a network address to discover what kind of protocols we need to communicate with it. - * - * Warning: this function is a bit touchy and there are some subtle dependencies within the logic flow. - * Edit with care. - * - */ - -/** - * - * PROBE_DIASPORA has a bias towards returning Diaspora information - * while PROBE_NORMAL has a bias towards dfrn/zot - in the case where - * an address (such as a Friendica address) supports more than one type - * of network. - * - */ - - -define ( 'PROBE_NORMAL', 0); - -function probe_url($url, $mode = PROBE_NORMAL) { - require_once('include/email.php'); - - $result = array(); - - if(! $url) - return $result; - - $network = null; - $has_lrdd = false; - $email_conversant = false; - - $twitter = ((strpos($url,'twitter.com') !== false) ? true : false); - $lastfm = ((strpos($url,'last.fm/user') !== false) ? true : false); - - $at_addr = ((strpos($url,'@') !== false) ? true : false); - - if((! $twitter) && (! $lastfm)) { - - if(strpos($url,'mailto:') !== false && $at_addr) { - $url = str_replace('mailto:','',$url); - $links = array(); - } - else - $links = lrdd($url); - - if(count($links)) { - $has_lrdd = true; - - logger('probe_url: found lrdd links: ' . print_r($links,true), LOGGER_DATA); - foreach($links as $link) { - if($link['@attributes']['rel'] === NAMESPACE_ZOT) - $zot = unamp($link['@attributes']['href']); - if($link['@attributes']['rel'] === NAMESPACE_DFRN) - $dfrn = unamp($link['@attributes']['href']); - if($link['@attributes']['rel'] === 'salmon') - $notify = unamp($link['@attributes']['href']); - if($link['@attributes']['rel'] === NAMESPACE_FEED) - $poll = unamp($link['@attributes']['href']); - if($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard') - $hcard = unamp($link['@attributes']['href']); - if($link['@attributes']['rel'] === 'http://webfinger.net/rel/profile-page') - $profile = unamp($link['@attributes']['href']); - if($link['@attributes']['rel'] === 'http://portablecontacts.net/spec/1.0') - $poco = unamp($link['@attributes']['href']); - - } - - // Status.Net can have more than one profile URL. We need to match the profile URL - // to a contact on incoming messages to prevent spam, and we won't know which one - // to match. So in case of two, one of them is stored as an alias. Only store URL's - // and not webfinger user@host aliases. If they've got more than two non-email style - // aliases, let's hope we're lucky and get one that matches the feed author-uri because - // otherwise we're screwed. - - foreach($links as $link) { - if($link['@attributes']['rel'] === 'alias') { - if(strpos($link['@attributes']['href'],'@') === false) { - if(isset($profile)) { - if($link['@attributes']['href'] !== $profile) - $alias = unamp($link['@attributes']['href']); - } - else - $profile = unamp($link['@attributes']['href']); - } - } - } - } - elseif($mode == PROBE_NORMAL) { - - // Check email - - $orig_url = $url; - if((strpos($orig_url,'@')) && validate_email($orig_url)) { - $x = q("SELECT `prvkey` FROM `user` WHERE `uid` = %d LIMIT 1", - intval(local_user()) - ); - $r = q("SELECT * FROM `mailacct` WHERE `uid` = %d AND `server` != '' LIMIT 1", - intval(local_user()) - ); - if(count($x) && count($r)) { - $mailbox = construct_mailbox_name($r[0]); - $password = ''; - openssl_private_decrypt(hex2bin($r[0]['pass']),$password,$x[0]['prvkey']); - $mbox = email_connect($mailbox,$r[0]['user'],$password); - if(! $mbox) - logger('probe_url: email_connect failed.'); - unset($password); - } - if($mbox) { - $msgs = email_poll($mbox,$orig_url); - logger('probe_url: searching ' . $orig_url . ', ' . count($msgs) . ' messages found.', LOGGER_DEBUG); - if(count($msgs)) { - $addr = $orig_url; - $network = NETWORK_MAIL; - $name = substr($url,0,strpos($url,'@')); - $phost = substr($url,strpos($url,'@')+1); - $profile = 'http://' . $phost; - // fix nick character range - $vcard = array('fn' => $name, 'nick' => $name, 'photo' => avatar_img($url)); - $notify = 'smtp ' . random_string(); - $poll = 'email ' . random_string(); - $priority = 0; - $x = email_msg_meta($mbox,$msgs[0]); - if(stristr($x[0]->from,$orig_url)) - $adr = imap_rfc822_parse_adrlist($x[0]->from,''); - elseif(stristr($x[0]->to,$orig_url)) - $adr = imap_rfc822_parse_adrlist($x[0]->to,''); - if(isset($adr)) { - foreach($adr as $feadr) { - if((strcasecmp($feadr->mailbox,$name) == 0) - &&(strcasecmp($feadr->host,$phost) == 0) - && (strlen($feadr->personal))) { - - $personal = imap_mime_header_decode($feadr->personal); - $vcard['fn'] = ""; - foreach($personal as $perspart) - if ($perspart->charset != "default") - $vcard['fn'] .= iconv($perspart->charset, 'UTF-8//IGNORE', $perspart->text); - else - $vcard['fn'] .= $perspart->text; - - $vcard['fn'] = notags($vcard['fn']); - } - } - } - } - imap_close($mbox); - } - } - } - } - - if($mode == PROBE_NORMAL) { - if(strlen($zot)) { - $s = fetch_url($zot); - if($s) { - $j = json_decode($s); - if($j) { - $network = NETWORK_ZOT; - $vcard = array( - 'fn' => $j->fullname, - 'nick' => $j->nickname, - 'photo' => $j->photo - ); - $profile = $j->url; - $notify = $j->post; - $pubkey = $j->pubkey; - $poll = 'N/A'; - } - } - } - - if(strlen($dfrn)) { - $ret = scrape_dfrn(($hcard) ? $hcard : $dfrn); - if(is_array($ret) && x($ret,'dfrn-request')) { - $network = NETWORK_DFRN; - $request = $ret['dfrn-request']; - $confirm = $ret['dfrn-confirm']; - $notify = $ret['dfrn-notify']; - $poll = $ret['dfrn-poll']; - - $vcard = array(); - $vcard['fn'] = $ret['fn']; - $vcard['nick'] = $ret['nick']; - $vcard['photo'] = $ret['photo']; - } - } - } - - if($network !== NETWORK_ZOT && $network !== NETWORK_DFRN && $network !== NETWORK_MAIL) { - if($has_lrdd) - $network = NETWORK_OSTATUS; - $priority = 0; - - if($hcard && ! $vcard) { - $vcard = scrape_vcard($hcard); - - // Google doesn't use absolute url in profile photos - - if((x($vcard,'photo')) && substr($vcard['photo'],0,1) == '/') { - $h = @parse_url($hcard); - if($h) - $vcard['photo'] = $h['scheme'] . '://' . $h['host'] . $vcard['photo']; - } - - logger('probe_url: scrape_vcard: ' . print_r($vcard,true), LOGGER_DATA); - } - - if($diaspora && $addr) { - // Diaspora returns the name as the nick. As the nick will never be updated, - // let's use the Diaspora nickname (the first part of the handle) as the nick instead - $addr_parts = explode('@', $addr); - $vcard['nick'] = $addr_parts[0]; - } - - if($twitter) { - logger('twitter: setup'); - $tid = basename($url); - $tapi = 'https://api.twitter.com/1/statuses/user_timeline.rss'; - if(intval($tid)) - $poll = $tapi . '?user_id=' . $tid; - else - $poll = $tapi . '?screen_name=' . $tid; - $profile = 'http://twitter.com/#!/' . $tid; - //$vcard['photo'] = 'https://api.twitter.com/1/users/profile_image/' . $tid; - $vcard['photo'] = 'https://api.twitter.com/1/users/profile_image?screen_name=' . $tid . '&size=bigger'; - $vcard['nick'] = $tid; - $vcard['fn'] = $tid; - } - - if($lastfm) { - $profile = $url; - $poll = str_replace(array('www.','last.fm/'),array('','ws.audioscrobbler.com/1.0/'),$url) . '/recenttracks.rss'; - $vcard['nick'] = basename($url); - $vcard['fn'] = $vcard['nick'] . t(' on Last.fm'); - $network = NETWORK_FEED; - } - - if(! x($vcard,'fn')) - if(x($vcard,'nick')) - $vcard['fn'] = $vcard['nick']; - - $check_feed = false; - - if($twitter || ! $poll) - $check_feed = true; - if((! isset($vcard)) || (! x($vcard,'fn')) || (! $profile)) - $check_feed = true; - if(($at_addr) && (! count($links))) - $check_feed = false; - - if($check_feed) { - - $feedret = scrape_feed(($poll) ? $poll : $url); - logger('probe_url: scrape_feed ' . (($poll)? $poll : $url) . ' returns: ' . print_r($feedret,true), LOGGER_DATA); - if(count($feedret) && ($feedret['feed_atom'] || $feedret['feed_rss'])) { - $poll = ((x($feedret,'feed_atom')) ? unamp($feedret['feed_atom']) : unamp($feedret['feed_rss'])); - if(! x($vcard)) - $vcard = array(); - } - - if(x($feedret,'photo') && (! x($vcard,'photo'))) - $vcard['photo'] = $feedret['photo']; - require_once('library/simplepie/simplepie.inc'); - $feed = new SimplePie(); - $xml = fetch_url($poll); - - logger('probe_url: fetch feed: ' . $poll . ' returns: ' . $xml, LOGGER_DATA); - $a = get_app(); - - logger('probe_url: scrape_feed: headers: ' . $a->get_curl_headers(), LOGGER_DATA); - - $feed->set_raw_data($xml); - - $feed->init(); - if($feed->error()) - logger('probe_url: scrape_feed: Error parsing XML: ' . $feed->error()); - - - if(! x($vcard,'photo')) - $vcard['photo'] = $feed->get_image_url(); - $author = $feed->get_author(); - - if($author) { - $vcard['fn'] = unxmlify(trim($author->get_name())); - if(! $vcard['fn']) - $vcard['fn'] = trim(unxmlify($author->get_email())); - if(strpos($vcard['fn'],'@') !== false) - $vcard['fn'] = substr($vcard['fn'],0,strpos($vcard['fn'],'@')); - $email = unxmlify($author->get_email()); - if(! $profile && $author->get_link()) - $profile = trim(unxmlify($author->get_link())); - if(! $vcard['photo']) { - $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); - if($rawtags) { - $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; - if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo')) - $vcard['photo'] = $elems['link'][0]['attribs']['']['href']; - } - } - } - else { - $item = $feed->get_item(0); - if($item) { - $author = $item->get_author(); - if($author) { - $vcard['fn'] = trim(unxmlify($author->get_name())); - if(! $vcard['fn']) - $vcard['fn'] = trim(unxmlify($author->get_email())); - if(strpos($vcard['fn'],'@') !== false) - $vcard['fn'] = substr($vcard['fn'],0,strpos($vcard['fn'],'@')); - $email = unxmlify($author->get_email()); - if(! $profile && $author->get_link()) - $profile = trim(unxmlify($author->get_link())); - } - if(! $vcard['photo']) { - $rawmedia = $item->get_item_tags('http://search.yahoo.com/mrss/','thumbnail'); - if($rawmedia && $rawmedia[0]['attribs']['']['url']) - $vcard['photo'] = unxmlify($rawmedia[0]['attribs']['']['url']); - } - if(! $vcard['photo']) { - $rawtags = $item->get_item_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); - if($rawtags) { - $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; - if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo')) - $vcard['photo'] = $elems['link'][0]['attribs']['']['href']; - } - } - } - } - - if((! $vcard['photo']) && strlen($email)) - $vcard['photo'] = avatar_img($email); - if($poll === $profile) - $lnk = $feed->get_permalink(); - if(isset($lnk) && strlen($lnk)) - $profile = $lnk; - - if(! (x($vcard,'fn'))) - $vcard['fn'] = notags($feed->get_title()); - if(! (x($vcard,'fn'))) - $vcard['fn'] = notags($feed->get_description()); - - if(strpos($vcard['fn'],'Twitter / ') !== false) { - $vcard['fn'] = substr($vcard['fn'],strpos($vcard['fn'],'/')+1); - $vcard['fn'] = trim($vcard['fn']); - } - if(! x($vcard,'nick')) { - $vcard['nick'] = strtolower(notags(unxmlify($vcard['fn']))); - if(strpos($vcard['nick'],' ')) - $vcard['nick'] = trim(substr($vcard['nick'],0,strpos($vcard['nick'],' '))); - } - if(! $network) - $network = NETWORK_FEED; - if(! $priority) - $priority = 2; - } - } - - if(! x($vcard,'photo')) { - $a = get_app(); - $vcard['photo'] = $a->get_baseurl() . '/images/person-175.jpg' ; - } - - if(! $profile) - $profile = $url; - - // No human could be associated with this link, use the URL as the contact name - - if(($network === NETWORK_FEED) && ($poll) && (! x($vcard,'fn'))) - $vcard['fn'] = $url; - - $vcard['fn'] = notags($vcard['fn']); - $vcard['nick'] = str_replace(' ','',notags($vcard['nick'])); - - $result['name'] = $vcard['fn']; - $result['nick'] = $vcard['nick']; - $result['url'] = $profile; - $result['addr'] = $addr; - $result['batch'] = $batch; - $result['notify'] = $notify; - $result['poll'] = $poll; - $result['request'] = $request; - $result['confirm'] = $confirm; - $result['poco'] = $poco; - $result['photo'] = $vcard['photo']; - $result['priority'] = $priority; - $result['network'] = $network; - $result['alias'] = $alias; - $result['pubkey'] = $pubkey; - - logger('probe_url: ' . print_r($result,true), LOGGER_DEBUG); - - return $result; -} diff --git a/include/account.php b/include/account.php index 9f967f2c3..1206223d9 100644 --- a/include/account.php +++ b/include/account.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ require_once('include/config.php'); require_once('include/network.php'); @@ -6,6 +6,7 @@ require_once('include/plugin.php'); require_once('include/text.php'); require_once('include/language.php'); require_once('include/datetime.php'); +require_once('include/crypto.php'); function check_account_email($email) { @@ -26,7 +27,7 @@ function check_account_email($email) { $r = q("select account_email from account where account_email = '%s' limit 1", dbesc($email) ); - if(count($r)) { + if($r) { $result['message'] .= t('Your email address is already registered at this site.'); } } @@ -80,12 +81,19 @@ function check_account_invite($invite_code) { function check_account_admin($arr) { if(is_site_admin()) return true; - $admin_mail = trim(get_config('system','admin_email')); + $admin_email = trim(get_config('system','admin_email')); if(strlen($admin_email) && $admin_email === trim($arr['email'])) return true; return false; } +function account_total() { + $r = q("select account_id from account where true"); + if(is_array($r)) + return count($r); + return false; +} + function create_account($arr) { @@ -100,8 +108,10 @@ function create_account($arr) { $parent = ((x($arr,'parent')) ? intval($arr['parent']) : 0 ); $flags = ((x($arr,'account_flags')) ? intval($arr['account_flags']) : ACCOUNT_OK); $roles = ((x($arr,'account_roles')) ? intval($arr['account_roles']) : 0 ); - + $expires = ((x($arr,'expires')) ? intval($arr['expires']) : '0000-00-00 00:00:00'); + $default_service_class = get_config('system','default_service_class'); + if($default_service_class === false) $default_service_class = ''; @@ -110,6 +120,8 @@ function create_account($arr) { return $result; } + // prevent form hackery + if($roles & ACCOUNT_ROLE_ADMIN) { $admin_result = check_account_admin($arr); if(! $admin_result) { @@ -117,6 +129,20 @@ function create_account($arr) { } } + // allow the admin_email account to be admin, but only if it's the first account. + + $c = account_total(); + if(($c === 0) && (check_account_admin($arr))) + $roles |= ACCOUNT_ROLE_ADMIN; + + // Ensure that there is a host keypair. + + if((! get_config('system','pubkey')) && (! get_config('system','prvkey'))) { + $hostkey = new_keypair(4096); + set_config('system','pubkey',$hostkey['pubkey']); + set_config('system','prvkey',$hostkey['prvkey']); + } + $invite_result = check_account_invite($invite_code); if($invite_result['error']) { $result['message'] = $invite_result['message']; @@ -218,10 +244,15 @@ function send_reg_approval_email($arr) { dbesc($hash), dbesc(datetime_convert()), intval($arr['account']['account_id']), - dbesc($arr['password']), + dbesc(''), dbesc($arr['account']['account_language']) ); + $ip = $_SERVER['REMOTE_ADDR']; + + $details = (($ip) ? $ip . ' [' . gethostbyaddr($ip) . ']' : '[unknown or stealth IP]'); + + $delivered = 0; foreach($admins as $admin) { @@ -230,16 +261,16 @@ function send_reg_approval_email($arr) { else push_lang('en'); - - $email_msg = replace_macros(get_intltext_template('register_verify_email.tpl'), array( - '$sitename' => get_config('config','sitename'), + $email_msg = replace_macros(get_intltext_template('register_verify_eml.tpl'), array( + '$sitename' => get_config('system','sitename'), '$siteurl' => z_root(), '$email' => $arr['email'], '$uid' => $arr['account']['account_id'], - '$hash' => $hash + '$hash' => $hash, + '$details' => $details )); - $res = mail($admin['email'], sprintf( t('Registration request at %s'), get_config('config','sitename')), + $res = mail($admin['email'], sprintf( t('Registration request at %s'), get_config('system','sitename')), $email_msg, 'From: ' . t('Administrator') . '@' . get_app()->get_hostname() . "\n" . 'Content-type: text/plain; charset=UTF-8' . "\n" @@ -248,6 +279,9 @@ function send_reg_approval_email($arr) { if($res) $delivered ++; + else + logger('send_reg_approval_email: failed to ' . $admin['email'] . 'account_id: ' . $arr['account']['account_id']); + pop_lang(); } @@ -257,10 +291,10 @@ function send_reg_approval_email($arr) { function send_verification_email($email,$password) { $email_msg = replace_macros(get_intltext_template('register_open_eml.tpl'), array( - '$sitename' => get_config('config','sitename'), + '$sitename' => get_config('system','sitename'), '$siteurl' => z_root(), '$email' => $email, - '$password' => $password, + '$password' => t('your registration password'), )); $res = mail($email, sprintf( t('Registration details for %s'), get_config('system','sitename')), @@ -271,3 +305,154 @@ function send_verification_email($email,$password) { ); return($res ? true : false); } + + +function user_allow($hash) { + + $a = get_app(); + + $ret = array('success' => false); + + $register = q("SELECT * FROM `register` WHERE `hash` = '%s' LIMIT 1", + dbesc($hash) + ); + + if(! $register) + return $ret; + + $account = q("SELECT * FROM account WHERE account_id = %d LIMIT 1", + intval($register[0]['uid']) + ); + + if(! $account) + return $ret; + + $r = q("DELETE FROM register WHERE hash = '%s' LIMIT 1", + dbesc($register[0]['hash']) + ); + + $r = q("update account set account_flags = (account_flags ^ %d) where (account_flags & %d) and account_id = %d limit 1", + intval(ACCOUNT_BLOCKED), + intval(ACCOUNT_BLOCKED), + intval($register[0]['uid']) + ); + $r = q("update account set account_flags = (account_flags ^ %d) where (account_flags & %d) and account_id = %d limit 1", + intval(ACCOUNT_PENDING), + intval(ACCOUNT_PENDING), + intval($register[0]['uid']) + ); + + push_lang($register[0]['language']); + + $email_tpl = get_intltext_template("register_open_eml.tpl"); + $email_tpl = replace_macros($email_tpl, array( + '$sitename' => get_config('system','sitename'), + '$siteurl' => z_root(), + '$username' => $account[0]['account_email'], + '$email' => $account[0]['account_email'], + '$password' => '', + '$uid' => $account[0]['account_id'] + )); + + $res = mail($account[0]['account_email'], sprintf( t('Registration details for %s'), get_config('system','sitename')), + $email_tpl, + 'From: ' . t('Administrator') . '@' . $_SERVER['SERVER_NAME'] . "\n" + . 'Content-type: text/plain; charset=UTF-8' . "\n" + . 'Content-transfer-encoding: 8bit' ); + + pop_lang(); + + if($res) { + info( t('Account approved.') . EOL ); + return true; + } + +} + + +// This does not have to go through user_remove() and save the nickname +// permanently against re-registration, as the person was not yet +// allowed to have friends on this system + +function user_deny($hash) { + + $register = q("SELECT * FROM register WHERE hash = '%s' LIMIT 1", + dbesc($hash) + ); + + if(! count($register)) + return false; + + $account = q("SELECT account_id FROM account WHERE account_id = %d LIMIT 1", + intval($register[0]['uid']) + ); + + if(! $account) + return false; + + $r = q("DELETE FROM account WHERE account_id = %d LIMIT 1", + intval($register[0]['uid']) + ); + + $r = q("DELETE FROM `register` WHERE id = %d LIMIT 1", + dbesc($register[0]['id']) + ); + notice( sprintf(t('Registration revoked for %s'), $account[0]['account_email']) . EOL); + return true; + +} + + +/** + * @function downgrade_accounts() + * Checks for accounts that have past their expiration date. + * If the account has a service class which is not the site default, + * the service class is reset to the site default and expiration reset to never. + * If the account has no service class it is expired and subsequently disabled. + * called from include/poller.php as a scheduled task. + * + * Reclaiming resources which are no longer within the service class limits is + * not the job of this function, but this can be implemented by plugin if desired. + * Default behaviour is to stop allowing additional resources to be consumed. + */ + + +function downgrade_accounts() { + + $r = q("select * from account where not ( account_flags & %d ) + and account_expires != '0000-00-00 00:00:00' + and account_expires < UTC_TIMESTAMP() ", + intval(ACCOUNT_EXPIRED) + ); + + if(! $r) + return; + + $basic = get_config('system','default_service_class'); + + + foreach($r as $rr) { + + if(($basic) && ($rr['account_service_class']) && ($rr['account_service_class'] != $basic)) { + $x = q("UPDATE account set account_service_class = '%s', account_expires = '%s' + where account_id = %d limit 1", + dbesc($basic), + dbesc('0000-00-00 00:00:00'), + intval($rr['account_id']) + ); + $ret = array('account' => $rr); + call_hooks('account_downgrade', $ret ); + logger('downgrade_accounts: Account id ' . $rr['account_id'] . ' downgraded.'); + } + else { + $x = q("UPDATE account SET account_flags = (account_flags | %d) where account_id = %d limit 1", + intval(ACCOUNT_EXPIRED), + intval($rr['account_id']) + ); + $ret = array('account' => $rr); + call_hooks('account_downgrade', $ret); + logger('downgrade_accounts: Account id ' . $rr['account_id'] . ' expired.'); + } + } +} + diff --git a/include/acl_selectors.php b/include/acl_selectors.php index 8c45292cf..8d94264e4 100644 --- a/include/acl_selectors.php +++ b/include/acl_selectors.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ /** * */ @@ -14,7 +14,7 @@ function group_select($selname,$selclass,$preselected = false,$size = 4) { $o .= "<select name=\"{$selname}[]\" id=\"$selclass\" class=\"$selclass\" multiple=\"multiple\" size=\"$size\" >\r\n"; - $r = q("SELECT * FROM `group` WHERE `deleted` = 0 AND `uid` = %d ORDER BY `name` ASC", + $r = q("SELECT * FROM `groups` WHERE `deleted` = 0 AND `uid` = %d ORDER BY `name` ASC", intval(local_user()) ); @@ -25,7 +25,7 @@ function group_select($selname,$selclass,$preselected = false,$size = 4) { call_hooks($a->module . '_pre_' . $selname, $arr); - if(count($r)) { + if($r) { foreach($r as $rr) { if((is_array($preselected)) && in_array($rr['id'], $preselected)) $selected = " selected=\"selected\" "; @@ -45,7 +45,7 @@ function group_select($selname,$selclass,$preselected = false,$size = 4) { return $o; } - +/* MicMee 20130114 function contact_selector no longer in use, sql table contact does no longer exist function contact_selector($selname, $selclass, $preselected = false, $options) { $a = get_app(); @@ -148,7 +148,7 @@ function contact_selector($selname, $selclass, $preselected = false, $options) { call_hooks($a->module . '_post_' . $selname, $o); return $o; -} +}*/ @@ -163,17 +163,6 @@ function contact_select($selname, $selclass, $preselected = false, $size = 4, $p $sql_extra = ''; - if($privmail || $celeb) { - $sql_extra .= sprintf(" AND `rel` = %d ", intval(CONTACT_IS_FRIEND)); - } - - if($privmail) { - $sql_extra .= " AND `network` IN ( 'dfrn', 'dspr' ) "; - } - elseif($privatenet) { - $sql_extra .= " AND `network` IN ( 'dfrn', 'mail', 'face', 'dspr' ) "; - } - $tabindex = ($tabindex > 0 ? "tabindex=\"$tabindex\"" : ""); if($privmail) @@ -181,10 +170,11 @@ function contact_select($selname, $selclass, $preselected = false, $size = 4, $p else $o .= "<select name=\"{$selname}[]\" id=\"$selclass\" class=\"$selclass\" multiple=\"multiple\" size=\"$size\" $tabindex >\r\n"; - $r = q("SELECT `id`, `name`, `url`, `network` FROM `contact` - WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 AND `archive` = 0 AND `notify` != '' + $r = q("SELECT abook_id, xchan_name, xchan_url, xchan_photo_s from abook left join xchan on abook_xchan = xchan_hash + where abook_flags = 0 or not ( abook_flags & %d ) and abook_channel = %d $sql_extra - ORDER BY `name` ASC ", + ORDER BY xchan_name ASC ", + intval(ABOOK_FLAG_SELF), intval(local_user()) ); @@ -195,16 +185,16 @@ function contact_select($selname, $selclass, $preselected = false, $size = 4, $p call_hooks($a->module . '_pre_' . $selname, $arr); - if(count($r)) { + if($r) { foreach($r as $rr) { if((is_array($preselected)) && in_array($rr['id'], $preselected)) $selected = " selected=\"selected\" "; else $selected = ''; - $trimmed = mb_substr($rr['name'],0,20); + $trimmed = mb_substr($rr['xchan_name'],0,20); - $o .= "<option value=\"{$rr['id']}\" $selected title=\"{$rr['name']}|{$rr['url']}\" >$trimmed</option>\r\n"; + $o .= "<option value=\"{$rr['abook_id']}\" $selected title=\"{$rr['xchan_name']}|{$rr['xchan_url']}\" >$trimmed</option>\r\n"; } } @@ -218,72 +208,45 @@ function contact_select($selname, $selclass, $preselected = false, $size = 4, $p function fixacl(&$item) { - $item = intval(str_replace(array('<','>'),array('',''),$item)); + $item = str_replace(array('<','>'),array('',''),$item); } -function populate_acl($user = null,$celeb = false) { +function populate_acl($defaults = null,$show_jotnets = true) { $allow_cid = $allow_gid = $deny_cid = $deny_gid = false; - if(is_array($user)) { - $allow_cid = ((strlen($user['allow_cid'])) - ? explode('><', $user['allow_cid']) : array() ); - $allow_gid = ((strlen($user['allow_gid'])) - ? explode('><', $user['allow_gid']) : array() ); - $deny_cid = ((strlen($user['deny_cid'])) - ? explode('><', $user['deny_cid']) : array() ); - $deny_gid = ((strlen($user['deny_gid'])) - ? explode('><', $user['deny_gid']) : array() ); + if(is_array($defaults)) { + $allow_cid = ((strlen($defaults['allow_cid'])) + ? explode('><', $defaults['allow_cid']) : array() ); + $allow_gid = ((strlen($defaults['allow_gid'])) + ? explode('><', $defaults['allow_gid']) : array() ); + $deny_cid = ((strlen($defaults['deny_cid'])) + ? explode('><', $defaults['deny_cid']) : array() ); + $deny_gid = ((strlen($defaults['deny_gid'])) + ? explode('><', $defaults['deny_gid']) : array() ); array_walk($allow_cid,'fixacl'); array_walk($allow_gid,'fixacl'); array_walk($deny_cid,'fixacl'); array_walk($deny_gid,'fixacl'); } - - /*$o = ''; - $o .= '<div id="acl-wrapper">'; - $o .= '<div id="acl-permit-outer-wrapper">'; - $o .= '<div id="acl-permit-text">' . t('Visible To:') . '</div><div id="jot-public">' . t('everybody') . '</div>'; - $o .= '<div id="acl-permit-text-end"></div>'; - $o .= '<div id="acl-permit-wrapper">'; - $o .= '<div id="group_allow_wrapper">'; - $o .= '<label id="acl-allow-group-label" for="group_allow" >' . t('Groups') . '</label>'; - $o .= group_select('group_allow','group_allow',$allow_gid); - $o .= '</div>'; - $o .= '<div id="contact_allow_wrapper">'; - $o .= '<label id="acl-allow-contact-label" for="contact_allow" >' . t('Contacts') . '</label>'; - $o .= contact_select('contact_allow','contact_allow',$allow_cid,4,false,$celeb,true); - $o .= '</div>'; - $o .= '</div>' . "\r\n"; - $o .= '<div id="acl-allow-end"></div>' . "\r\n"; - $o .= '</div>'; - $o .= '<div id="acl-deny-outer-wrapper">'; - $o .= '<div id="acl-deny-text">' . t('Except For:') . '</div>'; - $o .= '<div id="acl-deny-text-end"></div>'; - $o .= '<div id="acl-deny-wrapper">'; - $o .= '<div id="group_deny_wrapper" >'; - $o .= '<label id="acl-deny-group-label" for="group_deny" >' . t('Groups') . '</label>'; - $o .= group_select('group_deny','group_deny', $deny_gid); - $o .= '</div>'; - $o .= '<div id="contact_deny_wrapper" >'; - $o .= '<label id="acl-deny-contact-label" for="contact_deny" >' . t('Contacts') . '</label>'; - $o .= contact_select('contact_deny','contact_deny', $deny_cid,4,false, $celeb,true); - $o .= '</div>'; - $o .= '</div>' . "\r\n"; - $o .= '<div id="acl-deny-end"></div>' . "\r\n"; - $o .= '</div>'; - $o .= '</div>' . "\r\n"; - $o .= '<div id="acl-wrapper-end"></div>' . "\r\n";*/ + $jotnets = ''; + if($show_jotnets) { + call_hooks('jot_networks', $jotnets); + } + $tpl = get_markup_template("acl_selector.tpl"); $o = replace_macros($tpl, array( '$showall'=> t("Visible to everybody"), - '$show' => t("show"), - '$hide' => t("don't show"), + '$show' => t("Show"), + '$hide' => t("Don't show"), '$allowcid' => json_encode($allow_cid), '$allowgid' => json_encode($allow_gid), '$denycid' => json_encode($deny_cid), '$denygid' => json_encode($deny_gid), + '$jotnets' => $jotnets, + '$aclModalTitle' => t('Permissions'), + '$aclModalDismiss' => t('Close') )); diff --git a/include/activities.php b/include/activities.php index ced9f3d18..4502b758e 100644 --- a/include/activities.php +++ b/include/activities.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ function profile_activity($changed, $value) { $a = get_app(); @@ -17,13 +17,15 @@ function profile_activity($changed, $value) { return; $arr = array(); - $arr['uri'] = $arr['parent_uri'] = item_message_id(); + $arr['mid'] = $arr['parent_mid'] = item_message_id(); $arr['uid'] = local_user(); $arr['aid'] = $self['channel_account_id']; $arr['owner_xchan'] = $arr['author_xchan'] = $self['xchan_hash']; $arr['item_flags'] = ITEM_WALL|ITEM_ORIGIN|ITEM_THREAD_TOP; $arr['verb'] = ACTIVITY_UPDATE; $arr['obj_type'] = ACTIVITY_OBJ_PROFILE; + + $arr['$plink'] = z_root() . '/channel/' . $self['channel_address'] . '/?f=&mid=' . $arr['mid']; $A = '[url=' . z_root() . '/channel/' . $self['channel_address'] . ']' . $self['channel_name'] . '[/url]'; @@ -45,6 +47,8 @@ function profile_activity($changed, $value) { $prof = '[url=' . z_root() . '/profile/' . $self['channel_address'] . ']' . t('public profile') . '[/url]'; if($t == 1 && strlen($value)) { + // if it's a url, the HTML quotes will mess it up, so link it and don't try and zidify it because we don't know what it points to. + $value = linkify($value); $message = sprintf( t('%1$s changed %2$s to “%3$s”'), $A, $changes, $value); $message .= "\n\n" . sprintf( t('Visit %1$s\'s %2$s'), $A, $prof); } @@ -73,7 +77,8 @@ function profile_activity($changed, $value) { $arr['deny_cid'] = $self['channel_deny_cid']; $arr['deny_gid'] = $self['channel_deny_gid']; - $i = item_store($arr); + $res = item_store($arr); + $i = $res['item_id']; if($i) { // FIXME - limit delivery in notifier.php to those specificed in the perms argument diff --git a/include/api.php b/include/api.php index d755c7018..57551a3b0 100644 --- a/include/api.php +++ b/include/api.php @@ -1,17 +1,58 @@ -<?php - require_once("bbcode.php"); - require_once("datetime.php"); - require_once("conversation.php"); - require_once("oauth.php"); - require_once("html2plain.php"); +<?php /** @file */ + +require_once("bbcode.php"); +require_once("datetime.php"); +require_once("conversation.php"); +require_once("oauth.php"); +require_once("html2plain.php"); +require_once('include/security.php'); +require_once('include/photos.php'); +require_once('include/items.php'); + /* - * Twitter-Like API * + * Red API. Loosely based on and possibly compatible with a Twitter-Like API but all similarities end there. + * + */ + + + /** + ** TWITTER API */ - $API = Array(); + $API = array(); + $called_api = Null; + // All commands which require authentication accept a "channel" parameter + // which is the left hand side of the channel address/nickname. + // If provided, the desired channel is selected before caarying out the command. + // If not provided, the default channel associated with the account is used. + // If channel selection fails, the API command requiring login will fail. + + function api_user() { + $aid = get_account_id(); + $channel = get_app()->get_channel(); + + if(($aid) && (x($_REQUEST,'channel'))) { + + // Only change channel if it is different than the current channel + + if($channel && x($channel,'channel_address') && $channel['channel_address'] != $_REQUEST['channel']) { + $c = q("select channel_id from channel where channel_address = '%s' and channel_account_id = %d limit 1", + dbesc($_REQUEST['channel']), + intval($aid) + ); + if((! $c) || (! change_channel($c[0]['channel_id']))) + return false; + } + } + if ($_SESSION["allow_api"]) + return local_user(); + return false; + } + + function api_date($str){ //Wed May 23 06:01:13 +0000 2007 return datetime_convert('UTC', 'UTC', $str, "D M d H:i:s +0000 Y" ); @@ -30,22 +71,25 @@ function api_login(&$a){ // login with oauth - try{ + try { $oauth = new FKOAuth1(); - list($consumer,$token) = $oauth->verify_request(OAuthRequest::from_request()); + $req = OAuthRequest::from_request(); + list($consumer,$token) = $oauth->verify_request($req); +// list($consumer,$token) = $oauth->verify_request(OAuthRequest::from_request()); if (!is_null($token)){ $oauth->loginUser($token->uid); call_hooks('logged_in', $a->user); return; } - echo __file__.__line__.__function__."<pre>"; var_dump($consumer, $token); die(); - }catch(Exception $e){ + echo __file__.__line__.__function__."<pre>"; + var_dump($consumer, $token); + die(); + } + catch(Exception $e) { logger(__file__.__line__.__function__."\n".$e); - //die(__file__.__line__.__function__."<pre>".$e); die(); } - // workaround for HTTP-auth in CGI mode if(x($_SERVER,'REDIRECT_REMOTE_USER')) { $userpass = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"],6)) ; @@ -56,48 +100,42 @@ } } + if(x($_SERVER,'HTTP_AUTHORIZATION')) { + $userpass = base64_decode(substr($_SERVER["HTTP_AUTHORIZATION"],6)) ; + if(strlen($userpass)) { + list($name, $password) = explode(':', $userpass); + $_SERVER['PHP_AUTH_USER'] = $name; + $_SERVER['PHP_AUTH_PW'] = $password; + } + } + + if (!isset($_SERVER['PHP_AUTH_USER'])) { logger('API_login: ' . print_r($_SERVER,true), LOGGER_DEBUG); - header('WWW-Authenticate: Basic realm="Friendica"'); + header('WWW-Authenticate: Basic realm="Red"'); header('HTTP/1.0 401 Unauthorized'); die('This api requires login'); } - $user = $_SERVER['PHP_AUTH_USER']; - $encrypted = hash('whirlpool',trim($_SERVER['PHP_AUTH_PW'])); - - - /** - * next code from mod/auth.php. needs better solution - */ - // process normal login request - - $r = q("SELECT * FROM `user` WHERE ( `email` = '%s' OR `nickname` = '%s' ) - AND `password` = '%s' AND `blocked` = 0 AND `account_expired` = 0 AND `verified` = 1 LIMIT 1", - dbesc(trim($user)), - dbesc(trim($user)), - dbesc($encrypted) - ); - if(count($r)){ - $record = $r[0]; - } else { + require_once('include/auth.php'); + $record = account_verify_password($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW']); + if(! $record) { logger('API_login failure: ' . print_r($_SERVER,true), LOGGER_DEBUG); - header('WWW-Authenticate: Basic realm="Friendica"'); + header('WWW-Authenticate: Basic realm="Red"'); header('HTTP/1.0 401 Unauthorized'); die('This api requires login'); } require_once('include/security.php'); authenticate_success($record); - - call_hooks('logged_in', $a->user); - + $_SESSION['allow_api'] = true; } /************************** * MAIN API ENTRY POINT * **************************/ + function api_call(&$a){ GLOBAL $API, $called_api; @@ -108,15 +146,19 @@ if (strpos($a->query_string, $p)===0){ $called_api= explode("/",$p); //unset($_SERVER['PHP_AUTH_USER']); - if ($info['auth']===true && local_user()===false) { + if ($info['auth'] === true && api_user() === false) { api_login($a); } - load_contact_links(local_user()); + load_contact_links(api_user()); + + $channel = $a->get_channel(); - logger('API call for ' . $a->user['username'] . ': ' . $a->query_string); + logger('API call for ' . $channel['channel_name'] . ': ' . $a->query_string); logger('API parameters: ' . print_r($_REQUEST,true)); + $type="json"; + if (strpos($a->query_string, ".xml")>0) $type="xml"; if (strpos($a->query_string, ".json")>0) $type="json"; if (strpos($a->query_string, ".rss")>0) $type="rss"; @@ -133,7 +175,7 @@ return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r; break; case "json": - //header ("Content-Type: application/json"); + header ("Content-Type: application/json"); foreach($r as $rr) return json_encode($rr); break; @@ -192,7 +234,7 @@ 'updated' => api_date(null), 'atom_updated' => datetime_convert('UTC','UTC','now',ATOM_TIME), 'language' => $user_info['language'], - 'logo' => $a->get_baseurl()."/images/friendica-32.png", + 'logo' => $a->get_baseurl()."/images/rm-64.png", ); return $arr; @@ -201,6 +243,7 @@ /** * Returns user info array. */ + function api_get_user(&$a, $contact_id = Null){ global $called_api; $user = null; @@ -209,46 +252,48 @@ if(!is_null($contact_id)){ $user=$contact_id; - $extra_query = "AND `contact`.`id` = %d "; + $extra_query = " AND abook_id = %d "; } if(is_null($user) && x($_GET, 'user_id')) { $user = intval($_GET['user_id']); - $extra_query = "AND `contact`.`id` = %d "; + $extra_query = " AND abook_id = %d "; } if(is_null($user) && x($_GET, 'screen_name')) { $user = dbesc($_GET['screen_name']); - $extra_query = "AND `contact`.`nick` = '%s' "; - if (local_user()!==false) $extra_query .= "AND `contact`.`uid`=".intval(local_user()); - + $extra_query = " AND xchan_addr like '%s@%%' "; + if (api_user()!==false) + $extra_query .= " AND abook_channel = ".intval(api_user()); } - if (is_null($user) && $a->argc > (count($called_api)-1)){ + if (is_null($user) && argc() > (count($called_api)-1)){ $argid = count($called_api); - list($user, $null) = explode(".",$a->argv[$argid]); + list($user, $null) = explode(".",argv($argid)); if(is_numeric($user)){ $user = intval($user); - $extra_query = "AND `contact`.`id` = %d "; + $extra_query = " AND abook_id = %d "; } else { $user = dbesc($user); - $extra_query = "AND `contact`.`nick` = '%s' "; - if (local_user()!==false) $extra_query .= "AND `contact`.`uid`=".intval(local_user()); + $extra_query = " AND xchan_addr like '%s@%%' "; + if (api_user() !== false) + $extra_query .= " AND abook_channel = ".intval(api_user()); } } if (! $user) { - if (local_user()===false) { - api_login($a); return False; + if (api_user() === false) { + api_login($a); + return False; } else { - $user = $_SESSION['uid']; - $extra_query = "AND `contact`.`uid` = %d AND `contact`.`self` = 1 "; + $user = local_user(); + $extra_query = " AND abook_channel = %d AND (abook_flags & " . ABOOK_FLAG_SELF . " ) "; } } logger('api_user: ' . $extra_query . ', user: ' . $user); // user info - $uinfo = q("SELECT *, `contact`.`id` as `cid` FROM `contact` + $uinfo = q("SELECT * from abook left join xchan on abook_xchan = xchan_hash WHERE 1 $extra_query", $user @@ -257,81 +302,77 @@ return False; } - if($uinfo[0]['self']) { - $usr = q("select * from user where uid = %d limit 1", - intval(local_user()) + if($uinfo[0]['abook_flags'] & ABOOK_FLAG_SELF) { + $usr = q("select * from channel where channel_id = %d limit 1", + intval(api_user()) ); $profile = q("select * from profile where uid = %d and `is_default` = 1 limit 1", - intval(local_user()) + intval(api_user()) ); // count public wall messages $r = q("SELECT COUNT(`id`) as `count` FROM `item` - WHERE `uid` = %d - AND `type`='wall' + WHERE `uid` = %d + AND ( item_flags & %d ) and item_restrict = 0 AND `allow_cid`='' AND `allow_gid`='' AND `deny_cid`='' AND `deny_gid`=''", - intval($uinfo[0]['uid']) + intval($usr[0]['channel_id']), + intval(ITEM_WALL) ); $countitms = $r[0]['count']; } else { $r = q("SELECT COUNT(`id`) as `count` FROM `item` - WHERE `contact-id` = %d + WHERE author_xchan = '%s' AND `allow_cid`='' AND `allow_gid`='' AND `deny_cid`='' AND `deny_gid`=''", - intval($uinfo[0]['id']) + intval($uinfo[0]['xchan_hash']) ); $countitms = $r[0]['count']; } + // count friends - $r = q("SELECT COUNT(`id`) as `count` FROM `contact` - WHERE `uid` = %d AND `rel` IN ( %d, %d ) - AND `self`=0 AND `blocked`=0 AND `pending`=0 AND `hidden`=0", - intval($uinfo[0]['uid']), - intval(CONTACT_IS_SHARING), - intval(CONTACT_IS_FRIEND) - ); - $countfriends = $r[0]['count']; - - $r = q("SELECT COUNT(`id`) as `count` FROM `contact` - WHERE `uid` = %d AND `rel` IN ( %d, %d ) - AND `self`=0 AND `blocked`=0 AND `pending`=0 AND `hidden`=0", - intval($uinfo[0]['uid']), - intval(CONTACT_IS_FOLLOWER), - intval(CONTACT_IS_FRIEND) - ); - $countfollowers = $r[0]['count']; + if($usr) { + $r = q("SELECT COUNT(abook_id) as `count` FROM abook + WHERE abook_channel = %d AND abook_flags = 0 ", + intval($usr[0]['channel_id']) + ); + $countfriends = $r[0]['count']; + $countfollowers = $r[0]['count']; + } - $r = q("SELECT count(`id`) as `count` FROM item where starred = 1 and uid = %d and deleted = 0", - intval($uinfo[0]['uid']) + $r = q("SELECT count(`id`) as `count` FROM item where ( item_flags & %d ) and uid = %d and item_restrict = 0", + intval($uinfo[0]['channel_id']), + intval(ITEM_STARRED) ); $starred = $r[0]['count']; - if(! $uinfo[0]['self']) { + if(! ($uinfo[0]['abook_flags'] & ABOOK_FLAG_SELF)) { $countfriends = 0; $countfollowers = 0; $starred = 0; } $ret = Array( - 'id' => intval($uinfo[0]['cid']), - 'self' => intval($uinfo[0]['self']), - 'uid' => intval($uinfo[0]['uid']), - 'name' => (($uinfo[0]['name']) ? $uinfo[0]['name'] : $uinfo[0]['nick']), - 'screen_name' => (($uinfo[0]['nick']) ? $uinfo[0]['nick'] : $uinfo[0]['name']), - 'location' => ($usr) ? $usr[0]['default-location'] : '', - 'profile_image_url' => $uinfo[0]['micro'], - 'url' => $uinfo[0]['url'], - 'contact_url' => $a->get_baseurl()."/contacts/".$uinfo[0]['cid'], + 'id' => intval($uinfo[0]['abook_id']), + 'self' => (($uinfo[0]['abook_flags'] & ABOOK_FLAG_SELF) ? 1 : 0), + 'uid' => intval($uinfo[0]['abook_channel']), + 'guid' => $uinfo[0]['xchan_hash'], + 'name' => (($uinfo[0]['xchan_name']) ? $uinfo[0]['xchan_name'] : substr($uinfo[0]['xchan_addr'],0,strpos($uinfo[0]['xchan_addr'],'@'))), + 'screen_name' => substr($uinfo[0]['xchan_addr'],0,strpos($uinfo[0]['xchan_addr'],'@')), + 'location' => ($usr) ? $usr[0]['channel_location'] : '', + 'profile_image_url' => $uinfo[0]['xchan_photo_l'], + 'url' => $uinfo[0]['xchan_url'], +//FIXME + 'contact_url' => $a->get_baseurl() . "/connections/".$uinfo[0]['abook_id'], 'protected' => false, 'friends_count' => intval($countfriends), - 'created_at' => api_date($uinfo[0]['name_date']), + 'created_at' => api_date($uinfo[0]['abook_created']), 'utc_offset' => "+00:00", 'time_zone' => 'UTC', //$uinfo[0]['timezone'], 'geo_enabled' => false, 'statuses_count' => intval($countitms), #XXX: fix me - 'lang' => 'en', #XXX: fix me + 'lang' => get_app()->language, 'description' => (($profile) ? $profile[0]['pdesc'] : ''), 'followers_count' => intval($countfollowers), 'favourites_count' => intval($starred), @@ -355,30 +396,20 @@ } + function api_item_get_user(&$a, $item) { global $usercache; // The author is our direct contact, in a conversation with us. - if(link_compare($item['url'],$item['author-link'])) { - return api_get_user($a,$item['cid']); - } - else { - // The author may be a contact of ours, but is replying to somebody else. - // Figure out if we know him/her. - $normalised = normalise_link((strlen($item['author-link'])) ? $item['author-link'] : $item['url']); - if(($normalised != 'mailbox') && (x($a->contacts[$normalised]))) - return api_get_user($a,$a->contacts[$normalised]['id']); - } + + if($item['author']['abook_id']) { + return api_get_user($a,$item['author']['abook_id']); + } + // We don't know this person directly. - list($nick, $name) = array_map("trim",explode("(",$item['author-name'])); - $name=str_replace(")","",$name); - - if ($name == '') - $name = $nick; - - if ($nick == '') - $nick = $name; + $nick = substr($item['author']['xchan_addr'],0,strpos($item['author']['xchan_addr'],'@')); + $name = $item['author']['xchan_name']; // Generating a random ID if (is_null($usercache[$nick]) or !array_key_exists($nick, $usercache)) @@ -390,8 +421,8 @@ 'screen_name' => $nick, 'location' => '', //$uinfo[0]['default-location'], 'description' => '', - 'profile_image_url' => $item['author-avatar'], - 'url' => $item['author-link'], + 'profile_image_url' => $item['author']['xchan_photo_m'], + 'url' => $item['author']['xchan_url'], 'protected' => false, # 'followers_count' => 0, 'friends_count' => 0, @@ -448,9 +479,6 @@ return $ret; } - /** - ** TWITTER API - */ /** * Returns an HTTP 200 OK response code and a representation of the requesting user if authentication was successful; @@ -458,77 +486,143 @@ * http://developer.twitter.com/doc/get/account/verify_credentials */ function api_account_verify_credentials(&$a, $type){ - if (local_user()===false) return false; + if (api_user()===false) return false; $user_info = api_get_user($a); return api_apply_template("user", $type, array('$user' => $user_info)); } api_register_func('api/account/verify_credentials','api_account_verify_credentials', true); + + + function api_account_logout(&$a, $type){ + require_once('include/auth.php'); + nuke_session(); + return api_apply_template("user", $type, array('$user' => null)); + + } + api_register_func('api/account/logout','api_account_logout', false); + /** - * get data from $_POST or $_GET + * get data from $_REQUEST ( e.g. $_POST or $_GET ) */ - function requestdata($k){ - if (isset($_POST[$k])){ - return $_POST[$k]; + function requestdata($k) { + if(array_key_exists($k,$_REQUEST)) + return $_REQUEST[$k]; + return null; + } + + + /* + * Red basic channel export + */ + + function api_export_basic(&$a, $type) { + if(api_user() === false) { + logger('api_export_basic: no user'); + return false; } - if (isset($_GET[$k])){ - return $_GET[$k]; + + require_once('include/identity.php'); + + json_return_and_die(identity_basic_export(api_user())); + } + api_register_func('api/export/basic','api_export_basic', true); + api_register_func('api/red/channel/export/basic','api_export_basic', true); + + + function api_channel_stream(&$a, $type) { + if(api_user() === false) { + logger('api_channel_stream: no user'); + return false; } - return null; + + if($_SERVER['REQUEST_METHOD'] == 'POST') { + json_return_and_die(post_activity_item($_REQUEST)); + } + else { + // fetch stream + + } + } + api_register_func('api/red/channel/stream','api_channel_stream', true); + + + function api_albums(&$a,$type) { + json_return_and_die(photos_albums_list($a->get_channel(),$a->get_observer())); + } + api_register_func('api/red/albums','api_albums', true); + + function api_photos(&$a,$type) { + $album = $_REQUEST['album']; + json_return_and_die(photos_list_photos($a->get_channel(),$a->get_observer(),$album)); } + api_register_func('api/red/photos','api_photos', true); + + + + + function api_statuses_mediap(&$a, $type) { - if (local_user()===false) { - logger('api_statuses_update: no user'); - return false; - } - $user_info = api_get_user($a); - - $_REQUEST['type'] = 'wall'; - $_REQUEST['profile_uid'] = local_user(); - $_REQUEST['api_source'] = true; - $txt = requestdata('status'); - //$txt = urldecode(requestdata('status')); - - require_once('library/HTMLPurifier.auto.php'); - require_once('include/html2bbcode.php'); - - if((strpos($txt,'<') !== false) || (strpos($txt,'>') !== false)) { + if (api_user() === false) { + logger('api_statuses_update: no user'); + return false; + } + $user_info = api_get_user($a); + + $_REQUEST['type'] = 'wall'; + $_REQUEST['profile_uid'] = api_user(); + $_REQUEST['api_source'] = true; + + $txt = requestdata('status'); + + require_once('library/HTMLPurifier.auto.php'); + require_once('include/html2bbcode.php'); + + if((strpos($txt,'<') !== false) || (strpos($txt,'>') !== false)) { $txt = html2bb_video($txt); $config = HTMLPurifier_Config::createDefault(); - $config->set('Cache.DefinitionImpl', null); + $config->set('Cache.DefinitionImpl', null); $purifier = new HTMLPurifier($config); - $txt = $purifier->purify($txt); + $txt = $purifier->purify($txt); } $txt = html2bbcode($txt); - $a->argv[1]=$user_info['screen_name']; //should be set to username? + $a->argv[1] = $user_info['screen_name']; $_REQUEST['silent']='1'; //tell wall_upload function to return img info instead of echo - require_once('mod/wall_upload.php'); - $bebop = wall_upload_post($a); + require_once('mod/wall_upload.php'); + $posted = wall_upload_post($a); //now that we have the img url in bbcode we can add it to the status and insert the wall item. - $_REQUEST['body']=$txt."\n\n".$bebop; - require_once('mod/item.php'); - item_post($a); - - // this should output the last post (the one we just posted). - return api_status_show($a,$type); - } - api_register_func('api/statuses/mediap','api_statuses_mediap', true); - + $_REQUEST['body']=$txt."\n\n".$posted; + require_once('mod/item.php'); + item_post($a); + // this should output the last post (the one we just posted). + return api_status_show($a,$type); + } + api_register_func('api/statuses/mediap','api_statuses_mediap', true); function api_statuses_update(&$a, $type) { - if (local_user()===false) { + if (api_user() === false) { logger('api_statuses_update: no user'); return false; } + + logger('api_statuses_update: REQUEST ' . print_r($_REQUEST,true)); + logger('api_statuses_update: FILES ' . print_r($_FILES,true)); + + + // set this so that the item_post() function is quiet and doesn't redirect or emit json + + $_REQUEST['api_source'] = true; + + $user_info = api_get_user($a); // convert $_POST array items to the form we use for web posts. @@ -557,23 +651,26 @@ } else $_REQUEST['body'] = requestdata('status'); - //$_REQUEST['body'] = urldecode(requestdata('status')); $parent = requestdata('in_reply_to_status_id'); + if(ctype_digit($parent)) $_REQUEST['parent'] = $parent; else - $_REQUEST['parent_uri'] = $parent; + $_REQUEST['parent_mid'] = $parent; if(requestdata('lat') && requestdata('long')) $_REQUEST['coord'] = sprintf("%s %s",requestdata('lat'),requestdata('long')); - $_REQUEST['profile_uid'] = local_user(); + + $_REQUEST['profile_uid'] = api_user(); if($parent) $_REQUEST['type'] = 'net-comment'; else { $_REQUEST['type'] = 'wall'; + if(x($_FILES,'media')) { + $_FILES['userfile'] = $_FILES['media']; // upload the image if we have one $_REQUEST['silent']='1'; //tell wall_upload function to return img info instead of echo require_once('mod/wall_upload.php'); @@ -583,10 +680,6 @@ } } - // set this so that the item_post() function is quiet and doesn't redirect or emit json - - $_REQUEST['api_source'] = true; - // call out normal post function require_once('mod/item.php'); @@ -598,40 +691,99 @@ api_register_func('api/statuses/update','api_statuses_update', true); + function red_item_new(&$a, $type) { + + if (api_user() === false) { + logger('api_red_item_new: no user'); + return false; + } + + logger('api_red_item_new: REQUEST ' . print_r($_REQUEST,true)); + logger('api_red_item_new: FILES ' . print_r($_FILES,true)); + + + // set this so that the item_post() function is quiet and doesn't redirect or emit json + + $_REQUEST['api_source'] = true; + $_REQUEST['profile_uid'] = api_user(); + + if(x($_FILES,'media')) { + $_FILES['userfile'] = $_FILES['media']; + // upload the image if we have one + $_REQUEST['silent']='1'; //tell wall_upload function to return img info instead of echo + require_once('mod/wall_upload.php'); + $media = wall_upload_post($a); + if(strlen($media)>0) + $_REQUEST['body'] .= "\n\n".$media; + } + + require_once('mod/item.php'); + $x = item_post($a); + json_return_and_die($x); + } + + api_register_func('api/red/item/new','red_item_new', true); + + + + + + + + + + function api_status_show(&$a, $type){ $user_info = api_get_user($a); - // get last public wall message - $lastwall = q("SELECT `item`.*, `i`.`contact-id` as `reply_uid`, `i`.`nick` as `reply_author` - FROM `item`, `contact`, - (SELECT `item`.`id`, `item`.`contact-id`, `contact`.`nick` FROM `item`,`contact` WHERE `contact`.`id`=`item`.`contact-id`) as `i` - WHERE `item`.`contact-id` = %d - AND `i`.`id` = `item`.`parent` - AND `contact`.`id`=`item`.`contact-id` AND `contact`.`self`=1 - AND `type`!='activity' - AND `item`.`allow_cid`='' AND `item`.`allow_gid`='' AND `item`.`deny_cid`='' AND `item`.`deny_gid`='' - ORDER BY `created` DESC - LIMIT 1", - intval($user_info['id']) + + // get last public message + + require_once('include/security.php'); + + $lastwall = q("SELECT * from item where 1 + and item_private = 0 and item_restrict = 0 + and author_xchan = '%s' + and allow_cid = '' and allow_gid = '' and deny_cid = '' and deny_gid = '' + and verb = '%s' + and uid in ( " . stream_perms_api_uids() . " ) + order by created desc limit 1", + dbesc($user_info['guid']), + dbesc(ACTIVITY_POST) ); - if (count($lastwall)>0){ + if($lastwall){ $lastwall = $lastwall[0]; $in_reply_to_status_id = ''; $in_reply_to_user_id = ''; $in_reply_to_screen_name = ''; + + if($lastwall['author_xchan'] != $lastwall['owner_xchan']) { + $w = q("select * from abook left join xchan on abook_xchan = xchan_hash where + xchan_hash = '%s' limit 1", + dbesc($lastwall['owner_xchan']) + ); + if($w) { + $in_reply_to_user_id = $w[0]['abook_id']; + $in_reply_to_screen_name = substr($w[0]['xchan_addr'],0,strpos($w[0]['xchan_addr'],'@')); + } + } + if ($lastwall['parent']!=$lastwall['id']) { - $in_reply_to_status_id=$lastwall['parent']; - $in_reply_to_user_id = $lastwall['reply_uid']; - $in_reply_to_screen_name = $lastwall['reply_author']; - } + $in_reply_to_status_id=$lastwall['thr_parent']; + if(! $in_reply_to_user_id) { + $in_reply_to_user_id = $user_info['id']; + $in_reply_to_screen_name = $user_info['screen_name']; + } + } + unobscure($lastwall); $status_info = array( - 'text' => html2plain(bbcode($lastwall['body']), 0), + 'text' => html2plain(prepare_text($lastwall['body'],$lastwall['mimetype']), 0), 'truncated' => false, 'created_at' => api_date($lastwall['created']), 'in_reply_to_status_id' => $in_reply_to_status_id, 'source' => (($lastwall['app']) ? $lastwall['app'] : 'web'), - 'id' => $lastwall['contact-id'], + 'id' => ($lastwall['id']), 'in_reply_to_user_id' => $in_reply_to_user_id, 'in_reply_to_screen_name' => $in_reply_to_screen_name, 'geo' => '', @@ -642,61 +794,79 @@ ); $status_info['user'] = $user_info; } + return api_apply_template("status", $type, array('$status' => $status_info)); } - - - /** * Returns extended information of a given user, specified by ID or screen name as per the required id parameter. * The author's most recent status will be returned inline. * http://developer.twitter.com/doc/get/users/show */ + +// FIXME - this is essentially the same as api_status_show except for the template formatting at the end. Consolidate. + + function api_users_show(&$a, $type){ $user_info = api_get_user($a); - // get last public wall message - $lastwall = q("SELECT `item`.*, `i`.`contact-id` as `reply_uid`, `i`.`nick` as `reply_author` - FROM `item`, `contact`, - (SELECT `item`.`id`, `item`.`contact-id`, `contact`.`nick` FROM `item`,`contact` WHERE `contact`.`id`=`item`.`contact-id`) as `i` - WHERE `item`.`contact-id` = %d - AND `i`.`id` = `item`.`parent` - AND `contact`.`id`=`item`.`contact-id` AND `contact`.`self`=1 - AND `type`!='activity' - AND `item`.`allow_cid`='' AND `item`.`allow_gid`='' AND `item`.`deny_cid`='' AND `item`.`deny_gid`='' - ORDER BY `created` DESC - LIMIT 1", - intval($user_info['id']) + + require_once('include/security.php'); + + $lastwall = q("SELECT * from item where 1 + and item_private != 0 and item_restrict = 0 + and author_xchan = '%s' + and allow_cid = '' and allow_gid = '' and deny_cid = '' and deny_gid = '' + and verb = '%s' + and uid in ( " . stream_perms_api_uids() . " ) + order by created desc limit 1", + dbesc($user_info['guid']), + dbesc(ACTIVITY_POST) ); - if (count($lastwall)>0){ + if($lastwall){ $lastwall = $lastwall[0]; $in_reply_to_status_id = ''; $in_reply_to_user_id = ''; $in_reply_to_screen_name = ''; + + if($lastwall['author_xchan'] != $lastwall['owner_xchan']) { + $w = q("select * from abook left join xchan on abook_xchan = xchan_hash where + xchan_hash = '%s' limit 1", + dbesc($lastwall['owner_xchan']) + ); + if($w) { + $in_reply_to_user_id = $w[0]['abook_id']; + $in_reply_to_screen_name = substr($w[0]['xchan_addr'],0,strpos($w[0]['xchan_addr'],'@')); + } + } + if ($lastwall['parent']!=$lastwall['id']) { - $in_reply_to_status_id=$lastwall['parent']; - $in_reply_to_user_id = $lastwall['reply_uid']; - $in_reply_to_screen_name = $lastwall['reply_author']; + $in_reply_to_status_id=$lastwall['thr_parent']; + if(! $in_reply_to_user_id) { + $in_reply_to_user_id = $user_info['id']; + $in_reply_to_screen_name = $user_info['screen_name']; + } } + unobscure($lastwall); $user_info['status'] = array( - 'created_at' => api_date($lastwall['created']), - 'id' => $lastwall['contact-id'], - 'text' => html2plain(bbcode($lastwall['body']), 0), - 'source' => (($lastwall['app']) ? $lastwall['app'] : 'web'), + 'text' => html2plain(prepare_text($lastwall['body'],$lastwall['mimetype']), 0), 'truncated' => false, + 'created_at' => api_date($lastwall['created']), 'in_reply_to_status_id' => $in_reply_to_status_id, + 'source' => (($lastwall['app']) ? $lastwall['app'] : 'web'), + 'id' => (($w) ? $w[0]['abook_id'] : $user_info['id']), 'in_reply_to_user_id' => $in_reply_to_user_id, - 'favorited' => false, 'in_reply_to_screen_name' => $in_reply_to_screen_name, 'geo' => '', + 'favorited' => false, 'coordinates' => $lastwall['coord'], 'place' => $lastwall['location'], - 'contributors' => '' + 'contributors' => '' ); + } return api_apply_template("user", $type, array('$user' => $user_info)); @@ -710,19 +880,22 @@ * TODO: Optional parameters * TODO: Add reply info */ + function api_statuses_home_timeline(&$a, $type){ - if (local_user()===false) return false; + if (api_user() === false) + return false; $user_info = api_get_user($a); - // get last newtork messages + // get last network messages // params - $count = (x($_REQUEST,'count')?$_REQUEST['count']:20); - $page = (x($_REQUEST,'page')?$_REQUEST['page']-1:0); - if ($page<0) $page=0; - $since_id = (x($_REQUEST,'since_id')?$_REQUEST['since_id']:0); - $max_id = (x($_REQUEST,'max_id')?$_REQUEST['max_id']:0); + $count = (x($_REQUEST,'count')?$_REQUEST['count']:20); + $page = (x($_REQUEST,'page')?$_REQUEST['page']-1:0); + if($page < 0) + $page = 0; + $since_id = (x($_REQUEST,'since_id')?$_REQUEST['since_id']:0); + $max_id = (x($_REQUEST,'max_id')?$_REQUEST['max_id']:0); $exclude_replies = (x($_REQUEST,'exclude_replies')?1:0); //$since_id = 0;//$since_id = (x($_REQUEST,'since_id')?$_REQUEST['since_id']:0); @@ -736,33 +909,40 @@ if ($exclude_replies > 0) $sql_extra .= ' AND `item`.`parent` = `item`.`id`'; - $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, - `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, - `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn_id`, `contact`.`self`, - `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid` - FROM `item`, `contact` - WHERE `item`.`uid` = %d - AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0 - AND `contact`.`id` = `item`.`contact-id` - AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 + if (api_user() != $user_info['uid']) { + $observer = get_app()->get_observer(); + require_once('include/permissions.php'); + if(! perm_is_allowed($user_info['uid'],(($observer) ? $observer['xchan_hash'] : ''),'view_stream')) + return ''; + $sql_extra .= " and item_private = 0 "; + } + + $r = q("SELECT * from item WHERE uid = %d and item_restrict = 0 $sql_extra - AND `item`.`id`>%d - ORDER BY `item`.`received` DESC LIMIT %d ,%d ", + AND id > %d + ORDER BY received DESC LIMIT %d ,%d ", intval($user_info['uid']), intval($since_id), - intval($start), intval($count) + intval($start), + intval($count) ); + xchan_query($r,true); + $ret = api_format_items($r,$user_info); // We aren't going to try to figure out at the item, group, and page // level which items you've seen and which you haven't. If you're looking // at the network timeline just mark everything seen. - $r = q("UPDATE `item` SET `unseen` = 0 - WHERE `unseen` = 1 AND `uid` = %d", - intval($user_info['uid']) - ); + if (api_user() == $user_info['uid']) { + $r = q("UPDATE `item` SET item_flags = ( item_flags ^ %d ) + WHERE item_flags & %d and uid = %d", + intval(ITEM_UNSEEN), + intval(ITEM_UNSEEN), + intval($user_info['uid']) + ); + } $data = array('$statuses' => $ret); @@ -785,7 +965,7 @@ api_register_func('api/statuses/friends_timeline','api_statuses_home_timeline', true); function api_statuses_public_timeline(&$a, $type){ - if (local_user()===false) return false; + if (api_user()===false) return false; $user_info = api_get_user($a); // get last newtork messages @@ -805,42 +985,22 @@ if ($max_id > 0) $sql_extra = 'AND `item`.`id` <= '.intval($max_id); + require_once('include/security.php'); - /*$r = q("SELECT `item`.*, `item`.`id` AS `item_id`, - `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, - `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn_id`, `contact`.`self`, - `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid` - FROM `item`, `contact` - WHERE `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0 - AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' - AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' - AND `item`.`private` = 0 AND `item`.`wall` = 1 AND `user`.`hidewall` = 0 - AND `contact`.`id` = `item`.`contact-id` - AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 - $sql_extra - AND `item`.`id`>%d - ORDER BY `item`.`received` DESC LIMIT %d ,%d ", - intval($since_id), - intval($start), intval($count) - );*/ - $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, - `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, - `contact`.`network`, `contact`.`thumb`, `contact`.`self`, `contact`.`writable`, - `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid`, - `user`.`nickname`, `user`.`hidewall` - FROM `item` LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - LEFT JOIN `user` ON `user`.`uid` = `item`.`uid` - WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 - AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' - AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' - AND `item`.`private` = 0 AND `item`.`wall` = 1 AND `user`.`hidewall` = 0 - AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 + $r = q("select * from item where item_restrict = 0 + and allow_cid = '' and allow_gid = '' + and deny_cid = '' and deny_gid = '' + and item_private = 0 + and uid in ( " . stream_perms_api_uids() . " ) $sql_extra - AND `item`.`id`>%d - ORDER BY `received` DESC LIMIT %d, %d ", + AND id > %d group by mid + order by received desc LIMIT %d, %d ", intval($since_id), - intval($start), - intval($count)); + intval($start), + intval($count) + ); + + xchan_query($r,true); $ret = api_format_items($r,$user_info); @@ -853,7 +1013,7 @@ break; case "as": $as = api_format_as($a, $ret, $user_info); - $as['title'] = $a->config['sitename']." Public Timeline"; + $as['title'] = $a->config['sitename']. " " . t('Public Timeline'); $as['link']['url'] = $a->get_baseurl()."/"; return($as); break; @@ -865,14 +1025,15 @@ /** * + */ function api_statuses_show(&$a, $type){ - if (local_user()===false) return false; + if (api_user()===false) return false; $user_info = api_get_user($a); // params - $id = intval($a->argv[3]); + $id = intval(argv(3)); logger('API: api_statuses_show: '.$id); @@ -885,17 +1046,10 @@ else $sql_extra .= " AND `item`.`id` = %d"; - $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, - `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, - `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn_id`, `contact`.`self`, - `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid` - FROM `item`, `contact` - WHERE `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0 - AND `contact`.`id` = `item`.`contact-id` - AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 - $sql_extra", + $r = q("select * from item where item_restrict = 0 $sql_extra", intval($id) ); + xchan_query($r,true); $ret = api_format_items($r,$user_info); @@ -919,39 +1073,36 @@ * */ function api_statuses_repeat(&$a, $type){ - if (local_user()===false) return false; + if (api_user()===false) return false; $user_info = api_get_user($a); // params - $id = intval($a->argv[3]); + $id = intval(argv(3)); logger('API: api_statuses_repeat: '.$id); //$include_entities = (x($_REQUEST,'include_entities')?$_REQUEST['include_entities']:false); - $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `contact`.`nick` as `reply_author`, - `contact`.`name`, `contact`.`photo`, `contact`.`url` as `reply_url`, `contact`.`rel`, - `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn_id`, `contact`.`self`, - `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid` - FROM `item`, `contact` - WHERE `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0 - AND `contact`.`id` = `item`.`contact-id` - AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 - $sql_extra - AND `item`.`id`=%d", + $observer = get_app()->get_observer(); + + $r = q("SELECT * from item where item_restrict = 0 and id = %d limit 1", intval($id) ); - if ($r[0]['body'] != "") { - $_REQUEST['body'] = html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8')."[url=".$r[0]['reply_url']."]".$r[0]['reply_author']."[/url] \n".$r[0]['body']; - $_REQUEST['profile_uid'] = local_user(); - $_REQUEST['type'] = 'wall'; - $_REQUEST['api_source'] = true; + if(perm_is_allowed($r[0]['uid'],$observer['xchan_hash'],'view_stream')) { + if ($r[0]['body'] != "") { + $_REQUEST['body'] = html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8')."[zrl=".$r[0]['reply_url']."]".$r[0]['reply_author']."[/zrl] \n".$r[0]['body']; + $_REQUEST['profile_uid'] = api_user(); + $_REQUEST['type'] = 'wall'; + $_REQUEST['api_source'] = true; - require_once('mod/item.php'); - item_post($a); + require_once('mod/item.php'); + item_post($a); + } } + else + return false; if ($type == 'xml') $ok = "true"; @@ -965,19 +1116,54 @@ /** * */ + function api_statuses_destroy(&$a, $type){ - if (local_user()===false) return false; + if (api_user()===false) return false; $user_info = api_get_user($a); // params - $id = intval($a->argv[3]); + $id = intval(argv(3)); + if($id) { + // first prove that we own the item - logger('API: api_statuses_destroy: '.$id); + $r = q("select * from item where id = %d and uid = %d limit 1", + intval($id), + intval($user_info['uid']) + ); + if(! $r) + return false; + } + else { + if($_REQUEST['namespace'] && $_REQUEST['remote_id']) { + $r = q("select * from item_id where service = '%s' and sid = '%s' and uid = %d limit 1", + dbesc($_REQUEST['namespace']), + dbesc($_REQUEST['remote_id']), + intval($user_info['uid']) + ); + if(! $r) + return false; + $id = $r[0]['iid']; + } + if($_REQUEST['namespace'] && $_REQUEST['comment_id']) { + $r = q("select * from item_id left join item on item.id = item_id.iid where service = '%s' and sid = '%s' and uid = %d and item.id != item.parent limit 1", + dbesc($_REQUEST['namespace']), + dbesc($_REQUEST['comment_id']), + intval($user_info['uid']) + ); + if(! $r) + return false; + $id = $r[0]['iid']; + } + } + if(! $id) + return false; + logger('API: api_statuses_destroy: '.$id); require_once('include/items.php'); drop_item($id, false); + if ($type == 'xml') $ok = "true"; else @@ -992,11 +1178,13 @@ * http://developer.twitter.com/doc/get/statuses/mentions * */ + +// FIXME function api_statuses_mentions(&$a, $type){ - if (local_user()===false) return false; + if (api_user()===false) return false; $user_info = api_get_user($a); - // get last newtork messages + // get last network messages // params @@ -1069,17 +1257,18 @@ return api_apply_template("timeline", $type, $data); } api_register_func('api/statuses/mentions','api_statuses_mentions', true); + // FIXME?? I don't think mentions and replies are congruent in this case api_register_func('api/statuses/replies','api_statuses_mentions', true); function api_statuses_user_timeline(&$a, $type){ - if (local_user()===false) return false; + if (api_user()===false) return false; $user_info = api_get_user($a); - // get last newtork messages + // get last network messages - logger("api_statuses_user_timeline: local_user: ". local_user() . + logger("api_statuses_user_timeline: api_user: ". api_user() . "\nuser_info: ".print_r($user_info, true) . "\n_REQUEST: ".print_r($_REQUEST, true), LOGGER_DEBUG); @@ -1096,27 +1285,43 @@ $sql_extra = ''; if ($user_info['self']==1) $sql_extra .= " AND `item`.`wall` = 1 "; + +//FIXME - this isn't yet implemented if ($exclude_replies > 0) $sql_extra .= ' AND `item`.`parent` = `item`.`id`'; - $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, - `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, - `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn_id`, `contact`.`self`, - `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid` - FROM `item`, `contact` - WHERE `item`.`uid` = %d - AND `item`.`contact-id` = %d - AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0 - AND `contact`.`id` = `item`.`contact-id` - AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 - $sql_extra - AND `item`.`id`>%d - ORDER BY `item`.`received` DESC LIMIT %d ,%d ", - intval(local_user()), - intval($user_info['id']), - intval($since_id), - intval($start), intval($count) - ); +// $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, +// `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, +// `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn_id`, `contact`.`self`, +// `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid` +// FROM `item`, `contact` +// WHERE `item`.`uid` = %d +// AND `item`.`contact-id` = %d +// AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0 +// AND `contact`.`id` = `item`.`contact-id` +// AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 +// $sql_extra +// AND `item`.`id`>%d +// ORDER BY `item`.`received` DESC LIMIT %d ,%d ", +// intval(api_user()), +// intval($user_info['id']), +// intval($since_id), +// intval($start), intval($count) +// ); + + $arr = array( + 'uid' => api_user(), + 'since_id' => $since_id, + 'start' => $start, + 'records' => $count); + + if ($user_info['self']==1) + $arr['wall'] = 1; + else + $arr['cid'] = $user_info['id']; + + $r = items_fetch($arr,get_app()->get_channel(),get_observer_hash()); + $ret = api_format_items($r,$user_info); @@ -1134,7 +1339,7 @@ function api_favorites(&$a, $type){ - if (local_user()===false) return false; + if (api_user()===false) return false; $user_info = api_get_user($a); // in friendica starred item are private @@ -1252,7 +1457,7 @@ function api_format_messages($item, $recipient, $sender) { // standard meta information - $ret=Array( + $ret = array( 'id' => $item['id'], 'created_at' => api_date($item['created']), 'sender_id' => $sender['id'] , @@ -1262,19 +1467,19 @@ 'recipient_screen_name' => $recipient['screen_name'], 'recipient' => $recipient, ); - + unobscure($item); //don't send title to regular StatusNET requests to avoid confusing these apps if (x($_GET, 'getText')) { $ret['title'] = $item['title'] ; if ($_GET["getText"] == "html") { - $ret['text'] = bbcode($item['body']); + $ret['text'] = prepare_text($item['body'],$item['mimetype']); } elseif ($_GET["getText"] == "plain") { - $ret['text'] = html2plain(bbcode($item['body']), 0); + $ret['text'] = html2plain(prepare_text($item['body'],$item['mimetype']), 0); } } else { - $ret['text'] = $item['title']."\n".html2plain(bbcode($item['body']), 0); + $ret['text'] = $item['title']."\n".html2plain(prepare_text($item['body'],$item['mimetype']), 0); } if (isset($_GET["getUserObjects"]) && $_GET["getUserObjects"] == "false") { unset($ret['sender']); @@ -1291,34 +1496,36 @@ //logger('api_format_items: ' . print_r($user_info,true)); $a = get_app(); - $ret = Array(); + $ret = array(); foreach($r as $item) { localize_item($item); - $status_user = (($item['cid']==$user_info['id'])?$user_info: api_item_get_user($a,$item)); - if ($item['parent']!=$item['id']) { - $r = q("select id from item where parent=%s and id<%s order by id desc limit 1", - intval($item['parent']), intval($item['id'])); + $status_user = (($item['author_xchan']==$user_info['guid'])?$user_info: api_item_get_user($a,$item)); + + if($item['parent'] != $item['id']) { + $r = q("select id from item where parent= %d and id < %d order by id desc limit 1", + intval($item['parent']), + intval($item['id']) + ); if ($r) $in_reply_to_status_id = $r[0]['id']; else $in_reply_to_status_id = $item['parent']; - $r = q("select `item`.`contact-id`, `contact`.nick, `item`.`author-name` from item, contact - where `contact`.`id` = `item`.`contact-id` and `item`.id=%d", intval($in_reply_to_status_id)); + xchan_query($r,true); - $in_reply_to_screen_name = $r[0]['author-name']; - $in_reply_to_user_id = $r[0]['contact-id']; + $in_reply_to_screen_name = $r[0]['author']['xchan_name']; + $in_reply_to_user_id = $r[0]['author']['abook_id']; } else { $in_reply_to_screen_name = ''; $in_reply_to_user_id = 0; $in_reply_to_status_id = 0; } - + unobscure($item); // Workaround for ostatus messages where the title is identically to the body - $statusbody = trim(html2plain(bbcode($item['body']), 0)); + $statusbody = trim(html2plain(prepare_text($item['body'],$item['mimetype']), 0)); $statustitle = trim($item['title']); if (($statustitle != '') and (strpos($statusbody, $statustitle) !== false)) @@ -1326,41 +1533,40 @@ else $statustext = trim($statustitle."\n\n".$statusbody); - if (($item["network"] == NETWORK_FEED) and (strlen($statustext)> 1000)) - $statustext = substr($statustext, 0, 1000)."... \n".$item["plink"]; $status = array( - 'text' => $statustext, - 'truncated' => False, - 'created_at'=> api_date($item['created']), - 'in_reply_to_status_id' => $in_reply_to_status_id, - 'source' => (($item['app']) ? $item['app'] : 'web'), - 'id' => intval($item['id']), - 'in_reply_to_user_id' => $in_reply_to_user_id, - 'in_reply_to_screen_name' => $in_reply_to_screen_name, - 'geo' => '', - 'favorited' => $item['starred'] ? true : false, - 'user' => $status_user , - 'statusnet_html' => trim(bbcode($item['body'])), + 'text' => $statustext, + 'truncated' => False, + 'created_at' => api_date($item['created']), + 'in_reply_to_status_id' => $in_reply_to_status_id, + 'source' => (($item['app']) ? $item['app'] : 'web'), + 'id' => intval($item['id']), + 'in_reply_to_user_id' => $in_reply_to_user_id, + 'in_reply_to_screen_name' => $in_reply_to_screen_name, + 'geo' => '', + 'favorited' => (($item['item_flags'] & ITEM_STARRED) ? true : false), + 'user' => $status_user , + 'statusnet_html' => trim(prepare_text($item['body'],$item['mimetype'])), + 'statusnet_conversation_id' => $item['parent'], ); // Seesmic doesn't like the following content if ($_SERVER['HTTP_USER_AGENT'] != 'Seesmic') { $status2 = array( - 'updated' => api_date($item['edited']), - 'published' => api_date($item['created']), - 'message_id' => $item['uri'], - 'url' => ($item['plink']!=''?$item['plink']:$item['author-link']), - 'coordinates' => $item['coord'], - 'place' => $item['location'], + 'updated' => api_date($item['edited']), + 'published' => api_date($item['created']), + 'message_id' => $item['mid'], + 'url' => $item['plink'], + 'coordinates' => $item['coord'], + 'place' => $item['location'], 'contributors' => '', 'annotations' => '', - 'entities' => '', - 'objecttype' => (($item['obj_type']) ? $item['obj_type'] : ACTIVITY_OBJ_NOTE), - 'verb' => (($item['verb']) ? $item['verb'] : ACTIVITY_POST), - 'self' => $a->get_baseurl()."/api/statuses/show/".$item['id'].".".$type, - 'edit' => $a->get_baseurl()."/api/statuses/show/".$item['id'].".".$type, + 'entities' => '', + 'objecttype' => (($item['obj_type']) ? $item['obj_type'] : ACTIVITY_OBJ_NOTE), + 'verb' => (($item['verb']) ? $item['verb'] : ACTIVITY_POST), + 'self' => $a->get_baseurl()."/api/statuses/show/".$item['id'].".".$type, + 'edit' => $a->get_baseurl()."/api/statuses/show/".$item['id'].".".$type, ); $status = array_merge($status, $status2); @@ -1398,7 +1604,7 @@ return api_apply_template('test', $type, array('$ok' => $ok)); } - api_register_func('api/help/test','api_help_test',true); + api_register_func('api/help/test','api_help_test',false); /** * https://dev.twitter.com/docs/api/1/get/statuses/friends @@ -1406,7 +1612,7 @@ * returns: json, xml **/ function api_statuses_f(&$a, $type, $qtype) { - if (local_user()===false) return false; + if (api_user()===false) return false; $user_info = api_get_user($a); @@ -1426,18 +1632,20 @@ return false; } + // For Red, the closest thing we can do to figure out if you're friends is if both of you are sending each other your streams. + // This won't work if either of you send your stream to everybody on the network if($qtype == 'friends') - $sql_extra = sprintf(" AND ( `rel` = %d OR `rel` = %d ) ", intval(CONTACT_IS_SHARING), intval(CONTACT_IS_FRIEND)); + $sql_extra = sprintf(" AND ( abook_their_perms & %d ) and ( abook_my_perms & %d ) ", intval(PERMS_W_STREAM), intval(PERMS_W_STREAM)); if($qtype == 'followers') - $sql_extra = sprintf(" AND ( `rel` = %d OR `rel` = %d ) ", intval(CONTACT_IS_FOLLOWER), intval(CONTACT_IS_FRIEND)); + $sql_extra = sprintf(" AND ( abook_my_perms & %d ) and not ( abook_their_perms & %d ) ", intval(PERMS_W_STREAM), intval(PERMS_W_STREAM)); - $r = q("SELECT id FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 $sql_extra", - intval(local_user()) + $r = q("SELECT abook_id FROM abook where abook_flags = 0 and abook_channel = %d $sql_extra", + intval(api_user()) ); $ret = array(); foreach($r as $cid){ - $ret[] = api_get_user($a, $cid['id']); + $ret[] = api_get_user($a, $cid['abook_id']); } @@ -1463,37 +1671,41 @@ function api_statusnet_config(&$a,$type) { - $name = $a->config['sitename']; + + load_config('system'); + + $name = get_config('system','sitename'); $server = $a->get_hostname(); - $logo = $a->get_baseurl() . '/images/friendica-64.png'; - $email = $a->config['admin_email']; - $closed = (($a->config['system']['register_policy'] == REGISTER_CLOSED) ? 'true' : 'false'); - $private = (($a->config['system']['block_public']) ? 'true' : 'false'); - $textlimit = (string) (($a->config['max_import_size']) ? $a->config['max_import_size'] : 200000); - if($a->config['api_import_size']) - $texlimit = string($a->config['api_import_size']); - $ssl = (($a->config['system']['have_ssl']) ? 'true' : 'false'); + $logo = $a->get_baseurl() . '/images/rm-64.png'; + $email = get_config('system','admin_email'); + $closed = ((get_config('system','register_policy') == REGISTER_CLOSED) ? 'true' : 'false'); + $private = ((get_config('system','block_public')) ? 'true' : 'false'); + $textlimit = (string) ((get_config('system','max_import_size')) ? get_config('system','max_import_size') : 200000); + if(get_config('system','api_import_size')) + $texlimit = string(get_config('system','api_import_size')); + $ssl = ((get_config('system','have_ssl')) ? 'true' : 'false'); $sslserver = (($ssl === 'true') ? str_replace('http:','https:',$a->get_baseurl()) : ''); $config = array( 'site' => array('name' => $name,'server' => $server, 'theme' => 'default', 'path' => '', - 'logo' => $logo, 'fancy' => 'true', 'language' => 'en', 'email' => $email, 'broughtby' => '', - 'broughtbyurl' => '', 'timezone' => 'UTC', 'closed' => $closed, 'inviteonly' => 'false', - 'private' => $private, 'textlimit' => $textlimit, 'sslserver' => $sslserver, 'ssl' => $ssl, - 'shorturllength' => '30', - 'friendica' => array( - 'FRIENDICA_PLATFORM' => FRIENDICA_PLATFORM, - 'FRIENDICA_VERSION' => FRIENDICA_VERSION, - 'DFRN_PROTOCOL_VERSION' => DFRN_PROTOCOL_VERSION, - 'DB_UPDATE_VERSION' => DB_UPDATE_VERSION - ) - ), - ); + 'logo' => $logo, 'fancy' => 'true', 'language' => 'en', 'email' => $email, 'broughtby' => '', + 'broughtbyurl' => '', 'timezone' => 'UTC', 'closed' => $closed, 'inviteonly' => 'false', + 'private' => $private, 'textlimit' => $textlimit, 'sslserver' => $sslserver, 'ssl' => $ssl, + 'shorturllength' => '30', + 'redmatrix' => array( + 'RED_PLATFORM' => RED_PLATFORM, + 'RED_VERSION' => RED_VERSION, + 'ZOT_REVISION' => ZOT_REVISION, + 'DB_UPDATE_VERSION' => DB_UPDATE_VERSION + ) + )); return api_apply_template('config', $type, array('$config' => $config)); } api_register_func('api/statusnet/config','api_statusnet_config',false); + api_register_func('api/friendica/config','api_statusnet_config',false); + api_register_func('api/red/config','api_statusnet_config',false); function api_statusnet_version(&$a,$type) { @@ -1513,18 +1725,38 @@ api_register_func('api/statusnet/version','api_statusnet_version',false); + function api_friendica_version(&$a,$type) { + + if($type === 'xml') { + header("Content-type: application/xml"); + echo '<?xml version="1.0" encoding="UTF-8"?>' . "\r\n" . '<version>' . RED_VERSION . '</version>' . "\r\n"; + killme(); + } + elseif($type === 'json') { + header("Content-type: application/json"); + echo '"' . RED_VERSION . '"'; + killme(); + } + } + api_register_func('api/friendica/version','api_friendica_version',false); + api_register_func('api/red/version','api_friendica_version',false); + + function api_ff_ids(&$a,$type,$qtype) { - if(! local_user()) + if(! api_user()) return false; + + // For Red, the closest thing we can do to figure out if you're friends is if both of you are sending each other your streams. + // This won't work if either of you send your stream to everybody on the network + if($qtype == 'friends') - $sql_extra = sprintf(" AND ( `rel` = %d OR `rel` = %d ) ", intval(CONTACT_IS_SHARING), intval(CONTACT_IS_FRIEND)); + $sql_extra = sprintf(" AND ( abook_their_perms & %d ) and ( abook_my_perms & %d ) ", intval(PERMS_W_STREAM), intval(PERMS_W_STREAM)); if($qtype == 'followers') - $sql_extra = sprintf(" AND ( `rel` = %d OR `rel` = %d ) ", intval(CONTACT_IS_FOLLOWER), intval(CONTACT_IS_FRIEND)); + $sql_extra = sprintf(" AND ( abook_my_perms & %d ) and not ( abook_their_perms & %d ) ", intval(PERMS_W_STREAM), intval(PERMS_W_STREAM)); - - $r = q("SELECT id FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 $sql_extra", - intval(local_user()) + $r = q("SELECT abook_id FROM abook where abook_flags = 0 and abook_channel = %d $sql_extra", + intval(api_user()) ); if(is_array($r)) { @@ -1532,14 +1764,14 @@ header("Content-type: application/xml"); echo '<?xml version="1.0" encoding="UTF-8"?>' . "\r\n" . '<ids>' . "\r\n"; foreach($r as $rr) - echo '<id>' . $rr['id'] . '</id>' . "\r\n"; + echo '<id>' . $rr['abook_id'] . '</id>' . "\r\n"; echo '</ids>' . "\r\n"; killme(); } elseif($type === 'json') { $ret = array(); header("Content-type: application/json"); - foreach($r as $rr) $ret[] = $rr['id']; + foreach($r as $rr) $ret[] = $rr['abook_id']; echo json_encode($ret); killme(); } @@ -1557,7 +1789,7 @@ function api_direct_messages_new(&$a, $type) { - if (local_user()===false) return false; + if (api_user()===false) return false; if (!x($_POST, "text") || !x($_POST,"screen_name")) return; @@ -1566,17 +1798,17 @@ require_once("include/message.php"); $r = q("SELECT `id` FROM `contact` WHERE `uid`=%d AND `nick`='%s'", - intval(local_user()), + intval(api_user()), dbesc($_POST['screen_name'])); $recipient = api_get_user($a, $r[0]['id']); $replyto = ''; $sub = ''; if (x($_REQUEST,'replyto')) { - $r = q('SELECT `parent_uri`, `title` FROM `mail` WHERE `uid`=%d AND `id`=%d', - intval(local_user()), + $r = q('SELECT `parent_mid`, `title` FROM `mail` WHERE `uid`=%d AND `id`=%d', + intval(api_user()), intval($_REQUEST['replyto'])); - $replyto = $r[0]['parent_uri']; + $replyto = $r[0]['parent_mid']; $sub = $r[0]['title']; } else { @@ -1612,7 +1844,7 @@ api_register_func('api/direct_messages/new','api_direct_messages_new',true); function api_direct_messages_box(&$a, $type, $box) { - if (local_user()===false) return false; + if (api_user()===false) return false; $user_info = api_get_user($a); @@ -1628,7 +1860,7 @@ $sql_extra = "`from-url`='".dbesc( $profile_url )."'"; } elseif ($box=="conversation") { - $sql_extra = "`parent_uri`='".dbesc( $_GET["uri"] ) ."'"; + $sql_extra = "`parent_mid`='".dbesc( $_GET["uri"] ) ."'"; } elseif ($box=="all") { $sql_extra = "true"; @@ -1638,7 +1870,7 @@ } $r = q("SELECT * FROM `mail` WHERE uid=%d AND $sql_extra ORDER BY created DESC LIMIT %d,%d", - intval(local_user()), + intval(api_user()), intval($start), intval($count) ); @@ -1690,9 +1922,13 @@ function api_oauth_request_token(&$a, $type){ try{ $oauth = new FKOAuth1(); - $r = $oauth->fetch_request_token(OAuthRequest::from_request()); + $req = OAuthRequest::from_request(); +logger('Req: ' . var_export($req,true)); + $r = $oauth->fetch_request_token($req); }catch(Exception $e){ - echo "error=". OAuthUtil::urlencode_rfc3986($e->getMessage()); killme(); + logger('oauth_exception: ' . print_r($e->getMessage(),true)); + echo "error=". OAuthUtil::urlencode_rfc3986($e->getMessage()); + killme(); } echo $r; killme(); @@ -1700,7 +1936,8 @@ function api_oauth_access_token(&$a, $type){ try{ $oauth = new FKOAuth1(); - $r = $oauth->fetch_access_token(OAuthRequest::from_request()); + $req = OAuthRequest::from_request(); + $r = $oauth->fetch_access_token($req); }catch(Exception $e){ echo "error=". OAuthUtil::urlencode_rfc3986($e->getMessage()); killme(); } diff --git a/include/apps.php b/include/apps.php new file mode 100644 index 000000000..bd5c50405 --- /dev/null +++ b/include/apps.php @@ -0,0 +1,455 @@ +<?php /** @file */ + +/** + * apps + * + */ + +require_once('include/plugin.php'); + +function get_system_apps() { + + $ret = array(); + $files = glob('app/*.apd'); + if($files) { + foreach($files as $f) { + $x = parse_app_description($f); + if($x) { + $ret[] = $x; + } + } + } + $files = glob('addon/*/*.apd'); + if($files) { + foreach($files as $f) { + $n = basename($f,'.apd'); + if(plugin_is_installed($n)) { + $x = parse_app_description($f); + if($x) { + $ret[] = $x; + } + } + } + } + + return $ret; + +} + +function app_name_compare($a,$b) { + return strcmp($a['name'],$b['name']); +} + +function parse_app_description($f) { + $ret = array(); + + $baseurl = z_root(); + $channel = get_app()->get_channel(); + $address = (($channel) ? $channel['channel_address'] : ''); + + //future expansion + + $observer = get_app()->get_observer(); + + + $lines = @file($f); + if($lines) { + foreach($lines as $x) { + if(preg_match('/^([a-zA-Z].*?):(.*?)$/ism',$x,$matches)) { + $ret[$matches[1]] = trim(str_replace(array('$baseurl','$nick'),array($baseurl,$address),$matches[2])); + } + } + } + + + if(! $ret['photo']) + $ret['photo'] = $baseurl . '/' . get_default_profile_photo(80); + + $ret['type'] = 'system'; + + foreach($ret as $k => $v) { + if(strpos($v,'http') === 0) + $ret[$k] = zid($v); + } + + if(array_key_exists('desc',$ret)) + $ret['desc'] = str_replace(array('\'','"'),array(''','&dquot;'),$ret['desc']); + + if(array_key_exists('target',$ret)) + $ret['target'] = str_replace(array('\'','"'),array(''','&dquot;'),$ret['target']); + + if(array_key_exists('requires',$ret)) { + $require = trim(strtolower($ret['requires'])); + switch($require) { + case 'nologin': + if(local_user()) + unset($ret); + break; + case 'admin': + if(! is_site_admin()) + unset($ret); + break; + case 'local_user': + if(! local_user()) + unset($ret); + break; + case 'observer': + if(! $observer) + unset($ret); + break; + default: + if(! local_user() && feature_enabled(local_user(),$require)) + unset($ret); + break; + + } +// logger('require: ' . print_r($ret,true)); + } + if($ret) { + translate_system_apps($ret); + return $ret; + } + return false; +} + + +function translate_system_apps(&$arr) { + $apps = array( + 'Site Admin' => t('Site Admin'), + 'Bookmarks' => t('Bookmarks'), + 'Address Book' => t('Address Book'), + 'Login' => t('Login'), + 'Channel Select' => t('Channel Select'), + 'Matrix' => t('Matrix'), + 'Settings' => t('Settings'), + 'Files' => t('Files'), + 'Webpages' => t('Webpages'), + 'Channel Home' => t('Channel Home'), + 'Profile' => t('Profile'), + 'Photos' => t('Photos'), + 'Events' => t('Events'), + 'Directory' => t('Directory'), + 'Help' => t('Help'), + 'Mail' => t('Mail'), + 'Mood' => t('Mood'), + 'Poke' => t('Poke'), + 'Chat' => t('Chat'), + 'Search' => t('Search'), + 'Probe' => t('Probe'), + 'Suggest' => t('Suggest') + ); + + if(array_key_exists($arr['name'],$apps)) + $arr['name'] = $apps[$arr['name']]; + +} + + +// papp is a portable app + +function app_render($papp,$mode = 'view') { + + /** + * modes: + * view: normal mode for viewing an app via bbcode from a conversation or page + * provides install/update button if you're logged in locally + * list: normal mode for viewing an app on the app page + * no buttons are shown + * edit: viewing the app page in editing mode provides a delete button + */ + + $installed = false; + + if(! $papp['photo']) + $papp['photo'] = z_root() . '/' . get_default_profile_photo(80); + + if(! $papp) + return; + + $papp['papp'] = papp_encode($papp); + + foreach($papp as $k => $v) { + if(strpos($v,'http') === 0 && $k != 'papp') + $papp[$k] = zid($v); + if($k === 'desc') + $papp['desc'] = str_replace(array('\'','"'),array(''','&dquot;'),$papp['desc']); + + if($k === 'requires') { + $require = trim(strtolower($v)); + switch($require) { + case 'nologin': + if(local_user()) + return ''; + break; + case 'admin': + if(! is_site_admin()) + return ''; + break; + case 'local_user': + if(! local_user()) + return ''; + break; + case 'observer': + $observer = get_app()->get_observer(); + if(! $observer) + return ''; + break; + default: + if(! local_user() && feature_enabled(local_user(),$require)) + return ''; + break; + + } + + } + } + + $hosturl = ''; + + if(local_user()) { + $installed = app_installed(local_user(),$papp); + $hosturl = z_root() . '/'; + } + elseif(remote_user()) { + $observer = get_app()->get_observer(); + if($observer && $observer['xchan_network'] === 'zot') { + // some folks might have xchan_url redirected offsite, use the connurl + $x = parse_url($observer['xchan_connurl']); + if($x) { + $hosturl = $x['scheme'] . '://' . $x['host'] . '/'; + } + } + } + + $install_action = (($installed) ? t('Update') : t('Install')); + + return replace_macros(get_markup_template('app.tpl'),array( + '$app' => $papp, + '$hosturl' => $hosturl, + '$purchase' => (($papp['page'] && (! $installed)) ? t('Purchase') : ''), + '$install' => (($hosturl && $mode == 'view') ? $install_action : ''), + '$edit' => ((local_user() && $installed && $mode == 'edit') ? t('Edit') : ''), + '$delete' => ((local_user() && $installed && $mode == 'edit') ? t('Delete') : '') + )); +} + + +function app_install($uid,$app) { + $app['uid'] = $uid; + if(app_installed($uid,$app)) + $x = app_update($app); + else + $x = app_store($app); + + if($x['success']) + return $x['app_id']; + + return false; +} + +function app_destroy($uid,$app) { + if($uid && $app['guid']) { + $r = q("delete from app where app_id = '%s' and app_channel = %d limit 1", + dbesc($app['guid']), + intval($uid) + ); + } +} + + +function app_installed($uid,$app) { + + $r = q("select id from app where app_id = '%s' and app_version = '%s' and app_channel = %d limit 1", + dbesc((array_key_exists('guid',$app)) ? $app['guid'] : ''), + dbesc((array_key_exists('version',$app)) ? $app['version'] : ''), + intval($uid) + ); + return(($r) ? true : false); + +} + + +function app_list($uid) { + $r = q("select * from app where app_channel = %d order by app_name asc", + intval($uid) + ); + if($r) { + for($x = 0; $x < count($r); $x ++) { + $r[$x]['type'] = 'personal'; + } + } + return($r); +} + + +function app_decode($s) { + $x = base64_decode(str_replace(array('<br />',"\r","\n",' '),array('','','',''),$s)); + return json_decode($x,true); +} + + +function app_store($arr) { + + // logger('app_store: ' . print_r($arr,true)); + + $darray = array(); + $ret = array('success' => false); + + $darray['app_url'] = ((x($arr,'url')) ? $arr['url'] : ''); + $darray['app_channel'] = ((x($arr,'uid')) ? $arr['uid'] : 0); + + if((! $darray['app_url']) || (! $darray['app_channel'])) + return $ret; + + if($arr['photo'] && ! strstr($arr['photo'],z_root())) { + $x = import_profile_photo($arr['photo'],get_observer_hash(),true); + $arr['photo'] = $x[1]; + } + + + $darray['app_id'] = ((x($arr,'guid')) ? $arr['guid'] : random_string(). '.' . get_app()->get_hostname()); + $darray['app_sig'] = ((x($arr,'sig')) ? $arr['sig'] : ''); + $darray['app_author'] = ((x($arr,'author')) ? $arr['author'] : get_observer_hash()); + $darray['app_name'] = ((x($arr,'name')) ? escape_tags($arr['name']) : t('Unknown')); + $darray['app_desc'] = ((x($arr,'desc')) ? escape_tags($arr['desc']) : ''); + $darray['app_photo'] = ((x($arr,'photo')) ? $arr['photo'] : z_root() . '/' . get_default_profile_photo(80)); + $darray['app_version'] = ((x($arr,'version')) ? escape_tags($arr['version']) : ''); + $darray['app_addr'] = ((x($arr,'addr')) ? escape_tags($arr['addr']) : ''); + $darray['app_price'] = ((x($arr,'price')) ? escape_tags($arr['price']) : ''); + $darray['app_page'] = ((x($arr,'page')) ? escape_tags($arr['page']) : ''); + $darray['app_requires'] = ((x($arr,'requires')) ? escape_tags($arr['requires']) : ''); + + $r = q("insert into app ( app_id, app_sig, app_author, app_name, app_desc, app_url, app_photo, app_version, app_channel, app_addr, app_price, app_page, app_requires ) values ( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s' )", + dbesc($darray['app_id']), + dbesc($darray['app_sig']), + dbesc($darray['app_author']), + dbesc($darray['app_name']), + dbesc($darray['app_desc']), + dbesc($darray['app_url']), + dbesc($darray['app_photo']), + dbesc($darray['app_version']), + intval($darray['app_channel']), + dbesc($darray['app_addr']), + dbesc($darray['app_price']), + dbesc($darray['app_page']), + dbesc($darray['app_requires']) + ); + if($r) { + $ret['success'] = true; + $ret['app_id'] = $darray['app_id']; + } + return $ret; +} + + +function app_update($arr) { + + $darray = array(); + $ret = array('success' => false); + + $darray['app_url'] = ((x($arr,'url')) ? $arr['url'] : ''); + $darray['app_channel'] = ((x($arr,'uid')) ? $arr['uid'] : 0); + $darray['app_id'] = ((x($arr,'guid')) ? $arr['guid'] : 0); + + if((! $darray['app_url']) || (! $darray['app_channel']) || (! $darray['app_id'])) + return $ret; + + if($arr['photo'] && ! strstr($arr['photo'],z_root())) { + $x = import_profile_photo($arr['photo'],get_observer_hash(),true); + $arr['photo'] = $x[1]; + } + + $darray['app_sig'] = ((x($arr,'sig')) ? $arr['sig'] : ''); + $darray['app_author'] = ((x($arr,'author')) ? $arr['author'] : get_observer_hash()); + $darray['app_name'] = ((x($arr,'name')) ? escape_tags($arr['name']) : t('Unknown')); + $darray['app_desc'] = ((x($arr,'desc')) ? escape_tags($arr['desc']) : ''); + $darray['app_photo'] = ((x($arr,'photo')) ? $arr['photo'] : z_root() . '/' . get_default_profile_photo(80)); + $darray['app_version'] = ((x($arr,'version')) ? escape_tags($arr['version']) : ''); + $darray['app_addr'] = ((x($arr,'addr')) ? escape_tags($arr['addr']) : ''); + $darray['app_price'] = ((x($arr,'price')) ? escape_tags($arr['price']) : ''); + $darray['app_page'] = ((x($arr,'page')) ? escape_tags($arr['page']) : ''); + $darray['app_requires'] = ((x($arr,'requires')) ? escape_tags($arr['requires']) : ''); + + $r = q("update app set app_sig = '%s', app_author = '%s', app_name = '%s', app_desc = '%s', app_url = '%s', app_photo = '%s', app_version = '%s', app_addr = '%s', app_price = '%s', app_page = '%s', app_requires = '%s' where app_id = '%s' and app_channel = %d limit 1", + dbesc($darray['app_sig']), + dbesc($darray['app_author']), + dbesc($darray['app_name']), + dbesc($darray['app_desc']), + dbesc($darray['app_url']), + dbesc($darray['app_photo']), + dbesc($darray['app_version']), + dbesc($darray['app_addr']), + dbesc($darray['app_price']), + dbesc($darray['app_page']), + dbesc($darray['app_requires']), + dbesc($darray['app_id']), + intval($darray['app_channel']) + ); + if($r) { + $ret['success'] = true; + $ret['app_id'] = $darray['app_id']; + } + + return $ret; + +} + + +function app_encode($app,$embed = false) { + + $ret = array(); + + $ret['type'] = 'personal'; + + if($app['app_id']) + $ret['guid'] = $app['app_id']; + + if($app['app_id']) + $ret['guid'] = $app['app_id']; + + if($app['app_sig']) + $ret['sig'] = $app['app_sig']; + + if($app['app_author']) + $ret['author'] = $app['app_author']; + + if($app['app_name']) + $ret['name'] = $app['app_name']; + + if($app['app_desc']) + $ret['desc'] = $app['app_desc']; + + if($app['app_url']) + $ret['url'] = $app['app_url']; + + if($app['app_photo']) + $ret['photo'] = $app['app_photo']; + + if($app['app_version']) + $ret['version'] = $app['app_version']; + + if($app['app_addr']) + $ret['addr'] = $app['app_addr']; + + if($app['app_price']) + $ret['price'] = $app['app_price']; + + if($app['app_page']) + $ret['page'] = $app['app_page']; + + if($app['app_requires']) + $ret['requires'] = $app['app_requires']; + + if(! $embed) + return $ret; + + $j = json_encode($ret); + return '[app]' . chunk_split(base64_encode($j),72,"\n") . '[/app]'; + +} + + +function papp_encode($papp) { + return chunk_split(base64_encode(json_encode($papp)),72,"\n"); + +}
\ No newline at end of file diff --git a/include/attach.php b/include/attach.php index 6d611cec0..f5eaaa448 100644 --- a/include/attach.php +++ b/include/attach.php @@ -1,6 +1,27 @@ <?php +/** @file + * + * @brief File/attach API with the potential for revision control. + * + * TODO: a filesystem storage abstraction which maintains security (and 'data' contains a system filename + * which is inaccessible from the web). This could get around PHP storage limits and store videos and larger + * items, using fread or OS methods or native code to read/write or chunk it through. + * Also an 'append' option to the storage function might be a useful addition. + */ +require_once('include/permissions.php'); +require_once('include/security.php'); + +/** + * @brief Guess the mimetype from file ending. + * + * This function takes a file name and guess the mimetype from the + * filename extension. + * + * @param $filename a string filename + * @return string The mimetype according to a file ending. + */ function z_mime_content_type($filename) { $mime_types = array( @@ -15,6 +36,7 @@ function z_mime_content_type($filename) { 'xml' => 'application/xml', 'swf' => 'application/x-shockwave-flash', 'flv' => 'video/x-flv', + 'epub' => 'application/epub+zip', // images 'png' => 'image/png', @@ -69,15 +91,802 @@ function z_mime_content_type($filename) { return $mime_types[$ext]; } } -// can't use this because we're just passing a name, e.g. not a file that can be opened -// elseif (function_exists('finfo_open')) { -// $finfo = @finfo_open(FILEINFO_MIME); -// $mimetype = @finfo_file($finfo, $filename); -// @finfo_close($finfo); -// return $mimetype; -// } + + return 'application/octet-stream'; + +} + + +/** + * @brief Count files/attachments. + * + * + * @param $channel_id + * @param $observer + * @param $hash (optional) + * @param $filename (optional) + * @param $filetype (optional) + * @return array + * $ret['success'] boolean + * $ret['results'] amount of found results, or false + * $ret['message'] string with error messages if any + */ +function attach_count_files($channel_id, $observer, $hash = '', $filename = '', $filetype = '') { + + $ret = array('success' => false); + + if(! perm_is_allowed($channel_id,$observer, 'read_storage')) { + $ret['message'] = t('Permission denied.'); + return $ret; + } + + require_once('include/security.php'); + $sql_extra = permissions_sql($channel_id); + + if($hash) + $sql_extra .= protect_sprintf(" and hash = '" . dbesc($hash) . "' "); + + if($filename) + $sql_extra .= protect_sprintf(" and filename like '@" . dbesc($filename) . "@' "); + + if($filetype) + $sql_extra .= protect_sprintf(" and filetype like '@" . dbesc($filetype) . "@' "); + + $r = q("select id from attach where uid = %d $sql_extra", + intval($channel_id) + ); + + $ret['success'] = ((is_array($r)) ? true : false); + $ret['results'] = ((is_array($r)) ? count($r) : false); + return $ret; + +} + +/** + * @brief Returns a list of files/attachments. + * + * @param $channel_id + * @param $observer + * @param $hash (optional) + * @param $filename (optional) + * @param $filetype (optional) + * @param $orderby + * @param $start + * @param $entries + * @return array + * $ret['success'] boolean + * $ret['results'] array with results, or false + * $ret['message'] string with error messages if any + */ +function attach_list_files($channel_id, $observer, $hash = '', $filename = '', $filetype = '', $orderby = 'created desc', $start = 0, $entries = 0) { + + $ret = array('success' => false); + + if(! perm_is_allowed($channel_id,$observer, 'read_storage')) { + $ret['message'] = t('Permission denied.'); + return $ret; + } + + require_once('include/security.php'); + $sql_extra = permissions_sql($channel_id); + + if($hash) + $sql_extra .= protect_sprintf(" and hash = '" . dbesc($hash) . "' "); + + if($filename) + $sql_extra .= protect_sprintf(" and filename like '@" . dbesc($filename) . "@' "); + + if($filetype) + $sql_extra .= protect_sprintf(" and filetype like '@" . dbesc($filetype) . "@' "); + + if($entries) + $limit = " limit " . intval($start) . ", " . intval(entries) . " "; + + // Retrieve all columns except 'data' + + $r = q("select id, aid, uid, hash, filename, filetype, filesize, revision, folder, flags, created, edited, allow_cid, allow_gid, deny_cid, deny_gid from attach where uid = %d $sql_extra $orderby $limit", + intval($channel_id) + ); + + $ret['success'] = ((is_array($r)) ? true : false); + $ret['results'] = ((is_array($r)) ? $r : false); + return $ret; + +} + +/** + * @brief Find an attachment by hash and revision. + * + * Returns the entire attach structure including data. + * + * This could exhaust memory so most useful only when immediately sending the data. + * + * @param $hash + * @param $rev + */ +function attach_by_hash($hash, $rev = 0) { + + $ret = array('success' => false); + + // Check for existence, which will also provide us the owner uid + + $sql_extra = ''; + if($rev == (-1)) + $sql_extra = " order by revision desc "; + elseif($rev) + $sql_extra = " and revision = " . intval($rev) . " "; + + + $r = q("SELECT uid FROM attach WHERE hash = '%s' $sql_extra LIMIT 1", + dbesc($hash) + ); + if(! $r) { + $ret['message'] = t('Item was not found.'); + return $ret; + } + + if(! perm_is_allowed($r[0]['uid'], get_observer_hash(), 'view_storage')) { + $ret['message'] = t('Permission denied.'); + return $ret; + } + + $sql_extra = permissions_sql($r[0]['uid']); + + // Now we'll see if we can access the attachment + + $r = q("SELECT * FROM attach WHERE hash = '%s' and uid = %d $sql_extra LIMIT 1", + dbesc($hash), + intval($r[0]['uid']) + ); + + if(! $r) { + $ret['message'] = t('Permission denied.'); + return $ret; + } + + $ret['success'] = true; + $ret['data'] = $r[0]; + return $ret; + +} + +/** + * @brief Find an attachment by hash and revision. + * + * Returns the entire attach structure excluding data. + * + * @see attach_by_hash() + * @param $hash + * @param $ref + */ +function attach_by_hash_nodata($hash, $rev = 0) { + + $ret = array('success' => false); + + // Check for existence, which will also provide us the owner uid + + $sql_extra = ''; + if($rev == (-1)) + $sql_extra = " order by revision desc "; + elseif($rev) + $sql_extra = " and revision = " . intval($rev) . " "; + + $r = q("SELECT uid FROM attach WHERE hash = '%s' $sql_extra LIMIT 1", + dbesc($hash) + ); + if(! $r) { + $ret['message'] = t('Item was not found.'); + return $ret; + } + + if(! perm_is_allowed($r[0]['uid'],get_observer_hash(),'view_storage')) { + $ret['message'] = t('Permission denied.'); + return $ret; + } + + $sql_extra = permissions_sql($r[0]['uid']); + + // Now we'll see if we can access the attachment + + $r = q("select id, aid, uid, hash, creator, filename, filetype, filesize, revision, folder, flags, created, edited, allow_cid, allow_gid, deny_cid, deny_gid from attach where uid = %d and hash = '%s' $sql_extra limit 1", + intval($r[0]['uid']), + dbesc($hash) + ); + + if(! $r) { + $ret['message'] = t('Permission denied.'); + return $ret; + } + + $ret['success'] = true; + $ret['data'] = $r[0]; + return $ret; + +} + +/** + * @brief + * + * @param $channel channel array of owner + * @param $observer_hash hash of current observer + * @param $options (optional) + * @param $arr (optional) + */ +function attach_store($channel, $observer_hash, $options = '', $arr = null) { + + $ret = array('success' => false); + $channel_id = $channel['channel_id']; + $sql_options = ''; + + if(! perm_is_allowed($channel_id,get_observer_hash(), 'write_storage')) { + $ret['message'] = t('Permission denied.'); + return $ret; + } + + // The 'update' option sets db values without uploading a new attachment + // 'replace' replaces the existing uploaded data + // 'revision' creates a new revision with new upload data + // Default is to upload a new file + + // revise or update must provide $arr['hash'] of the thing to revise/update + + if($options !== 'update') { + if(! x($_FILES,'userfile')) { + $ret['message'] = t('No source file.'); + return $ret; + } + + $src = $_FILES['userfile']['tmp_name']; + $filename = basename($_FILES['userfile']['name']); + $filesize = intval($_FILES['userfile']['size']); + } + + $existing_size = 0; + + if($options === 'replace') { + $x = q("select id, hash, filesize from attach where id = %d and uid = %d limit 1", + intval($replace), + intval($channel_id) + ); + if(! $x) { + $ret['message'] = t('Cannot locate file to replace'); + return $ret; + } + $existing_id = $x[0]['id']; + $existing_size = intval($x[0]['filesize']); + $hash = $x[0]['hash']; + } + + if($options === 'revise' || $options === 'update') { + $sql_options = " order by revision desc "; + if($options === 'update' && $arr && array_key_exists('revision',$arr)) + $sql_options = " and revision = " . intval($arr['revision']) . " "; + + $x = q("select id, aid, uid, filename, filetype, filesize, hash, revision, folder, flags, created, edited, allow_cid, allow_gid, deny_cid, deny_gid from attach where hash = '%s' and uid = %d $sql_options limit 1", + dbesc($arr['hash']), + intval($channel_id) + ); + if(! $x) { + $ret['message'] = t('Cannot locate file to revise/update'); + return $ret; + } + $hash = $x[0]['hash']; + } + + // Check storage limits + if($options !== 'update') { + $maxfilesize = get_config('system','maxfilesize'); + + if(($maxfilesize) && ($filesize > $maxfilesize)) { + $ret['message'] = sprintf( t('File exceeds size limit of %d'), $maxfilesize); + @unlink($src); + return $ret; + } + + $limit = service_class_fetch($channel_id, 'attach_upload_limit'); + + if($limit !== false) { + $r = q("select sum(filesize) as total from attach where aid = %d ", + intval($channel['channel_account_id']) + ); + if(($r) && (($r[0]['total'] + $filesize) > ($limit - $existing_size))) { + $ret['message'] = upgrade_message(true) . sprintf(t("You have reached your limit of %1$.0f Mbytes attachment storage."), $limit / 1024000); + @unlink($src); + return $ret; + } + } + $mimetype = z_mime_content_type($filename); + } + + if(! isset($hash)) + $hash = random_string(); + $created = datetime_convert(); + + if($options === 'replace') { + $r = q("update attach set filename = '%s', filetype = '%s', filesize = %d, data = '%s', edited = '%s' where id = %d and uid = %d limit 1", + dbesc($filename), + dbesc($mimetype), + intval($filesize), + dbesc(@file_get_contents($src)), + dbesc($created), + intval($existing_id), + intval($channel_id) + ); + } + elseif($options === 'revise') { + $r = q("insert into attach ( aid, uid, hash, creator, filename, filetype, filesize, revision, data, created, edited, allow_cid, allow_gid, deny_cid, deny_gid ) + VALUES ( %d, %d, '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", + intval($x[0]['aid']), + intval($channel_id), + dbesc($x[0]['hash']), + dbesc(get_observer_hash()), + dbesc($filename), + dbesc($mimetype), + intval($filesize), + intval($x[0]['revision'] + 1), + dbesc(@file_get_contents($src)), + dbesc($created), + dbesc($created), + dbesc($x[0]['allow_cid']), + dbesc($x[0]['allow_gid']), + dbesc($x[0]['deny_cid']), + dbesc($x[0]['deny_gid']) + ); + } + elseif($options === 'update') { + $r = q("update attach set filename = '%s', filetype = '%s', edited = '%s', + allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s' where id = %d and uid = %d limit 1", + dbesc((array_key_exists('filename',$arr)) ? $arr['filename'] : $x[0]['filename']), + dbesc((array_key_exists('filetype',$arr)) ? $arr['filetype'] : $x[0]['filetype']), + dbesc($created), + dbesc((array_key_exists('allow_cid',$arr)) ? $arr['allow_cid'] : $x[0]['allow_cid']), + dbesc((array_key_exists('allow_gid',$arr)) ? $arr['allow_gid'] : $x[0]['allow_gid']), + dbesc((array_key_exists('deny_cid',$arr)) ? $arr['deny_cid'] : $x[0]['deny_cid']), + dbesc((array_key_exists('deny_gid',$arr)) ? $arr['deny_gid'] : $x[0]['deny_gid']), + intval($x[0]['id']), + intval($x[0]['uid']) + ); + } else { - return 'application/octet-stream'; + $r = q("INSERT INTO attach ( aid, uid, hash, creator, filename, filetype, filesize, revision, data, created, edited, allow_cid, allow_gid,deny_cid, deny_gid ) + VALUES ( %d, %d, '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", + intval($channel['channel_account_id']), + intval($channel_id), + dbesc($hash), + dbesc(get_observer_hash()), + dbesc($filename), + dbesc($mimetype), + intval($filesize), + intval(0), + dbesc(@file_get_contents($src)), + dbesc($created), + dbesc($created), + dbesc(($arr && array_key_exists('allow_cid',$arr)) ? $arr['allow_cid'] : '<' . $channel['channel_hash'] . '>'), + dbesc(($arr && array_key_exists('allow_gid',$arr)) ? $arr['allow_gid'] : ''), + dbesc(($arr && array_key_exists('deny_cid',$arr)) ? $arr['deny_cid'] : ''), + dbesc(($arr && array_key_exists('deny_gid',$arr)) ? $arr['deny_gid'] : '') + ); + } + + if($options !== 'update') + @unlink($src); + + if(! $r) { + $ret['message'] = t('File upload failed. Possible system limit or action terminated.'); + return $ret; + } + + // Caution: This re-uses $sql_options set further above + + $r = q("select id, aid, uid, hash, creator, filename, filetype, filesize, revision, folder, flags, created, edited, allow_cid, allow_gid, deny_cid, deny_gid from attach where uid = %d and hash = '%s' $sql_options limit 1", + intval($channel_id), + dbesc($hash) + ); + + if(! $r) { + $ret['message'] = t('Stored file could not be verified. Upload failed.'); + return $ret; + } + + $ret['success'] = true; + $ret['data'] = $r[0]; + return $ret; +} + +/** + * Read a virtual directory and return contents, checking permissions of all parent components. + * @function z_readdir + * @param integer $channel_id + * @param string $observer_hash hash of current observer + * @param string $pathname + * @param string $parent_hash (optional) + * + * @returns array $ret + * $ret['success'] = boolean true or false + * $ret['message'] = error message if success is false + * $ret['data'] = array of attach DB entries without data component + */ +function z_readdir($channel_id, $observer_hash, $pathname, $parent_hash = '') { + + $ret = array('success' => false); + if(! perm_is_allowed($r[0]['uid'], get_observer_hash(), 'view_storage')) { + $ret['message'] = t('Permission denied.'); + return $ret; + } + + if(strpos($pathname, '/')) { + $paths = explode('/', $pathname); + if(count($paths) > 1) { + $curpath = array_shift($paths); + + $r = q("select hash, id from attach where uid = %d and filename = '%s' and (flags & %d ) " . permissions_sql($channel_id) . " limit 1", + intval($channel_id), + dbesc($curpath), + intval(ATTACH_FLAG_DIR) + ); + if(! $r) { + $ret['message'] = t('Path not available.'); + return $ret; + } + + return z_readdir($channel_id, $observer_hash, implode('/', $paths), $r[0]['hash']); + } + } + else + $paths = array($pathname); + + $r = q("select id, aid, uid, hash, creator, filename, filetype, filesize, revision, folder, flags, created, edited, allow_cid, allow_gid, deny_cid, deny_gid from attach where id = %d and folder = '%s' and filename = '%s' and (flags & %d ) " . permissions_sql($channel_id), + intval($channel_id), + dbesc($parent_hash), + dbesc($paths[0]), + intval(ATTACH_FLAG_DIR) + ); + if(! $r) { + $ret['message'] = t('Path not available.'); + return $ret; + } + $ret['success'] = true; + $ret['data'] = $r; + return $ret; +} + +/** + * @function attach_mkdir($channel,$observer_hash,$arr); + * + * @brief Create directory. + * + * @param $channel channel array of owner + * @param $observer_hash hash of current observer + * @param $arr parameter array to fulfil request + * Required: + * $arr['filename'] + * $arr['folder'] // hash of parent directory, empty string for root directory + * Optional: + * $arr['hash'] // precumputed hash for this node + * $arr['allow_cid'] + * $arr['allow_gid'] + * $arr['deny_cid'] + * $arr['deny_gid'] + */ +function attach_mkdir($channel, $observer_hash, $arr = null) { + + $ret = array('success' => false); + $channel_id = $channel['channel_id']; + $sql_options = ''; + + $basepath = 'store/' . $channel['channel_address']; + + logger('attach_mkdir: basepath: ' . $basepath); + + if(! is_dir($basepath)) + mkdir($basepath,STORAGE_DEFAULT_PERMISSIONS, true); + + if(! perm_is_allowed($channel_id, $observer_hash, 'write_storage')) { + $ret['message'] = t('Permission denied.'); + return $ret; + } + + if(! $arr['filename']) { + $ret['message'] = t('Empty pathname'); + return $ret; + } + + $arr['hash'] = (($arr['hash']) ? $arr['hash'] : random_string()); + + // Check for duplicate name. + // Check both the filename and the hash as we will be making use of both. + + $r = q("select hash from attach where ( filename = '%s' or hash = '%s' ) and folder = '%s' and uid = %d limit 1", + dbesc($arr['filename']), + dbesc($arr['hash']), + dbesc($arr['folder']), + intval($channel['channel_id']) + ); + if($r) { + $ret['message'] = t('duplicate filename or path'); + return $ret; + } + + if($arr['folder']) { + + // Walk the directory tree from parent back to root to make sure the parent is valid and name is unique and we + // have permission to see this path. This implies the root directory itself is public since we won't have permissions + // set on the psuedo-directory. We can however set permissions for anything and everything contained within it. + + $lpath = ''; + $lfile = $arr['folder']; + $sql_options = permissions_sql($channel['channel_id']); + + do { + $r = q("select filename, hash, flags, folder from attach where uid = %d and hash = '%s' and ( flags & %d ) + $sql_options limit 1", + intval($channel['channel_id']), + dbesc($lfile), + intval(ATTACH_FLAG_DIR) + ); + + if(! $r) { + logger('attach_mkdir: hash ' . $lfile . ' not found in ' . $lpath); + $ret['message'] = t('Path not found.'); + return $ret; + } + if($lfile) + $lpath = $r[0]['hash'] . '/' . $lpath; + $lfile = $r[0]['folder']; + } while ( ($r[0]['folder']) && ($r[0]['flags'] & ATTACH_FLAG_DIR)) ; + $path = $basepath . '/' . $lpath; + } + else + $path = $basepath . '/'; + + $path .= $arr['hash']; + + $created = datetime_convert(); + + $r = q("INSERT INTO attach ( aid, uid, hash, creator, filename, filetype, filesize, revision, folder, flags, data, created, edited, allow_cid, allow_gid, deny_cid, deny_gid ) + VALUES ( %d, %d, '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", + intval($channel['channel_account_id']), + intval($channel_id), + dbesc($arr['hash']), + dbesc(get_observer_hash()), + dbesc($arr['filename']), + dbesc('multipart/mixed'), + intval(0), + intval(0), + dbesc($arr['folder']), + intval(ATTACH_FLAG_DIR|ATTACH_FLAG_OS), + dbesc($path), + dbesc($created), + dbesc($created), + dbesc(($arr && array_key_exists('allow_cid',$arr)) ? $arr['allow_cid'] : $channel['channel_allow_cid']), + dbesc(($arr && array_key_exists('allow_gid',$arr)) ? $arr['allow_gid'] : $channel['channel_allow_gid']), + dbesc(($arr && array_key_exists('deny_cid',$arr)) ? $arr['deny_cid'] : $channel['channel_deny_cid']), + dbesc(($arr && array_key_exists('deny_gid',$arr)) ? $arr['deny_gid'] : $channel['channel_deny_gid']) + ); + + if($r) { + if(mkdir($path,STORAGE_DEFAULT_PERMISSIONS, true)) { + $ret['success'] = true; + $ret['data'] = $arr; + } + else { + logger('attach_mkdir: ' . mkdir . ' ' . $path . 'failed.'); + $ret['message'] = t('mkdir failed.'); + } + } + else + $ret['message'] = t('database storage failed.'); + + return $ret; + +} + +/** + * @brief Changes permissions of a file. + * + * @param $channel_id + * @param $resource + * @param $allow_cid + * @param $allow_gid + * @param $deny_cid + * @param $deny_gid + * @param $recurse + */ +function attach_change_permissions($channel_id, $resource, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $recurse = false) { + + $r = q("select hash, flags from attach where hash = '%s' and uid = %d limit 1", + dbesc($resource), + intval($channel_id) + ); + + if(! $r) + return; + + if($r[0]['flags'] & ATTACH_FLAG_DIR) { + if($recurse) { + $r = q("select hash, flags from attach where folder = '%s' and uid = %d", + dbesc($resource), + intval($channel_id) + ); + if($r) { + foreach($r as $rr) { + attach_change_permissions($channel_id, $resource, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $recurse); + } + } + } + } + + $x = q("update attach set allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s' where hash = '%s' and uid = %d limit 1", + dbesc($allow_cid), + dbesc($allow_gid), + dbesc($deny_cid), + dbesc($deny_gid), + dbesc($resource), + intval($channel_id) + ); + + return; +} + +/** + * @brief Delete a file. + * + * @param $channel_id + * @param $resource + */ +function attach_delete($channel_id, $resource) { + + + $c = q("select channel_address from channel where channel_id = %d limit 1", + intval($channel_id) + ); + + $channel_address = (($c) ? $c[0]['channel_address'] : 'notfound'); + + $r = q("select hash, flags from attach where hash = '%s' and uid = %d limit 1", + dbesc($resource), + intval($channel_id) + ); + + + if(! $r) + return; + + if($r[0]['flags'] & ATTACH_FLAG_DIR) { + $x = q("select hash, flags from attach where folder = '%s' and uid = %d", + dbesc($resource), + intval($channel_id) + ); + if($x) { + foreach($x as $xx) { + attach_delete($channel_id, $xx['hash']); + } + } + } + if($r[0]['flags'] & ATTACH_FLAG_OS) { + $y = q("select data from attach where hash = '%s' and uid = %d limit 1", + dbesc($resource), + intval($channel_id) + ); + + if($y) { + $f = 'store/' . $channel_address . '/' . $y[0]['data']; + if(is_dir($f)) + @rmdir($f); + elseif(file_exists($f)) + unlink($f); + } + } + + $z = q("delete from attach where hash = '%s' and uid = %d limit 1", + dbesc($resource), + intval($channel_id) + ); + + return; +} + +/** + * @brief Returns path to file in cloud/. + * + * @param $arr + * @return string with the path the file to cloud/ + */ +function get_cloudpath($arr) { + + $basepath = 'cloud/'; + if($arr['uid']) { + $r = q("select channel_address from channel where channel_id = %d limit 1", + intval($arr['uid']) + ); + if($r) + $basepath .= $r[0]['channel_address'] . '/'; } + + $path = $basepath; + + if($arr['folder']) { + + $lpath = ''; + $lfile = $arr['folder']; + + do { + $r = q("select filename, hash, flags, folder from attach where uid = %d and hash = '%s' and ( flags & %d ) + limit 1", + intval($arr['uid']), + dbesc($lfile), + intval(ATTACH_FLAG_DIR) + ); + + if(! $r) + break; + + if($lfile) + $lpath = $r[0]['filename'] . '/' . $lpath; + $lfile = $r[0]['folder']; + + } while ( ($r[0]['folder']) && ($r[0]['flags'] & ATTACH_FLAG_DIR)) ; + + $path .= $lpath; + } + + $path .= $arr['filename']; + return $path; } +/** + * @brief Returns path to parent folder in cloud/. + * + * @param $arr + * @return string with the folder path + */ +function get_parent_cloudpath($channel_id, $channel_name, $attachHash) { + //Build directory tree and redirect + $parentHash = $attachHash; + do { + $parentHash = find_folder_hash_by_attach_hash($channel_id, $parentHash); + if ($parentHash) { + $parentName = find_filename_by_hash($channel_id, $parentHash); + $parentFullPath = $parentName."/".$parentFullPath; + } + } while ($parentHash); + $parentFullPath = z_root() . "/cloud/" . $channel_name . "/" . $parentFullPath; + return $parentFullPath; +} +function find_folder_hash_by_attach_hash($channel_id, $attachHash) { + $r = q("select * from attach where uid = %d and hash = '%s' limit 1", + intval($channel_id), dbesc($attachHash) + ); + $hash = ""; + if($r) { + foreach($r as $rr) { + $hash = $rr['folder']; + } + } + return $hash; +} +function find_filename_by_hash($channel_id, $attachHash) { + $r = q("select * from attach where uid = %d and hash = '%s' limit 1", + intval($channel_id), dbesc($attachHash) + ); + $filename = ""; + if($r) { + foreach($r as $rr) { + $filename = $rr['filename']; + } + } + return $filename; +} + + +/** + * + * @param $in + * @param $out + */ +function pipe_streams($in, $out) { + $size = 0; + while (!feof($in)) + $size += fwrite($out, fread($in,8192)); + return $size; +} diff --git a/include/auth.php b/include/auth.php index c12432449..e8f13d0fb 100644 --- a/include/auth.php +++ b/include/auth.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ require_once('include/security.php'); @@ -14,13 +14,16 @@ function nuke_session() { unset($_SESSION['administrator']); unset($_SESSION['cid']); unset($_SESSION['theme']); - unset($_SESSION['mobile-theme']); + unset($_SESSION['mobile_theme']); + unset($_SESSION['show_mobile']); unset($_SESSION['page_flags']); unset($_SESSION['submanage']); unset($_SESSION['my_url']); unset($_SESSION['my_address']); unset($_SESSION['addr']); unset($_SESSION['return_url']); + unset($_SESSION['remote_service_class']); + unset($_SESSION['remote_hub']); } /** @@ -31,6 +34,7 @@ function nuke_session() { */ function account_verify_password($email,$pass) { + $r = q("select * from account where account_email = '%s'", dbesc($email) ); @@ -39,51 +43,88 @@ function account_verify_password($email,$pass) { foreach($r as $record) { if(($record['account_flags'] == ACCOUNT_OK) || ($record['account_flags'] == ACCOUNT_UNVERIFIED) && (hash('whirlpool',$record['account_salt'] . $pass) === $record['account_password'])) { + logger('password verified for ' . $email); return $record; } } + $error = 'password failed for ' . $email; + logger($error); + // Also log failed logins to a separate auth log to reduce overhead for server side intrusion prevention + $authlog = get_config('system', 'authlog'); + if ($authlog) + @file_put_contents($authlog, datetime_convert() . ':' . session_id() . ' ' . $error . "\n", FILE_APPEND); + return null; } -// login/logout - - +/** + * Inline - not a function + * look for auth parameters or re-validate an existing session + * also handles logout + */ +if((isset($_SESSION)) && (x($_SESSION,'authenticated')) && ((! (x($_POST,'auth-params'))) || ($_POST['auth-params'] !== 'login'))) { -if((isset($_SESSION)) && (x($_SESSION,'authenticated')) && ((! (x($_POST,'auth-params'))) || ($_POST['auth-params'] !== 'login'))) { + // process a logout request if(((x($_POST,'auth-params')) && ($_POST['auth-params'] === 'logout')) || ($a->module === 'logout')) { // process logout request - call_hooks("logging_out"); + $args = array('channel_id' => local_user()); + call_hooks('logging_out', $args); nuke_session(); info( t('Logged out.') . EOL); goaway(z_root()); } -// if(x($_SESSION,'visitor_id') && (! x($_SESSION,'uid'))) { -// $r = q("SELECT * FROM `contact` WHERE `id` = %d LIMIT 1", -// intval($_SESSION['visitor_id']) -// ); -// if(count($r)) { -// $a->contact = $r[0]; -// } -// } + // re-validate a visitor, optionally invoke "su" if permitted to do so + + if(x($_SESSION,'visitor_id') && (! x($_SESSION,'uid'))) { + // if our authenticated guest is allowed to take control of the admin channel, make it so. + $admins = get_config('system','remote_admin'); + if($admins && is_array($admins) && in_array($_SESSION['visitor_id'],$admins)) { + $x = q("select * from account where account_email = '%s' and account_email != '' and ( account_flags & %d ) limit 1", + dbesc(get_config('system','admin_email')), + intval(ACCOUNT_ROLE_ADMIN) + ); + if($x) { + new_cookie(60*60*24); // one day + $_SESSION['last_login_date'] = datetime_convert(); + unset($_SESSION['visitor_id']); // no longer a visitor + authenticate_success($x[0], true, true); + } + } + + $r = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where xchan_hash = '%s' limit 1", + dbesc($_SESSION['visitor_id']) + ); + if($r) { + get_app()->set_observer($r[0]); + } + else { + unset($_SESSION['visitor_id']); + unset($_SESSION['authenticated']); + } + $a->set_groups(init_groups_visitor($_SESSION['visitor_id'])); + } + + // already logged in user returning if(x($_SESSION,'uid') || x($_SESSION,'account_id')) { - // already logged in user returning + // first check if we're enforcing that sessions can't change IP address - $check = get_config('system','paranoia'); - // extra paranoia - if the IP changed, log them out - if($check && ($_SESSION['addr'] != $_SERVER['REMOTE_ADDR'])) { - logger('Session address changed. Paranoid setting in effect, blocking session. ' - . $_SESSION['addr'] . ' != ' . $_SERVER['REMOTE_ADDR']); - nuke_session(); - goaway(z_root()); + if($_SESSION['addr'] != $_SERVER['REMOTE_ADDR']) { + logger('SECURITY: Session IP address changed: ' . $_SESSION['addr'] . ' != ' . $_SERVER['REMOTE_ADDR']); + if(get_config('system','paranoia')) { + logger('Session address changed. Paranoid setting in effect, blocking session. ' + . $_SESSION['addr'] . ' != ' . $_SERVER['REMOTE_ADDR']); + nuke_session(); + goaway(z_root()); + } } $r = q("select * from account where account_id = %d limit 1", @@ -117,6 +158,8 @@ else { nuke_session(); } + // handle a fresh login request + if((x($_POST,'password')) && strlen($_POST['password'])) $encrypted = hash('whirlpool',trim($_POST['password'])); @@ -155,12 +198,18 @@ else { notice( t('Failed authentication') . EOL); } - logger('authenticate: ' . print_r(get_app()->account,true)); + logger('authenticate: ' . print_r(get_app()->account,true), LOGGER_DEBUG); } if((! $record) || (! count($record))) { - logger('authenticate: failed login attempt: ' . notags(trim($_POST['username'])) . ' from IP ' . $_SERVER['REMOTE_ADDR']); + $error = 'authenticate: failed login attempt: ' . notags(trim($_POST['username'])) . ' from IP ' . $_SERVER['REMOTE_ADDR']; + logger($error); + // Also log failed logins to a separate auth log to reduce overhead for server side intrusion prevention + $authlog = get_config('system', 'authlog'); + if ($authlog) + @file_put_contents($authlog, datetime_convert() . ':' . session_id() . ' ' . $error . "\n", FILE_APPEND); + notice( t('Login failed.') . EOL ); goaway(z_root()); } @@ -193,11 +242,11 @@ else { } -function new_cookie($time) { - $old_sid = session_id(); - session_set_cookie_params("$time"); - session_regenerate_id(false); - - q("UPDATE session SET sid = '%s' WHERE sid = '%s'", dbesc(session_id()), dbesc($old_sid)); -} - +function match_openid($authid) { + $r = q("select * from pconfig where cat = 'system' and k = 'openid' and v = '%s' limit 1", + dbesc($authid) + ); + if($r) + return $r[0]['uid']; + return false; +} diff --git a/include/bb2diaspora.php b/include/bb2diaspora.php index ee070b4fe..f9ecc564f 100644 --- a/include/bb2diaspora.php +++ b/include/bb2diaspora.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ require_once("include/oembed.php"); require_once("include/event.php"); @@ -281,6 +281,14 @@ function bb2diaspora($Text,$preserve_nl = false, $fordiaspora = true) { $Text = preg_replace("/\[img\](.*?)\[\/img\]/", '![' . t('image/photo') . '](' . '$1' . ')', $Text); $Text = preg_replace("/\[img\=(.*?)\](.*?)\[\/img\]/", '![' . t('image/photo') . '](' . '$2' . ')', $Text); + $Text = preg_replace("/\[zrl\]([$URLSearchString]*)\[\/zrl\]/ism", '[$1]($1)', $Text); + $Text = preg_replace("/\#\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '[#$2]($1)', $Text); + $Text = preg_replace("/\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '[$2]($1)', $Text); + + + $Text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/", '![' . t('image/photo') . '](' . '$1' . ')', $Text); + $Text = preg_replace("/\[zmg\=(.*?)\](.*?)\[\/zmg\]/", '![' . t('image/photo') . '](' . '$2' . ')', $Text); + // Perform MAIL Search $Text = preg_replace("(\[mail\]([$MAILSearchString]*)\[/mail\])", '[$1](mailto:$1)', $Text); $Text = preg_replace("/\[mail\=([$MAILSearchString]*)\](.*?)\[\/mail\]/", '[$2](mailto:$1)', $Text); diff --git a/include/bbcode.php b/include/bbcode.php index 881f5777b..60463fc00 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -1,8 +1,8 @@ -<?php +<?php /** @file */ require_once("include/oembed.php"); require_once('include/event.php'); - +require_once('include/zot.php'); function tryoembed($match) { @@ -16,6 +16,40 @@ function tryoembed($match) { return $html; } +function tryzrlaudio($match) { + + $link = $match[1]; + $m = @parse_url($link); + $zrl = false; + if($m['host']) { + $r = q("select hubloc_url from hubloc where hubloc_host = '%s' limit 1", + dbesc($m['host']) + ); + if($r) + $zrl = true; + } + if($zrl) + $link = zid($link); + return '<audio src="' . $link . '" controls="controls" ><a href="' . $link . '">' . $link . '</a></audio>'; +} + +function tryzrlvideo($match) { + $link = $match[1]; + $m = @parse_url($link); + $zrl = false; + if($m['host']) { + $r = q("select hubloc_url from hubloc where hubloc_host = '%s' limit 1", + dbesc($m['host']) + ); + if($r) + $zrl = true; + } + if($zrl) + $link = zid($link); + return '<video controls="controls" src="' . $link . '" style="width:100%; max-width:' . get_app()->videowidth . 'px"><a href="' . $link . '">' . $link . '</a></video>'; + +} + // [noparse][i]italic[/i][/noparse] turns into // [noparse][ i ]italic[ /i ][/noparse], // to hide them from parser. @@ -29,7 +63,7 @@ function bb_spacefy($st) { } // The previously spacefied [noparse][ i ]italic[ /i ][/noparse], -// now turns back and the [noparse] tags are trimed +// now turns back and the [noparse] tags are trimmed // returning [i]italic[/i] function bb_unspacefy_and_trim($st) { @@ -91,7 +125,7 @@ function bb_replace_images($body, $images) { // We're depending on the property of 'foreach' (specified on the PHP website) that // it loops over the array starting from the first element and going sequentially // to the last element - $newbody = str_replace('[$#saved_image' . $cnt . '#$]', '<img src="' . $image .'" alt="' . t('Image/photo') . '" />', $newbody); + $newbody = str_replace('[$#saved_image' . $cnt . '#$]', '<img class="zrl" src="' . $image .'" alt="' . t('Image/photo') . '" />', $newbody); $cnt++; } @@ -100,31 +134,241 @@ function bb_replace_images($body, $images) { - // BBcode 2 HTML was written by WAY2WEB.net - // extended to work with Mistpark/Friendica - Mike Macgirvin +function bb_parse_crypt($match) { -function bbcode($Text,$preserve_nl = false, $tryoembed = true) { + $attributes = $match[1]; + + $algorithm = ""; + + preg_match("/alg='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $algorithm = $matches[1]; + + preg_match("/alg=\"\;(.*?)\"\;/ism", $attributes, $matches); + if ($matches[1] != "") + $algorithm = $matches[1]; + + $hint = ""; + + + preg_match("/hint='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $hint = $matches[1]; + preg_match("/hint=\"\;(.*?)\"\;/ism", $attributes, $matches); + if ($matches[1] != "") + $hint = $matches[1]; + + $x = random_string(); + + $Text = '<br/><div id="' . $x . '"><img src="' . z_root() . '/images/lock_icon.gif" onclick="red_decrypt(\'' . $algorithm . '\',\'' . $hint . '\',\'' . $match[2] . '\',\'#' . $x . '\');" alt="' . t('Encrypted content') . '" title="' . t('Encrypted content') . '" /></div><br />'; + + return $Text; + +} + +function bb_parse_app($match) { + require_once('include/apps.php'); + + $app = app_decode($match[1]); + if($app) + return app_render($app); + +} + +function bb_qr($match) { + return '<img class="zrl" src="' . z_root() . '/photo/qr?f=&qr=' . urlencode($match[1]) . '" alt="' . t('QR code') . '" title="' . htmlspecialchars($match[1],ENT_QUOTES,'UTF-8') . '" />'; +} + + +function bb_ShareAttributes($match) { + + $attributes = $match[1]; + + $author = ""; + preg_match("/author='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $author = urldecode($matches[1]); + + $link = ""; + preg_match("/link='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $link = $matches[1]; + + $avatar = ""; + preg_match("/avatar='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $avatar = $matches[1]; + + $profile = ""; + preg_match("/profile='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $profile = $matches[1]; + + $posted = ""; + preg_match("/posted='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $posted = $matches[1]; + + $message_id = ""; + preg_match("/message_id='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $message_id = $matches[1]; + + + // FIXME - this should really be a wall-item-ago so it will get updated on the client + $reldate = (($posted) ? relative_date($posted) : ''); + + $headline = '<div class="shared_container"> <div class="shared_header">'; + + if ($avatar != "") + $headline .= '<img src="' . $avatar . '" alt="' . $author . '" height="32" width="32" />'; + + // Bob Smith wrote the following post 2 hours ago + + $fmt = sprintf( t('%1$s wrote the following %2$s %3$s'), + '<a href="' . zid($profile) . '" >' . $author . '</a>', + '<a href="' . zid($link) . '" >' . t('post') . '</a>', + $reldate + ); + + $headline .= '<span>' . $fmt . '</span></div>'; + + $text = $headline . '<div class="reshared-content">' . trim($match[2]) . '</div></div>'; + + return($text); +} +function bb_location($match) { + // not yet implemented +} + +function bbiframe($match) { $a = get_app(); - // Extract the private images which use data url's since preg has issues with - // large data sizes. Stash them away while we do bbcode conversion, and then put them back - // in after we've done all the regex matching. We cannot use any preg functions to do this. - $extracted = bb_extract_images($Text); - $Text = $extracted['body']; - $saved_image = $extracted['images']; + $sandbox = ((strpos($match[1],get_app()->get_hostname())) ? ' sandbox="allow-scripts" ' : ''); + + return '<iframe ' . $sandbox . ' src="' . $match[1] . '" width="' . $a->videowidth . '" height="' . $a->videoheight . '"><a href="' . $match[1] . '">' . $match[1] . '</a></iframe>'; +} + +function bb_ShareAttributesSimple($match) { + + $attributes = $match[1]; + + $author = ""; + preg_match("/author='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $author = html_entity_decode($matches[1],ENT_QUOTES,'UTF-8'); + + preg_match('/author="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $author = $matches[1]; + + $profile = ""; + preg_match("/profile='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $profile = $matches[1]; + + preg_match('/profile="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $profile = $matches[1]; + + $text = "<br />".html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8').' <a href="'.$profile.'">'.$author."</a>: div class=\"reshared-content\">" .$match[2]."</div>"; + + return($text); +} + +function rpost_callback($match) { + if ($match[2]) { + return str_replace($match[0],get_rpost_path(get_app()->get_observer()) . '&title=' . urlencode($match[2]) . '&body=' . urlencode($match[3]),$match[0]); + } else { + return str_replace($match[0],get_rpost_path(get_app()->get_observer()) . '&body=' . urlencode($match[3]),$match[0]); + } +} + +function bb_sanitize_style($input) { + //whitelist property limits (0 = no limitation) + $w = array( // color properties + "color" => 0, + "background-color" => 0, + // box properties + "padding" => array("px"=>100, "%"=>0, "em"=>2, "ex"=>2, "mm"=>0, "cm"=>0, "in"=>0, "pt"=>0, "pc"=>0), + "margin" => array("px"=>100, "%"=>0, "em"=>2, "ex"=>2, "mm"=>0, "cm"=>0, "in"=>0, "pt"=>0, "pc"=>0), + "border" => array("px"=>100, "%"=>0, "em"=>2, "ex"=>2, "mm"=>0, "cm"=>0, "in"=>0, "pt"=>0, "pc"=>0), + "float" => 0, + "clear" => 0, + // text properties + "text-decoration" => 0, + + ); + + $css_string = $input[1]; + $a = explode(';',$css_string); + foreach($a as $parts){ + list($k, $v) = explode(':', $parts); + $css[ trim($k) ] = trim($v); + } + + // sanitize properties + $b = array_merge(array_diff_key($css, $w), array_diff_key($w, $css)); + $css = array_diff_key($css, $b); + + foreach($css as $key => $value) { + if($w[$key] != null) { + foreach($w[$key] as $limit_key => $limit_value) { + //sanitize values + if(strpos($value, $limit_key)) { + $value = preg_replace_callback( + "/(\S.*?)$limit_key/ism", + function($match) use($limit_value, $limit_key) { + if($match[1] > $limit_value) { + return $limit_value . $limit_key; + } else { + return $match[1] . $limit_key; + } + }, + $value + ); + } + } + } + $css_string_san .= $key . ":" . $value ."; "; + } + return "<span style=\"" . $css_string_san . "\">" . $input[2] . "</span>"; +} + + // BBcode 2 HTML was written by WAY2WEB.net + // extended to work with Mistpark/Friendica/Red - Mike Macgirvin + +function bbcode($Text,$preserve_nl = false, $tryoembed = true) { + + $a = get_app(); + // Move all spaces out of the tags + // ....Uhm why? + // This is basically doing a trim() on the stuff in between tags, but it messes up + // carefully crafted bbcode and especially other pre-formatted code. + // Commenting out until we come up with a use case where it's needed. Then let's try and + // special case rather than a heavy-handed approach like this. - // Move all spaces out of the tags - $Text = preg_replace("/\[(\w*)\](\s*)/ism", '$2[$1]', $Text); - $Text = preg_replace("/(\s*)\[\/(\w*)\]/ism", '[/$2]$1', $Text); +// $Text = preg_replace("/\[(\w*)\](\s*)/ism", '$2[$1]', $Text); +// $Text = preg_replace("/(\s*)\[\/(\w*)\]/ism", '[/$2]$1', $Text); // Hide all [noparse] contained bbtags by spacefying them + if (strpos($Text,'[noparse]') !== false) { + $Text = preg_replace_callback("/\[noparse\](.*?)\[\/noparse\]/ism", 'bb_spacefy',$Text); + } + if (strpos($Text,'[nobb]') !== false) { + $Text = preg_replace_callback("/\[nobb\](.*?)\[\/nobb\]/ism", 'bb_spacefy',$Text); + } + if (strpos($Text,'[pre]') !== false) { + $Text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", 'bb_spacefy',$Text); + } - $Text = preg_replace_callback("/\[noparse\](.*?)\[\/noparse\]/ism", 'bb_spacefy',$Text); - $Text = preg_replace_callback("/\[nobb\](.*?)\[\/nobb\]/ism", 'bb_spacefy',$Text); - $Text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", 'bb_spacefy',$Text); +// Not yet implemented - thinking this should display a map or perhaps be a map directive +// if (strpos($Text,'[location]') !== false) { +// $Text = preg_replace_callback("/\[location\](.*?)\[\/location\]/ism", 'bb_location',$Text); +// } @@ -134,16 +378,43 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { $ev = bbtoevent($Text); + // process [observer] tags before we do anything else because we might + // be stripping away stuff that then doesn't need to be worked on anymore + + $observer = $a->get_observer(); + if ((strpos($Text,'[/observer]') !== false) || (strpos($Text,'[/rpost]') !== false)) { + if ($observer) { + $Text = preg_replace("/\[observer\=1\](.*?)\[\/observer\]/ism", '$1', $Text); + $Text = preg_replace("/\[observer\=0\].*?\[\/observer\]/ism", '', $Text); + $Text = preg_replace_callback("/\[rpost(=(.*?))?\](.*?)\[\/rpost\]/ism", 'rpost_callback', $Text); + } else { + $Text = preg_replace("/\[observer\=1\].*?\[\/observer\]/ism", '', $Text); + $Text = preg_replace("/\[observer\=0\](.*?)\[\/observer\]/ism", '$1', $Text); + $Text = preg_replace("/\[rpost(=.*?)?\](.*?)\[\/rpost\]/ism", '', $Text); + } + } + + $channel = $a->get_channel(); + if (strpos($Text,'[/channel]') !== false) { + if ($channel) { + $Text = preg_replace("/\[channel\=1\](.*?)\[\/channel\]/ism", '$1', $Text); + $Text = preg_replace("/\[channel\=0\].*?\[\/channel\]/ism", '', $Text); + } else { + $Text = preg_replace("/\[channel\=1\].*?\[\/channel\]/ism", '', $Text); + $Text = preg_replace("/\[channel\=0\](.*?)\[\/channel\]/ism", '$1', $Text); + } + } + + $Text = str_replace(array('[baseurl]','[sitename]'),array(z_root(),get_config('system','sitename')),$Text); + + // Replace any html brackets with HTML Entities to prevent executing HTML or script // Don't use strip_tags here because it breaks [url] search by replacing & with amp $Text = str_replace("<", "<", $Text); $Text = str_replace(">", ">", $Text); - // This only matters when looking for tags - otherwise has no meaning - - $Text = str_replace(array('[share]','[/share]'), array('',''), $Text); // Convert new line chars to html <br /> tags @@ -159,75 +430,136 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { $Text = str_replace(array("\n","\r"), array('',''),$Text); + $Text = str_replace(array("\t"," "),array(" "," "),$Text); // Set up the parameters for a URL search string $URLSearchString = "^\[\]"; // Set up the parameters for a MAIL search string $MAILSearchString = $URLSearchString; + // replace [observer.baseurl] + if ($observer) { + $obsBaseURL = $observer['xchan_url']; + $obsBaseURL = preg_replace("/\/channel\/.*$/", '', $obsBaseURL); + $Text = str_replace('[observer.baseurl]', $obsBaseURL, $Text); + $Text = str_replace('[observer.url]',$observer['xchan_url'], $Text); + $Text = str_replace('[observer.name]',$observer['xchan_name'], $Text); + $Text = str_replace('[observer.address]',$observer['xchan_addr'], $Text); + $Text = str_replace('[observer.photo]','[zmg]'.$observer['xchan_photo_l'].'[/zmg]', $Text); + } else { + $Text = str_replace('[observer.baseurl]', '', $Text); + $Text = str_replace('[observer.url]','', $Text); + $Text = str_replace('[observer.name]','', $Text); + $Text = str_replace('[observer.address]','', $Text); + $Text = str_replace('[observer.photo]','', $Text); + } // Perform URL Search - $Text = preg_replace("/([^\]\=]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", '$1<a href="$2" >$2</a>', $Text); - - if ($tryoembed) - $Text = preg_replace_callback("/\[bookmark\=([^\]]*)\].*?\[\/bookmark\]/ism",'tryoembed',$Text); + $urlchars = '[a-zA-Z0-9\:\/\-\?\&\;\.\=\@\_\~\#\%\$\!\+\,]'; - $Text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism",'[url=$1]$2[/url]',$Text); + if (strpos($Text,'http') !== false) { + $Text = preg_replace("/([^\]\='".'"'."]|^)(https?\:\/\/$urlchars+)/ism", '$1<a href="$2" >$2</a>', $Text); + } - if ($tryoembed) - $Text = preg_replace_callback("/\[url\]([$URLSearchString]*)\[\/url\]/ism",'tryoembed',$Text); + if (strpos($Text,'[/qr]') !== false) { + $Text = preg_replace_callback("/\[qr\](.*?)\[\/qr\]/ism","bb_qr",$Text); + } - $Text = preg_replace("/\[url\]([$URLSearchString]*)\[\/url\]/ism", '<a href="$1" >$1</a>', $Text); - $Text = preg_replace("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<a href="$1" >$2</a>', $Text); - //$Text = preg_replace("/\[url\=([$URLSearchString]*)\]([$URLSearchString]*)\[\/url\]/ism", '<a href="$1" target="_blank">$2</a>', $Text); + if (strpos($Text,'[/share]') !== false) { + $Text = preg_replace_callback("/\[share(.*?)\](.*?)\[\/share\]/ism","bb_ShareAttributes",$Text); + } + if($tryoembed) { + if (strpos($Text,'[/url]') !== false) { + $Text = preg_replace_callback("/\[url\]([$URLSearchString]*)\[\/url\]/ism",'tryoembed',$Text); + } + } + if (strpos($Text,'[/url]') !== false) { + $Text = preg_replace("/\#\^\[url\]([$URLSearchString]*)\[\/url\]/ism", '<span class="bookmark-identifier">#^</span><a class="bookmark" href="$1" >$1</a>', $Text); + $Text = preg_replace("/\#\^\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<span class="bookmark-identifier">#^</span><a class="bookmark" href="$1" >$2</a>', $Text); + $Text = preg_replace("/\[url\]([$URLSearchString]*)\[\/url\]/ism", '<a href="$1" >$1</a>', $Text); + $Text = preg_replace("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<a href="$1" >$2</a>', $Text); + } + if (strpos($Text,'[/zrl]') !== false) { + $Text = preg_replace("/\#\^\[zrl\]([$URLSearchString]*)\[\/zrl\]/ism", '<span class="bookmark-identifier">#^</span><a class="zrl bookmark" href="$1" >$1</a>', $Text); + $Text = preg_replace("/\#\^\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<span class="bookmark-identifier">#^</span><a class="zrl bookmark" href="$1" >$2</a>', $Text); + $Text = preg_replace("/\[zrl\]([$URLSearchString]*)\[\/zrl\]/ism", '<a class="zrl" href="$1" >$1</a>', $Text); + $Text = preg_replace("/\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<a class="zrl" href="$1" >$2</a>', $Text); + } // Perform MAIL Search - $Text = preg_replace("/\[mail\]([$MAILSearchString]*)\[\/mail\]/", '<a href="mailto:$1">$1</a>', $Text); - $Text = preg_replace("/\[mail\=([$MAILSearchString]*)\](.*?)\[\/mail\]/", '<a href="mailto:$1">$2</a>', $Text); - + if (strpos($Text,'[/mail]') !== false) { + $Text = preg_replace("/\[mail\]([$MAILSearchString]*)\[\/mail\]/", '<a href="mailto:$1">$1</a>', $Text); + $Text = preg_replace("/\[mail\=([$MAILSearchString]*)\](.*?)\[\/mail\]/", '<a href="mailto:$1">$2</a>', $Text); + } // Check for bold text - $Text = preg_replace("(\[b\](.*?)\[\/b\])ism",'<strong>$1</strong>',$Text); - + if (strpos($Text,'[b]') !== false) { + $Text = preg_replace("(\[b\](.*?)\[\/b\])ism",'<strong>$1</strong>',$Text); + } // Check for Italics text - $Text = preg_replace("(\[i\](.*?)\[\/i\])ism",'<em>$1</em>',$Text); - + if (strpos($Text,'[i]') !== false) { + $Text = preg_replace("(\[i\](.*?)\[\/i\])ism",'<em>$1</em>',$Text); + } // Check for Underline text - $Text = preg_replace("(\[u\](.*?)\[\/u\])ism",'<u>$1</u>',$Text); - + if (strpos($Text,'[u]') !== false) { + $Text = preg_replace("(\[u\](.*?)\[\/u\])ism",'<u>$1</u>',$Text); + } // Check for strike-through text - $Text = preg_replace("(\[s\](.*?)\[\/s\])ism",'<strike>$1</strike>',$Text); - + if (strpos($Text,'[s]') !== false) { + $Text = preg_replace("(\[s\](.*?)\[\/s\])ism",'<strike>$1</strike>',$Text); + } // Check for over-line text - $Text = preg_replace("(\[o\](.*?)\[\/o\])ism",'<span class="overline">$1</span>',$Text); - + if (strpos($Text,'[o]') !== false) { + $Text = preg_replace("(\[o\](.*?)\[\/o\])ism",'<span class="overline">$1</span>',$Text); + } // Check for colored text - $Text = preg_replace("(\[color=(.*?)\](.*?)\[\/color\])ism","<span style=\"color: $1;\">$2</span>",$Text); - + if (strpos($Text,'[/color]') !== false) { + $Text = preg_replace("(\[color=(.*?)\](.*?)\[\/color\])ism","<span style=\"color: $1;\">$2</span>",$Text); + } // Check for sized text - // [size=50] --> font-size: 50px (with the unit). - $Text = preg_replace("(\[size=(\d*?)\](.*?)\[\/size\])ism","<span style=\"font-size: $1px;\">$2</span>",$Text); - $Text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism","<span style=\"font-size: $1;\">$2</span>",$Text); - + // [size=50] --> font-size: 50px (with the unit). + if (strpos($Text,'[/size]') !== false) { + $Text = preg_replace("(\[size=(\d*?)\](.*?)\[\/size\])ism","<span style=\"font-size: $1px;\">$2</span>",$Text); + $Text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism","<span style=\"font-size: $1;\">$2</span>",$Text); + } + // Check for h1 + if (strpos($Text,'[h1]') !== false) { + $Text = preg_replace("(\[h1\](.*?)\[\/h1\])ism",'<h1>$1</h1>',$Text); + } + // Check for h2 + if (strpos($Text,'[h2]') !== false) { + $Text = preg_replace("(\[h2\](.*?)\[\/h2\])ism",'<h2>$1</h2>',$Text); + } + // Check for h3 + if (strpos($Text,'[h3]') !== false) { + $Text = preg_replace("(\[h3\](.*?)\[\/h3\])ism",'<h3>$1</h3>',$Text); + } + // Check for h4 + if (strpos($Text,'[h4]') !== false) { + $Text = preg_replace("(\[h4\](.*?)\[\/h4\])ism",'<h4>$1</h4>',$Text); + } + // Check for h5 + if (strpos($Text,'[h5]') !== false) { + $Text = preg_replace("(\[h5\](.*?)\[\/h5\])ism",'<h5>$1</h5>',$Text); + } + // Check for h6 + if (strpos($Text,'[h6]') !== false) { + $Text = preg_replace("(\[h6\](.*?)\[\/h6\])ism",'<h6>$1</h6>',$Text); + } // Check for centered text + if (strpos($Text,'[/center]') !== false) { $Text = preg_replace("(\[center\](.*?)\[\/center\])ism","<div style=\"text-align:center;\">$1</div>",$Text); - + } // Check for list text $Text = str_replace("[*]", "<li>", $Text); - // Check for style sheet commands - $Text = preg_replace("(\[style=(.*?)\](.*?)\[\/style\])ism","<span style=\"$1;\">$2</span>",$Text); - - // Check for CSS classes - $Text = preg_replace("(\[class=(.*?)\](.*?)\[\/class\])ism","<span class=\"$1\">$2</span>",$Text); - // handle nested lists $endlessloop = 0; while ((((strpos($Text, "[/list]") !== false) && (strpos($Text, "[list") !== false)) || - ((strpos($Text, "[/ol]") !== false) && (strpos($Text, "[ol]") !== false)) || - ((strpos($Text, "[/ul]") !== false) && (strpos($Text, "[ul]") !== false)) || - ((strpos($Text, "[/li]") !== false) && (strpos($Text, "[li]") !== false))) && (++$endlessloop < 20)) { + ((strpos($Text, "[/ol]") !== false) && (strpos($Text, "[ol]") !== false)) || + ((strpos($Text, "[/ul]") !== false) && (strpos($Text, "[ul]") !== false)) || + ((strpos($Text, "[/li]") !== false) && (strpos($Text, "[li]") !== false))) && (++$endlessloop < 20)) { $Text = preg_replace("/\[list\](.*?)\[\/list\]/ism", '<ul class="listbullet" style="list-style-type: circle;">$1</ul>' ,$Text); $Text = preg_replace("/\[list=\](.*?)\[\/list\]/ism", '<ul class="listnone" style="list-style-type: none;">$1</ul>' ,$Text); $Text = preg_replace("/\[list=1\](.*?)\[\/list\]/ism", '<ul class="listdecimal" style="list-style-type: decimal;">$1</ul>' ,$Text); @@ -239,15 +571,20 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { $Text = preg_replace("/\[ol\](.*?)\[\/ol\]/ism", '<ul class="listdecimal" style="list-style-type: decimal;">$1</ul>' ,$Text); $Text = preg_replace("/\[li\](.*?)\[\/li\]/ism", '<li>$1</li>' ,$Text); } - - $Text = preg_replace("/\[th\](.*?)\[\/th\]/sm", '<th>$1</th>' ,$Text); - $Text = preg_replace("/\[td\](.*?)\[\/td\]/sm", '<td>$1</td>' ,$Text); - $Text = preg_replace("/\[tr\](.*?)\[\/tr\]/sm", '<tr>$1</tr>' ,$Text); - $Text = preg_replace("/\[table\](.*?)\[\/table\]/sm", '<table>$1</table>' ,$Text); - - $Text = preg_replace("/\[table border=1\](.*?)\[\/table\]/sm", '<table border="1" >$1</table>' ,$Text); - $Text = preg_replace("/\[table border=0\](.*?)\[\/table\]/sm", '<table border="0" >$1</table>' ,$Text); - + if (strpos($Text,'[th]') !== false) { + $Text = preg_replace("/\[th\](.*?)\[\/th\]/sm", '<th>$1</th>' ,$Text); + } + if (strpos($Text,'[td]') !== false) { + $Text = preg_replace("/\[td\](.*?)\[\/td\]/sm", '<td>$1</td>' ,$Text); + } + if (strpos($Text,'[tr]') !== false) { + $Text = preg_replace("/\[tr\](.*?)\[\/tr\]/sm", '<tr>$1</tr>' ,$Text); + } + if (strpos($Text,'[/table]') !== false) { + $Text = preg_replace("/\[table\](.*?)\[\/table\]/sm", '<table>$1</table>' ,$Text); + $Text = preg_replace("/\[table border=1\](.*?)\[\/table\]/sm", '<table border="1" >$1</table>' ,$Text); + $Text = preg_replace("/\[table border=0\](.*?)\[\/table\]/sm", '<table border="0" >$1</table>' ,$Text); + } $Text = str_replace('[hr]','<hr />', $Text); // This is actually executed in prepare_body() @@ -255,14 +592,16 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { $Text = str_replace('[nosmile]','',$Text); // Check for font change text - $Text = preg_replace("/\[font=(.*?)\](.*?)\[\/font\]/sm","<span style=\"font-family: $1;\">$2</span>",$Text); - + if (strpos($Text,'[/font]') !== false) { + $Text = preg_replace("/\[font=(.*?)\](.*?)\[\/font\]/sm","<span style=\"font-family: $1;\">$2</span>",$Text); + } // Declare the format for [code] layout $CodeLayout = '<code>$1</code>'; // Check for [code] text - $Text = preg_replace("/\[code\](.*?)\[\/code\]/ism","$CodeLayout", $Text); - + if (strpos($Text,'[code]') !== false) { + $Text = preg_replace("/\[code\](.*?)\[\/code\]/ism","$CodeLayout", $Text); + } // Declare the format for [spoiler] layout $SpoilerLayout = '<blockquote class="spoiler">$1</blockquote>'; @@ -280,8 +619,8 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { $endlessloop = 0; while ((strpos($Text, "[/spoiler]")!== false) and (strpos($Text, "[spoiler=") !== false) and (++$endlessloop < 20)) $Text = preg_replace("/\[spoiler=[\"\']*(.*?)[\"\']*\](.*?)\[\/spoiler\]/ism", - "<br /><strong class=".'"spoiler"'.">" . $t_wrote . "</strong><blockquote class=".'"spoiler"'.">$2</blockquote>", - $Text); + "<br /><strong class=".'"spoiler"'.">" . $t_wrote . "</strong><blockquote class=".'"spoiler"'.">$2</blockquote>", + $Text); // Declare the format for [quote] layout $QuoteLayout = '<blockquote>$1</blockquote>'; @@ -300,73 +639,141 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { $endlessloop = 0; while ((strpos($Text, "[/quote]")!== false) and (strpos($Text, "[quote=") !== false) and (++$endlessloop < 20)) $Text = preg_replace("/\[quote=[\"\']*(.*?)[\"\']*\](.*?)\[\/quote\]/ism", - "<br /><strong class=".'"author"'.">" . $t_wrote . "</strong><blockquote>$2</blockquote>", - $Text); - - // [img=widthxheight]image source[/img] - //$Text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '<img src="$3" style="height: $2px; width: $1px;" >', $Text); - $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '<img src="$3" style="width: $1px;" >', $Text); + "<br /><strong class=".'"author"'.">" . $t_wrote . "</strong><blockquote>$2</blockquote>", + $Text); // Images // [img]pathtoimage[/img] - $Text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '<img src="$1" alt="' . t('Image/photo') . '" />', $Text); + if (strpos($Text,'[/img]') !== false) { + $Text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '<img style="max-width=100%;" src="$1" alt="' . t('Image/photo') . '" />', $Text); + } + if (strpos($Text,'[/zmg]') !== false) { + $Text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '<img class="zrl" style="max-width=100%;" src="$1" alt="' . t('Image/photo') . '" />', $Text); + } + // [img float={left, right}]pathtoimage[/img] + if (strpos($Text,'[/img]') !== false) { + $Text = preg_replace("/\[img float=left\](.*?)\[\/img\]/ism", '<img style="max-width=100%;" src="$1" style="float: left;" alt="' . t('Image/photo') . '" />', $Text); + } + if (strpos($Text,'[/img]') !== false) { + $Text = preg_replace("/\[img float=right\](.*?)\[\/img\]/ism", '<img style="max-width=100%;" src="$1" style="float: right;" alt="' . t('Image/photo') . '" />', $Text); + } + if (strpos($Text,'[/zmg]') !== false) { + $Text = preg_replace("/\[zmg float=left\](.*?)\[\/zmg\]/ism", '<img style="max-width=100%;" class="zrl" src="$1" style="float: left;" alt="' . t('Image/photo') . '" />', $Text); + } + if (strpos($Text,'[/zmg]') !== false) { + $Text = preg_replace("/\[zmg float=right\](.*?)\[\/zmg\]/ism", '<img style="max-width=100%;" class="zrl" src="$1" style="float: right;" alt="' . t('Image/photo') . '" />', $Text); + } - $Text = preg_replace("/\[crypt\](.*?)\[\/crypt\]/ism",'<br/><img src="' .$a->get_baseurl() . '/images/lock_icon.gif" alt="' . t('Encrypted content') . '" title="' . t('Encrypted content') . '" /><br />', $Text); - $Text = preg_replace("/\[crypt=(.*?)\](.*?)\[\/crypt\]/ism",'<br/><img src="' .$a->get_baseurl() . '/images/lock_icon.gif" alt="' . t('Encrypted content') . '" title="' . '$1' . ' ' . t('Encrypted content') . '" /><br />', $Text); + // [img=widthxheight]pathtoimage[/img] + if (strpos($Text,'[/img]') !== false) { + $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '<img src="$3" style="width: 100%; max-width: $1px;" alt="' . t('Image/photo') . '" >', $Text); + } + if (strpos($Text,'[/zmg]') !== false) { + $Text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*)\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: 100%; max-width: $1px;" alt="' . t('Image/photo') . '" >', $Text); + } - // Try to Oembed - if ($tryoembed) { - $Text = preg_replace("/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4))\[\/video\]/ism", '<video src="$1" controls="controls" width="' . $a->videowidth . '" height="' . $a->videoheight . '"><a href="$1">$1</a></video>', $Text); - $Text = preg_replace("/\[audio\](.*?\.(ogg|ogv|oga|ogm|webm|mp4|mp3))\[\/audio\]/ism", '<audio src="$1" controls="controls"><a href="$1">$1</a></audio>', $Text); + // [img=widthxheight float={left, right}]pathtoimage[/img] + if (strpos($Text,'[/img]') !== false) { + $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*) float=left\](.*?)\[\/img\]/ism", '<img src="$3" style="width: 100%; max-width: $1px; float: left;" alt="' . t('Image/photo') . '" >', $Text); + } + if (strpos($Text,'[/img]') !== false) { + $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*) float=right\](.*?)\[\/img\]/ism", '<img src="$3" style="width: 100%; max-width: $1px; float: right;" alt="' . t('Image/photo') . '" >', $Text); + } + if (strpos($Text,'[/zmg]') !== false) { + $Text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*) float=left\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: 100%; max-width: $1px; float: left;" alt="' . t('Image/photo') . '" >', $Text); + } + if (strpos($Text,'[/zmg]') !== false) { + $Text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*) float=right\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: 100%; max-width: $1px; float: right;" alt="' . t('Image/photo') . '" >', $Text); + } - $Text = preg_replace_callback("/\[video\](.*?)\[\/video\]/ism", 'tryoembed', $Text); - $Text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", 'tryoembed', $Text); - } else { - $Text = preg_replace("/\[video\](.*?)\[\/video\]/", '$1', $Text); - $Text = preg_replace("/\[audio\](.*?)\[\/audio\]/", '$1', $Text); + // style (sanitized) + if (strpos($Text,'[/style]') !== false) { + $Text = preg_replace_callback("(\[style=(.*?)\](.*?)\[\/style\])ism", "bb_sanitize_style", $Text); } - // html5 video and audio + // crypt + if (strpos($Text,'[/crypt]') !== false) { + $x = random_string(); + $Text = preg_replace("/\[crypt\](.*?)\[\/crypt\]/ism",'<br/><div id="' . $x . '"><img src="' .$a->get_baseurl() . '/images/lock_icon.gif" onclick="red_decrypt(\'rot13\',\'\',\'$1\',\'#' . $x . '\');" alt="' . t('Encrypted content') . '" title="' . t('Encrypted content') . '" /><br /></div>', $Text); + $Text = preg_replace_callback("/\[crypt (.*?)\](.*?)\[\/crypt\]/ism", 'bb_parse_crypt', $Text); + } + if(strpos($Text,'[/app]') !== false) { + $Text = preg_replace_callback("/\[app\](.*?)\[\/app\]/ism",'bb_parse_app', $Text); + } - if ($tryoembed) - $Text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '<iframe src="$1" width="' . $a->videowidth . '" height="' . $a->videoheight . '"><a href="$1">$1</a></iframe>', $Text); - else - $Text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '<a href="$1">$1</a>', $Text); - // Youtube extensions - if ($tryoembed) { - $Text = preg_replace_callback("/\[youtube\](https?:\/\/www.youtube.com\/watch\?v\=.*?)\[\/youtube\]/ism", 'tryoembed', $Text); - $Text = preg_replace_callback("/\[youtube\](www.youtube.com\/watch\?v\=.*?)\[\/youtube\]/ism", 'tryoembed', $Text); - $Text = preg_replace_callback("/\[youtube\](https?:\/\/youtu.be\/.*?)\[\/youtube\]/ism",'tryoembed',$Text); + // html5 video and audio + if (strpos($Text,'[/video]') !== false) { + $Text = preg_replace_callback("/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4))\[\/video\]/ism", 'tryzrlvideo', $Text); + } + if (strpos($Text,'[/audio]') !== false) { + $Text = preg_replace_callback("/\[audio\](.*?\.(ogg|ogv|oga|ogm|webm|mp4|mp3))\[\/audio\]/ism", 'tryzrlaudio', $Text); } - $Text = preg_replace("/\[youtube\]https?:\/\/www.youtube.com\/watch\?v\=(.*?)\[\/youtube\]/ism",'[youtube]$1[/youtube]',$Text); - $Text = preg_replace("/\[youtube\]https?:\/\/www.youtube.com\/embed\/(.*?)\[\/youtube\]/ism",'[youtube]$1[/youtube]',$Text); - $Text = preg_replace("/\[youtube\]https?:\/\/youtu.be\/(.*?)\[\/youtube\]/ism",'[youtube]$1[/youtube]',$Text); - - if ($tryoembed) - $Text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '<iframe width="' . $a->videowidth . '" height="' . $a->videoheight . '" src="http://www.youtube.com/embed/$1" frameborder="0" ></iframe>', $Text); - else - $Text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", "http://www.youtube.com/watch?v=$1", $Text); + // Try to Oembed + if ($tryoembed) { + if (strpos($Text,'[/video]') !== false) { + $Text = preg_replace_callback("/\[video\](.*?)\[\/video\]/ism", 'tryoembed', $Text); + } + if (strpos($Text,'[/audio]') !== false) { + $Text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", 'tryoembed', $Text); + } + } - if ($tryoembed) { - $Text = preg_replace_callback("/\[vimeo\](https?:\/\/player.vimeo.com\/video\/[0-9]+).*?\[\/vimeo\]/ism",'tryoembed',$Text); - $Text = preg_replace_callback("/\[vimeo\](https?:\/\/vimeo.com\/[0-9]+).*?\[\/vimeo\]/ism",'tryoembed',$Text); + // if video couldn't be embedded, link to it instead. + if (strpos($Text,'[/video]') !== false) { + $Text = preg_replace("/\[video\](.*?)\[\/video\]/", '<a href="$1">$1</a>', $Text); + } + if (strpos($Text,'[/audio]') !== false) { + $Text = preg_replace("/\[audio\](.*?)\[\/audio\]/", '<a href="$1">$1</a>', $Text); } - $Text = preg_replace("/\[vimeo\]https?:\/\/player.vimeo.com\/video\/([0-9]+)(.*?)\[\/vimeo\]/ism",'[vimeo]$1[/vimeo]',$Text); - $Text = preg_replace("/\[vimeo\]https?:\/\/vimeo.com\/([0-9]+)(.*?)\[\/vimeo\]/ism",'[vimeo]$1[/vimeo]',$Text); - if ($tryoembed) - $Text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '<iframe width="' . $a->videowidth . '" height="' . $a->videoheight . '" src="http://player.vimeo.com/video/$1" frameborder="0" ></iframe>', $Text); - else - $Text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", "http://vimeo.com/$1", $Text); -// $Text = preg_replace("/\[youtube\](.*?)\[\/youtube\]/", '<object width="425" height="350" type="application/x-shockwave-flash" data="http://www.youtube.com/v/$1" ><param name="movie" value="http://www.youtube.com/v/$1"></param><!--[if IE]><embed src="http://www.youtube.com/v/$1" type="application/x-shockwave-flash" width="425" height="350" /><![endif]--></object>', $Text); + if ($tryoembed){ + if (strpos($Text,'[/iframe]') !== false) { + $Text = preg_replace_callback("/\[iframe\](.*?)\[\/iframe\]/ism", 'bbiframe', $Text); + } + } + else { + if (strpos($Text,'[/iframe]') !== false) { + $Text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '<a href="$1">$1</a>', $Text); + } + } + // Youtube extensions + if (strpos($Text,'[youtube]') !== false) { + if ($tryoembed) { + $Text = preg_replace_callback("/\[youtube\](https?:\/\/www.youtube.com\/watch\?v\=.*?)\[\/youtube\]/ism", 'tryoembed', $Text); + $Text = preg_replace_callback("/\[youtube\](www.youtube.com\/watch\?v\=.*?)\[\/youtube\]/ism", 'tryoembed', $Text); + $Text = preg_replace_callback("/\[youtube\](https?:\/\/youtu.be\/.*?)\[\/youtube\]/ism",'tryoembed',$Text); + } + $Text = preg_replace("/\[youtube\]https?:\/\/www.youtube.com\/watch\?v\=(.*?)\[\/youtube\]/ism",'[youtube]$1[/youtube]',$Text); + $Text = preg_replace("/\[youtube\]https?:\/\/www.youtube.com\/embed\/(.*?)\[\/youtube\]/ism",'[youtube]$1[/youtube]',$Text); + $Text = preg_replace("/\[youtube\]https?:\/\/youtu.be\/(.*?)\[\/youtube\]/ism",'[youtube]$1[/youtube]',$Text); + + if ($tryoembed) + $Text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '<iframe width="' . $a->videowidth . '" height="' . $a->videoheight . '" src="http://www.youtube.com/embed/$1" frameborder="0" ></iframe>', $Text); + else + $Text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", "http://www.youtube.com/watch?v=$1", $Text); + } + if (strpos($Text,'[vimeo]') !== false) { + if ($tryoembed) { + $Text = preg_replace_callback("/\[vimeo\](https?:\/\/player.vimeo.com\/video\/[0-9]+).*?\[\/vimeo\]/ism",'tryoembed',$Text); + $Text = preg_replace_callback("/\[vimeo\](https?:\/\/vimeo.com\/[0-9]+).*?\[\/vimeo\]/ism",'tryoembed',$Text); + } + + $Text = preg_replace("/\[vimeo\]https?:\/\/player.vimeo.com\/video\/([0-9]+)(.*?)\[\/vimeo\]/ism",'[vimeo]$1[/vimeo]',$Text); + $Text = preg_replace("/\[vimeo\]https?:\/\/vimeo.com\/([0-9]+)(.*?)\[\/vimeo\]/ism",'[vimeo]$1[/vimeo]',$Text); + + if ($tryoembed) + $Text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '<iframe width="' . $a->videowidth . '" height="' . $a->videoheight . '" src="http://player.vimeo.com/video/$1" frameborder="0" ></iframe>', $Text); + else + $Text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", "http://vimeo.com/$1", $Text); + } // oembed tag $Text = oembed_bbcode2html($Text); @@ -392,11 +799,15 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { // Unhide all [noparse] contained bbtags unspacefying them // and triming the [noparse] tag. - - $Text = preg_replace_callback("/\[noparse\](.*?)\[\/noparse\]/ism", 'bb_unspacefy_and_trim',$Text); - $Text = preg_replace_callback("/\[nobb\](.*?)\[\/nobb\]/ism", 'bb_unspacefy_and_trim',$Text); - $Text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", 'bb_unspacefy_and_trim',$Text); - + if (strpos($Text,'[noparse]') !== false) { + $Text = preg_replace_callback("/\[noparse\](.*?)\[\/noparse\]/ism", 'bb_unspacefy_and_trim',$Text); + } + if (strpos($Text,'[nobb]') !== false) { + $Text = preg_replace_callback("/\[nobb\](.*?)\[\/nobb\]/ism", 'bb_unspacefy_and_trim',$Text); + } + if (strpos($Text,'[pre]') !== false) { + $Text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", 'bb_unspacefy_and_trim',$Text); + } $Text = preg_replace('/\[\&\;([#a-z0-9]+)\;\]/','&$1;',$Text); @@ -405,28 +816,6 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { $Text = preg_replace("/\<(.*?)(src|href)=\"[^hfm](.*?)\>/ism",'<$1$2="">',$Text); - if($saved_image) - $Text = bb_replace_images($Text, $saved_image); - - // Clean up the HTML by loading and saving the HTML with the DOM - // Only do it when it has to be done - for performance reasons - if (!$tryoembed) { - $doc = new DOMDocument(); - $doc->preserveWhiteSpace = false; - - $Text = mb_convert_encoding($Text, 'HTML-ENTITIES', "UTF-8"); - - $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">'; - @$doc->loadHTML($doctype."<html><body>".$Text."</body></html>"); - - $Text = $doc->saveHTML(); - $Text = str_replace(array("<html><body>", "</body></html>", $doctype), array("", "", ""), $Text); - - $Text = str_replace('<br></li>','</li>', $Text); - - $Text = mb_convert_encoding($Text, "UTF-8", 'HTML-ENTITIES'); - } - call_hooks('bbcode',$Text); return $Text; diff --git a/include/bookmarks.php b/include/bookmarks.php new file mode 100644 index 000000000..21a775f9a --- /dev/null +++ b/include/bookmarks.php @@ -0,0 +1,85 @@ +<?php /** @file */ + +require_once('include/menu.php'); + +function bookmark_add($channel,$sender,$taxonomy,$private,$opts = null) { + + $menu_id = 0; + $menu_name = ''; + $ischat = false; + + if(is_array($opts)) { + $menu_id = ((x($opts,'menu_id')) ? intval($opt['menu_id']) : 0); + $menu_name = ((x($opts,'menu_name')) ? escape_tags($opts['menu_name']) : ''); + $ischat = ((x($opts,'ischat')) ? intval($opts['ischat']) : 0); + } + + $iarr = array(); + $channel_id = $channel['channel_id']; + + if($private) + $iarr['contact_allow'] = array($channel['channel_hash']); + $iarr['mitem_link'] = $taxonomy['url']; + $iarr['mitem_desc'] = $taxonomy['term']; + $iarr['mitem_flags'] = (($ischat) ? MENU_ITEM_CHATROOM : 0); + + $m = @parse_url($taxonomy['url']); + $zrl = false; + if($m['host']) { + $r = q("select hubloc_url from hubloc where hubloc_host = '%s' limit 1", + dbesc($m['host']) + ); + if($r) + $zrl = true; + } + + if($zrl) + $iarr['mitem_flags'] |= MENU_ITEM_ZID; + + $arr = array(); + if(! $menu_name) { + $arr['menu_name'] = substr($sender['xchan_hash'],0,16) . ' ' . $sender['xchan_name']; + $arr['menu_desc'] = sprintf( t('%1$s\'s bookmarks'), $sender['xchan_name']); + } + else { + $arr['menu_name'] = $arr['menu_desc'] = $menu_name; + } + $arr['menu_flags'] = (($sender['xchan_hash'] === $channel['channel_hash']) ? MENU_BOOKMARK : MENU_SYSTEM|MENU_BOOKMARK); + $arr['menu_channel_id'] = $channel_id; + + if(! $menu_id) { + $x = menu_list($arr['menu_channel_id'],$arr['menu_name'],$arr['menu_flags']); + if($x) + $menu_id = $x[0]['menu_id']; + else + $menu_id = menu_create($arr); + } + + if(! $menu_id) { + logger('bookmark_add: unable to create menu ' . $arr['menu_name']); + return; + } + logger('add_bookmark: menu_id ' . $menu_id); + $r = q("select * from menu_item where mitem_link = '%s' and mitem_menu_id = %d and mitem_channel_id = %d limit 1", + dbesc($iarr['mitem_link']), + intval($menu_id), + intval($channel_id) + ); + if($r) + logger('add_bookmark: duplicate menu entry', LOGGER_DEBUG); + if(! $r) + $r = menu_add_item($menu_id,$channel_id,$iarr); + + return $r; +} + +function get_bookmark_link($observer) { + + if((! $observer) || ($observer['xchan_network'] !== 'zot')) + return ''; + + $h = @parse_url($observer['xchan_url']); + if($h) + return $h['scheme'] . '://' . $h['host'] . (($h['port']) ? ':' . $h['port'] : '') . '/rbmark?f='; + return ''; +} diff --git a/include/cache.php b/include/cache.php index 567046f7a..a70650b5e 100644 --- a/include/cache.php +++ b/include/cache.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ /** * cache api @@ -6,52 +6,37 @@ class Cache { public static function get($key){ - $r = q("SELECT `v` FROM `cache` WHERE `k`='%s' limit 1", + $r = q("SELECT v FROM cache WHERE k = '%s' limit 1", dbesc($key) ); - if (count($r)) return $r[0]['v']; + if ($r) + return $r[0]['v']; return null; } public static function set($key,$value) { - q("REPLACE INTO `cache` (`k`,`v`,`updated`) VALUES ('%s','%s','%s')", + $r = q("SELECT * FROM cache WHERE k = '%s' limit 1", + dbesc($key) + ); + if($r) { + q("UPDATE cache SET v = '%s', updated = '%s' WHERE k = '%s' limit 1", + dbesc($value), + dbesc(datetime_convert()), + dbesc($key)); + } + else { + q("INSERT INTO cache ( k, v, updated) VALUES ('%s','%s','%s')", dbesc($key), dbesc($value), dbesc(datetime_convert())); + } } -/* - * - * Leaving this legacy code temporaily to see how REPLACE fares - * as opposed to non-atomic checks when faced with fast moving key duplication. - * As a MySQL extension it isn't portable, but we're not yet very portable. - */ - -/* - * $r = q("SELECT * FROM `cache` WHERE `k`='%s' limit 1", - * dbesc($key) - * ); - * if(count($r)) { - * q("UPDATE `cache` SET `v` = '%s', `updated = '%s' WHERE `k` = '%s' limit 1", - * dbesc($value), - * dbesc(datetime_convert()), - * dbesc($key)); - * } - * else { - * q("INSERT INTO `cache` (`k`,`v`,`updated`) VALUES ('%s','%s','%s')", - * dbesc($key), - * dbesc($value), - * dbesc(datetime_convert())); - * } - * } - */ - - public static function clear(){ - q("DELETE FROM `cache` WHERE `updated` < '%s'", + q("DELETE FROM cache WHERE updated < '%s'", dbesc(datetime_convert('UTC','UTC',"now - 30 days"))); } diff --git a/include/chat.php b/include/chat.php new file mode 100644 index 000000000..4c79319ee --- /dev/null +++ b/include/chat.php @@ -0,0 +1,228 @@ +<?php /** @file */ + + +function chatroom_create($channel,$arr) { + + $ret = array('success' => false); + + $name = trim($arr['name']); + if(! $name) { + $ret['message'] = t('Missing room name'); + return $ret; + } + + $r = q("select cr_id from chatroom where cr_uid = %d and cr_name = '%s' limit 1", + intval($channel['channel_id']), + dbesc($name) + ); + if($r) { + $ret['message'] = t('Duplicate room name'); + return $ret; + } + + $r = q("select count(cr_id) as total from chatroom where cr_aid = %d", + intval($channel['channel_account_id']) + ); + if($r) + $limit = service_class_fetch($channel_id,'chatrooms'); + + if(($r) && ($limit !== false) && ($r[0]['total'] >= $limit)) { + $ret['message'] = upgrade_message(); + return $ret; + } + + if(! array_key_exists('expire',$arr)) + $arr['expire'] = 120; // minutes, e.g. 2 hours + + $created = datetime_convert(); + + $x = q("insert into chatroom ( cr_aid, cr_uid, cr_name, cr_created, cr_edited, cr_expire, allow_cid, allow_gid, deny_cid, deny_gid ) + values ( %d, %d , '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s' ) ", + intval($channel['channel_account_id']), + intval($channel['channel_id']), + dbesc($name), + dbesc($created), + dbesc($created), + intval($arr['expire']), + dbesc($arr['allow_cid']), + dbesc($arr['allow_gid']), + dbesc($arr['deny_cid']), + dbesc($arr['deny_gid']) + ); + + if($x) + $ret['success'] = true; + + return $ret; +} + + +function chatroom_destroy($channel,$arr) { + + $ret = array('success' => false); + if(intval($arr['cr_id'])) + $sql_extra = " and cr_id = " . intval($arr['cr_id']) . " "; + elseif(trim($arr['cr_name'])) + $sql_extra = " and cr_name = '" . protect_sprintf(dbesc(trim($arr['cr_name']))) . "' "; + else { + $ret['message'] = t('Invalid room specifier.'); + return $ret; + } + + $r = q("select * from chatroom where cr_uid = %d $sql_extra limit 1", + intval($channel['channel_id']) + ); + if(! $r) { + $ret['message'] = t('Invalid room specifier.'); + return $ret; + } + + q("delete from chatroom where cr_id = %d limit 1", + intval($r[0]['cr_id']) + ); + if($r[0]['cr_id']) { + q("delete from chatpresence where cp_room = %d", + intval($r[0]['cr_id']) + ); + q("delete from chat where chat_room = %d", + intval($r[0]['cr_id']) + ); + } + $ret['success'] = true; + return $ret; +} + + +function chatroom_enter($observer_xchan,$room_id,$status,$client) { + + if(! $room_id || ! $observer_xchan) + return; + + $r = q("select * from chatroom where cr_id = %d limit 1", + intval($room_id) + ); + if(! $r) { + notice( t('Room not found.') . EOL); + return false; + } + require_once('include/security.php'); + $sql_extra = permissions_sql($r[0]['cr_uid']); + + $x = q("select * from chatroom where cr_id = %d and cr_uid = %d $sql_extra limit 1", + intval($room_id), + intval($r[0]['cr_uid']) + ); + if(! $x) { + notice( t('Permission denied.') . EOL); + return false; + } + + $limit = service_class_fetch($r[0]['cr_uid'],'chatters_inroom'); + if($limit !== false) { + $x = q("select count(*) as total from chatpresence where cp_room = %d", + intval($room_id) + ); + if($x && $x[0]['total'] > $limit) { + notice( t('Room is full') . EOL); + return false; + } + } + + if(intval($x[0]['cr_expire'])) + $r = q("delete from chat where created < UTC_TIMESTAMP() - INTERVAL " . intval($x[0]['cr_expire']) . " MINUTE and chat_room = " . intval($x[0]['cr_id'])); + + $r = q("select * from chatpresence where cp_xchan = '%s' and cp_room = %d limit 1", + dbesc($observer_xchan), + intval($room_id) + ); + if($r) { + q("update chatpresence set cp_last = '%s' where cp_id = %d and cp_client = '%s' limit 1", + dbesc(datetime_convert()), + intval($r[0]['cp_id']), + dbesc($client) + ); + return true; + } + + $r = q("insert into chatpresence ( cp_room, cp_xchan, cp_last, cp_status, cp_client ) + values ( %d, '%s', '%s', '%s', '%s' )", + intval($room_id), + dbesc($observer_xchan), + dbesc(datetime_convert()), + dbesc($status), + dbesc($client) + ); + return $r; +} + + +function chatroom_leave($observer_xchan,$room_id,$client) { + if(! $room_id || ! $observer_xchan) + return; + + $r = q("select * from chatpresence where cp_xchan = '%s' and cp_room = %d and cp_client = '%s' limit 1", + dbesc($observer_xchan), + intval($room_id), + dbesc($client) + ); + if($r) { + q("delete from chatpresence where cp_id = %d limit 1", + intval($r[0]['cp_id']) + ); + } + + return true; +} + + +function chatroom_list($uid) { + require_once('include/security.php'); + $sql_extra = permissions_sql($uid); + + $r = q("select cr_name, cr_id, count(cp_id) as cr_inroom from chatroom left join chatpresence on cr_id = cp_room where cr_uid = %d $sql_extra group by cr_name order by cr_name", + intval($uid) + ); + + return $r; +} + +/** + * create a chat message via API. + * It is the caller's responsibility to enter the room. + */ + +function chat_message($uid,$room_id,$xchan,$text) { + + $ret = array('success' => false); + + if(! $text) + return; + + $sql_extra = permissions_sql($uid); + + $r = q("select * from chatroom where cr_uid = %d and cr_id = %d $sql_extra", + intval($uid), + intval($room_id) + ); + if(! $r) + return $ret; + + $arr = array( + 'chat_room' => $room_id, + 'chat_xchan' => $xchan, + 'chat_text' => $text + ); + + call_hooks('chat_message',$arr); + + $x = q("insert into chat ( chat_room, chat_xchan, created, chat_text ) + values( %d, '%s', '%s', '%s' )", + intval($room_id), + dbesc($xchan), + dbesc(datetime_convert()), + dbesc($arr['chat_text']) + ); + + $ret['success'] = true; + return $ret; +} diff --git a/include/cli_startup.php b/include/cli_startup.php index d9f6ecb8e..6bd4e7520 100644 --- a/include/cli_startup.php +++ b/include/cli_startup.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ require_once('boot.php'); @@ -14,14 +14,13 @@ function cli_startup() { if(is_null($db)) { @include(".htconfig.php"); - require_once("dba.php"); - $db = new dba($db_host, $db_user, $db_pass, $db_data); - unset($db_host, $db_user, $db_pass, $db_data); + require_once('include/dba/dba_driver.php'); + $db = dba_factory($db_host, $db_port, $db_user, $db_pass, $db_data); + unset($db_host, $db_port, $db_user, $db_pass, $db_data); }; require_once('include/session.php'); - load_config('config'); load_config('system'); $a->set_baseurl(get_config('system','baseurl')); diff --git a/include/cli_suggest.php b/include/cli_suggest.php new file mode 100644 index 000000000..321ffd2e0 --- /dev/null +++ b/include/cli_suggest.php @@ -0,0 +1,22 @@ +<?php /** @file */ + +require_once('boot.php'); +require_once('include/cli_startup.php'); +require_once('include/socgraph.php'); + + +function cli_suggest_run($argv, $argc){ + + cli_startup(); + + $a = get_app(); + + update_suggestions(); + +} + +if (array_search(__file__,get_included_files())===0){ + cli_suggest_run($argv,$argc); + killme(); +} + diff --git a/include/comanche.php b/include/comanche.php new file mode 100644 index 000000000..b0eac475d --- /dev/null +++ b/include/comanche.php @@ -0,0 +1,250 @@ +<?php /** @file */ + +require_once('include/security.php'); +require_once('include/menu.php'); +require_once('include/widgets.php'); + +// When editing a webpage - a dropdown is needed to select a page layout +// On submit, the pdl_select value (which is the mid of an item with item_restrict = ITEM_PDL) is stored in +// the webpage's resource_id, with resource_type 'pdl'. + +// Then when displaying a webpage, we can see if it has a pdl attached. If not we'll +// use the default site/page layout. + +// If it has a pdl we'll load it as we know the mid and pass the body through comanche_parser() which will generate the +// page layout from the given description + + +function pdl_selector($uid,$current="") { + + $o = ''; + + $sql_extra = item_permissions_sql($uid); + + $r = q("select item_id.*, mid from item_id left join item on iid = item.id where item_id.uid = %d and item_id.uid = item.uid and service = 'PDL' order by sid asc", + intval($owner) + ); + + $arr = array('channel_id' => $uid, 'current' => $current, 'entries' => $r); + call_hooks('pdl_selector',$arr); + + $entries = $arr['entries']; + $current = $arr['current']; + + $o .= "<select name=\"pdl_select\" id=\"pdl_select\" size=\"1\" >"; + $entries[] = array('title' => t('Default'), 'mid' => ''); + foreach($entries as $selection) { + $selected = (($selection == $current) ? ' selected="selected" ' : ''); + $o .= "<option value=\"{$selection['mid']}\" $selected >{$selection['sid']}</option>"; + } + + $o .= '</select>'; + return $o; +} + + + +function comanche_parser(&$a,$s) { + + $cnt = preg_match_all("/\[comment\](.*?)\[\/comment\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $s = str_replace($mtch[0],'',$s); + } + } + + $cnt = preg_match("/\[layout\](.*?)\[\/layout\]/ism", $s, $matches); + if($cnt) + $a->page['template'] = trim($matches[1]); + + $cnt = preg_match("/\[template=(.*?)\](.*?)\[\/template\]/ism", $s, $matches); + if($cnt) { + $a->page['template'] = trim($matches[2]); + $a->page['template_style'] = trim($matches[2]) . '_' . $matches[1]; + } + + $cnt = preg_match("/\[template\](.*?)\[\/template\]/ism", $s, $matches); + if($cnt) { + $a->page['template'] = trim($matches[1]); + } + + $cnt = preg_match("/\[theme=(.*?)\](.*?)\[\/theme\]/ism", $s, $matches); + if($cnt) { + $a->layout['schema'] = trim($matches[1]); + $a->layout['theme'] = trim($matches[2]); + } + + $cnt = preg_match("/\[theme\](.*?)\[\/theme\]/ism", $s, $matches); + if($cnt) + $a->layout['theme'] = trim($matches[1]); + + $cnt = preg_match_all("/\[region=(.*?)\](.*?)\[\/region\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $a->layout['region_' . $mtch[1]] = comanche_region($a,$mtch[2]); + } + } + + $cnt = preg_match_all("/\[webpage\](.*?)\[\/webpage\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + // only the last webpage definition is used if there is more than one + foreach($matches as $mtch) { + $a->layout['webpage'] = comanche_webpage($a,$mtch[1]); + } + } + +} + + +function comanche_menu($name,$class = '') { + $channel_id = comanche_get_channel_id(); + if($channel_id) { + $m = menu_fetch($name,$channel_id,get_observer_hash()); + return menu_render($m,$class); + } +} + +function comanche_replace_region($match) { + $a = get_app(); + if(array_key_exists($match[1],$a->page)) { + return $a->page[$match[1]]; + } +} + +/** + * @function comanche_get_channel_id() + * Returns the channel_id of the profile owner of the page, or the local_user if there is no profile owner. + * Otherwise returns 0 + */ + +function comanche_get_channel_id() { + $channel_id = ((is_array(get_app()->profile)) ? get_app()->profile['profile_uid'] : 0); + if((! $channel_id) && (local_user())) + $channel_id = local_user(); + return $channel_id; +} + +function comanche_block($name) { + + $channel_id = comanche_get_channel_id(); + + if($channel_id) { + $o = ''; + $r = q("select * from item inner join item_id on iid = item.id and item_id.uid = item.uid and item.uid = %d and service = 'BUILDBLOCK' and sid = '%s' limit 1", + intval($channel_id), + dbesc($name) + ); + if($r) { + $o = '<div class="widget bblock">'; + if($r[0]['title']) + $o .= '<h3>' . $r[0]['title'] . '</h3>'; + $o .= prepare_text($r[0]['body'],$r[0]['mimetype']); + $o .= '</div>'; + + } + } + return $o; +} + +// This doesn't really belong in Comanche, but it could also be argued that it is the perfect place. +// We need to be able to select what kind of template and decoration to use for the webpage at the heart of our content. +// For now we'll allow an '[authored]' element which defaults to name and date, or 'none' to remove these, and perhaps +// 'full' to provide a social network style profile photo. +// But leave it open to have richer templating options and perhaps ultimately discard this one, once we have a better idea +// of what template and webpage options we might desire. + +function comanche_webpage(&$a,$s) { + + $ret = array(); + $cnt = preg_match_all("/\[authored\](.*?)\[\/authored\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $ret['authored'] = $mtch[1]; + } + } + return $ret; +} + + +// Widgets will have to get any operational arguments from the session, +// the global app environment, or config storage until we implement argument passing + + +function comanche_widget($name,$text) { + $a = get_app(); + $vars = array(); + $cnt = preg_match_all("/\[var=(.*?)\](.*?)\[\/var\]/ism", $text, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $vars[$mtch[1]] = $mtch[2]; + } + } + + $func = 'widget_' . trim($name); + if(function_exists($func)) + return $func($vars); +} + + +function comanche_region(&$a,$s) { + + $cnt = preg_match_all("/\[menu\](.*?)\[\/menu\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $s = str_replace($mtch[0],comanche_menu(trim($mtch[1])),$s); + } + } + + // menu class e.g. [menu=horizontal]my_menu[/menu] or [menu=tabbed]my_menu[/menu] + // allows different menu renderings to be applied + + $cnt = preg_match_all("/\[menu=(.*?)\](.*?)\[\/menu\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $s = str_replace($mtch[0],comanche_menu(trim($mtch[2]),$mtch[1]),$s); + } + } + $cnt = preg_match_all("/\[block\](.*?)\[\/block\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $s = str_replace($mtch[0],comanche_block(trim($mtch[1])),$s); + } + } + + // need to modify this to accept parameters + + $cnt = preg_match_all("/\[widget=(.*?)\](.*?)\[\/widget\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $s = str_replace($mtch[0],comanche_widget(trim($mtch[1]),$mtch[2]),$s); + } + } + + return $s; +} + + +/* + * @function register_page_template($arr) + * Registers a page template/variant for use by Comanche selectors + * @param array $arr + * 'template' => template name + * 'variant' => array( + * 'name' => variant name + * 'desc' => text description + * 'regions' => array( + * 'name' => name + * 'desc' => text description + * ) + * ) + */ + + +function register_page_template($arr) { + get_app()->page_layouts[$arr['template']] = array($arr['variant']); + return; +} + + + + diff --git a/include/config.php b/include/config.php index 44606e329..add7b5f71 100644 --- a/include/config.php +++ b/include/config.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ /** * @@ -15,24 +15,27 @@ // retrieve a "family" of config variables from database to cached storage -if(! function_exists('load_config')) { function load_config($family) { global $a; - $r = q("SELECT * FROM `config` WHERE `cat` = '%s'", dbesc($family)); - if(count($r)) { - foreach($r as $rr) { - $k = $rr['k']; - if ($family === 'config') { - $a->config[$k] = $rr['v']; - } else { - $a->config[$family][$k] = $rr['v']; + + if(! array_key_exists($family,$a->config)) + $a->config[$family] = array(); + + if(! array_key_exists('config_loaded',$a->config[$family])) { + + $r = q("SELECT * FROM config WHERE cat = '%s'", dbesc($family)); + + if($r !== false) { + if($r) { + foreach($r as $rr) { + $k = $rr['k']; + $a->config[$family][$k] = $rr['v']; + } } + $a->config[$family]['config_loaded'] = true; } - } else if ($family != 'config') { - // Negative caching - $a->config[$family] = "!<unset>!"; - } -}} + } +} // get a particular config variable given the family name // and key. Returns false if not set. @@ -42,55 +45,50 @@ function load_config($family) { // local config cache, pull it into the cache so we don't have // to hit the DB again for this item. -if(! function_exists('get_config')) { -function get_config($family, $key, $instore = false) { + +function get_config($family, $key) { global $a; - if(! $instore) { - // Looking if the whole family isn't set - if(isset($a->config[$family])) { - if($a->config[$family] === '!<unset>!') { - return false; - } - } + if((! array_key_exists($family,$a->config)) || (! array_key_exists('config_loaded',$a->config[$family]))) + load_config($family); - if(isset($a->config[$family][$key])) { - if($a->config[$family][$key] === '!<unset>!') { - return false; - } - return $a->config[$family][$key]; + if(array_key_exists('config_loaded',$a->config[$family])) { + if(! array_key_exists($key,$a->config[$family])) { + return false; } + return ((! is_array($a->config[$family][$key])) && (preg_match('|^a:[0-9]+:{.*}$|s', $a->config[$family][$key])) + ? unserialize($a->config[$family][$key]) + : $a->config[$family][$key] + ); } - $ret = q("SELECT `v` FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1", + return false; +} + +function get_config_from_storage($family,$key) { + $ret = q("select * from config where cat = '%s' and k = '%s' limit 1", dbesc($family), dbesc($key) ); - if(count($ret)) { - // manage array value - $val = (preg_match("|^a:[0-9]+:{.*}$|s", $ret[0]['v'])?unserialize( $ret[0]['v']):$ret[0]['v']); - $a->config[$family][$key] = $val; - return $val; - } - else { - $a->config[$family][$key] = '!<unset>!'; - } - return false; -}} + return $ret; +} + + // Store a config value ($value) in the category ($family) // under the key ($key) // Return the value, or false if the database update failed -if(! function_exists('set_config')) { function set_config($family,$key,$value) { global $a; // manage array value - $dbvalue = (is_array($value)?serialize($value):$value); - $dbvalue = (is_bool($dbvalue) ? intval($dbvalue) : $dbvalue); - if(get_config($family,$key,true) === false) { + $dbvalue = ((is_array($value)) ? serialize($value) : $value); + $dbvalue = ((is_bool($dbvalue)) ? intval($dbvalue) : $dbvalue); + + if(get_config($family,$key) === false || (! get_config_from_storage($family,$key))) { $a->config[$family][$key] = $value; - $ret = q("INSERT INTO `config` ( `cat`, `k`, `v` ) VALUES ( '%s', '%s', '%s' ) ", + + $ret = q("INSERT INTO config ( cat, k, v ) VALUES ( '%s', '%s', '%s' ) ", dbesc($family), dbesc($key), dbesc($dbvalue) @@ -100,7 +98,7 @@ function set_config($family,$key,$value) { return $ret; } - $ret = q("UPDATE `config` SET `v` = '%s' WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1", + $ret = q("UPDATE config SET v = '%s' WHERE cat = '%s' AND k = '%s' LIMIT 1", dbesc($dbvalue), dbesc($family), dbesc($key) @@ -111,129 +109,254 @@ function set_config($family,$key,$value) { if($ret) return $value; return $ret; -}} +} - -if(! function_exists('load_pconfig')) { -function load_pconfig($uid,$family) { +function del_config($family,$key) { global $a; - $r = q("SELECT * FROM `pconfig` WHERE `cat` = '%s' AND `uid` = %d", + if(array_key_exists($family,$a->config) && array_key_exists($key,$a->config[$family])) + unset($a->config[$family][$key]); + $ret = q("DELETE FROM config WHERE cat = '%s' AND k = '%s' LIMIT 1", dbesc($family), + dbesc($key) + ); + return $ret; +} + + +function load_pconfig($uid,$family = '') { + global $a; + + if($uid === false) + return false; + + if(! array_key_exists($uid,$a->config)) + $a->config[$uid] = array(); + + // family is no longer used - load entire user config + + $r = q("SELECT * FROM `pconfig` WHERE `uid` = %d", intval($uid) ); - if(count($r)) { + + if($r) { foreach($r as $rr) { $k = $rr['k']; - $a->config[$uid][$family][$k] = $rr['v']; + $c = $rr['cat']; + if(! array_key_exists($c,$a->config[$uid])) { + $a->config[$uid][$c] = array(); + $a->config[$uid][$c]['config_loaded'] = true; + } + $a->config[$uid][$c][$k] = $rr['v']; } - } else if ($family != 'config') { - // Negative caching - $a->config[$uid][$family] = "!<unset>!"; - } -}} + } +} + -if(! function_exists('get_pconfig')) { function get_pconfig($uid,$family, $key, $instore = false) { global $a; - if(! $instore) { - // Looking if the whole family isn't set - if(isset($a->config[$uid][$family])) { - if($a->config[$uid][$family] === '!<unset>!') { - return false; - } - } + if($uid === false) + return false; - if(isset($a->config[$uid][$family][$key])) { - if($a->config[$uid][$family][$key] === '!<unset>!') { - return false; - } - return $a->config[$uid][$family][$key]; - } + if(! array_key_exists($uid,$a->config)) + load_pconfig($uid); + + if((! array_key_exists($family,$a->config[$uid])) || (! array_key_exists($key,$a->config[$uid][$family]))) + return false; + + return ((! is_array($a->config[$uid][$family][$key])) && (preg_match('|^a:[0-9]+:{.*}$|s', $a->config[$uid][$family][$key])) + ? unserialize($a->config[$uid][$family][$key]) + : $a->config[$uid][$family][$key] + ); +} + +function set_pconfig($uid,$family,$key,$value) { + + global $a; + + + // manage array value + $dbvalue = ((is_array($value)) ? serialize($value) : $value); + $dbvalue = ((is_bool($dbvalue)) ? intval($dbvalue) : $dbvalue); + + if(get_pconfig($uid,$family,$key) === false) { + if(! array_key_exists($uid,$a->config)) + $a->config[$uid] = array(); + if(! array_key_exists($family,$a->config[$uid])) + $a->config[$uid][$family] = array(); + + // keep a separate copy for all variables which were + // set in the life of this page. We need this to + // synchronise channel clones. + + if(! array_key_exists('transient',$a->config[$uid])) + $a->config[$uid]['transient'] = array(); + if(! array_key_exists($family,$a->config[$uid]['transient'])) + $a->config[$uid]['transient'][$family] = array(); + + $a->config[$uid][$family][$key] = $value; + $a->config[$uid]['transient'][$family][$key] = $value; + + $ret = q("INSERT INTO pconfig ( uid, cat, k, v ) VALUES ( %d, '%s', '%s', '%s' ) ", + intval($uid), + dbesc($family), + dbesc($key), + dbesc($dbvalue) + ); + if($ret) + return $value; + return $ret; } - $ret = q("SELECT `v` FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1", + $ret = q("UPDATE pconfig SET v = '%s' WHERE uid = %d and cat = '%s' AND k = '%s' LIMIT 1", + dbesc($dbvalue), intval($uid), dbesc($family), dbesc($key) ); - if(count($ret)) { - $val = (preg_match("|^a:[0-9]+:{.*}$|s", $ret[0]['v'])?unserialize( $ret[0]['v']):$ret[0]['v']); - $a->config[$uid][$family][$key] = $val; - return $val; - } - else { - $a->config[$uid][$family][$key] = '!<unset>!'; - } - return false; -}} + // keep a separate copy for all variables which were + // set in the life of this page. We need this to + // synchronise channel clones. -if(! function_exists('del_config')) { -function del_config($family,$key) { + if(! array_key_exists('transient',$a->config[$uid])) + $a->config[$uid]['transient'] = array(); + if(! array_key_exists($family,$a->config[$uid]['transient'])) + $a->config[$uid]['transient'][$family] = array(); + + $a->config[$uid][$family][$key] = $value; + $a->config[$uid]['transient'][$family][$key] = $value; + + if($ret) + return $value; + return $ret; +} + + +function del_pconfig($uid,$family,$key) { global $a; - if(x($a->config[$family],$key)) - unset($a->config[$family][$key]); - $ret = q("DELETE FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1", + if(x($a->config[$uid][$family],$key)) + unset($a->config[$uid][$family][$key]); + $ret = q("DELETE FROM pconfig WHERE uid = %d AND cat = '%s' AND k = '%s' LIMIT 1", + intval($uid), dbesc($family), dbesc($key) ); return $ret; -}} +} + +function load_xconfig($xchan,$family = '') { + global $a; + + if(! $xchan) + return false; + + if(! array_key_exists($xchan,$a->config)) + $a->config[$xchan] = array(); + + // family is no longer used. Entire config is loaded + + $r = q("SELECT * FROM `xconfig` WHERE `xchan` = '%s'", + dbesc($xchan) + ); + + if($r) { + foreach($r as $rr) { + $k = $rr['k']; + $c = $rr['cat']; + if(! array_key_exists($c,$a->config[$xchan])) { + $a->config[$xchan][$c] = array(); + $a->config[$xchan][$c]['config_loaded'] = true; + } + $a->config[$xchan][$c][$k] = $rr['v']; + } + } +} + -// Same as above functions except these are for personal config storage and take an -// additional $uid argument. -if(! function_exists('set_pconfig')) { -function set_pconfig($uid,$family,$key,$value) { + +function get_xconfig($xchan,$family, $key) { global $a; - // manage array value - $dbvalue = (is_array($value)?serialize($value):$value); + if(! $xchan) + return false; - if(get_pconfig($uid,$family,$key,true) === false) { - $a->config[$uid][$family][$key] = $value; - $ret = q("INSERT INTO `pconfig` ( `uid`, `cat`, `k`, `v` ) VALUES ( %d, '%s', '%s', '%s' ) ", - intval($uid), + if(! array_key_exists($xchan,$a->config)) + load_xconfig($xchan); + + if((! array_key_exists($family,$a->config[$xchan])) || (! array_key_exists($key,$a->config[$xchan][$family]))) + return false; + + return ((! is_array($a->config[$xchan][$family][$key])) && (preg_match('|^a:[0-9]+:{.*}$|s', $a->config[$xchan][$family][$key])) + ? unserialize($a->config[$xchan][$family][$key]) + : $a->config[$xchan][$family][$key] + ); + +} + + +function set_xconfig($xchan,$family,$key,$value) { + + global $a; + + // manage array value + $dbvalue = ((is_array($value)) ? serialize($value) : $value); + $dbvalue = ((is_bool($dbvalue)) ? intval($dbvalue) : $dbvalue); + + if(get_xconfig($xchan,$family,$key) === false) { + if(! array_key_exists($xchan,$a->config)) + $a->config[$xchan] = array(); + if(! array_key_exists($family,$a->config[$xchan])) + $a->config[$xchan][$family] = array(); + + $a->config[$xchan][$family][$key] = $value; + $ret = q("INSERT INTO xconfig ( xchan, cat, k, v ) VALUES ( '%s', '%s', '%s', '%s' ) ", + dbesc($xchan), dbesc($family), dbesc($key), dbesc($dbvalue) ); - if($ret) + if($ret) return $value; return $ret; } - $ret = q("UPDATE `pconfig` SET `v` = '%s' WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1", + + $ret = q("UPDATE xconfig SET v = '%s' WHERE xchan = '%s' and cat = '%s' AND k = '%s' LIMIT 1", dbesc($dbvalue), - intval($uid), + dbesc($xchan), dbesc($family), dbesc($key) ); - $a->config[$uid][$family][$key] = $value; + $a->config[$xchan][$family][$key] = $value; if($ret) return $value; return $ret; -}} -if(! function_exists('del_pconfig')) { -function del_pconfig($uid,$family,$key) { +} + + +function del_xconfig($xchan,$family,$key) { global $a; - if(x($a->config[$uid][$family],$key)) - unset($a->config[$uid][$family][$key]); - $ret = q("DELETE FROM `pconfig` WHERE `uid` = %d AND `cat` = '%s' AND `k` = '%s' LIMIT 1", - intval($uid), + if(x($a->config[$xchan][$family],$key)) + unset($a->config[$xchan][$family][$key]); + $ret = q("DELETE FROM `xconfig` WHERE `xchan` = '%s' AND `cat` = '%s' AND `k` = '%s' LIMIT 1", + dbesc($xchan), dbesc($family), dbesc($key) ); return $ret; -}} +} + + + diff --git a/include/contact_selectors.php b/include/contact_selectors.php index 76d005305..a3cfd2489 100644 --- a/include/contact_selectors.php +++ b/include/contact_selectors.php @@ -1,21 +1,19 @@ -<?php +<?php /** @file */ -function contact_profile_assign($current,$foreign_net) { +function contact_profile_assign($current) { $o = ''; - $disabled = (($foreign_net) ? ' disabled="true" ' : ''); + $o .= "<select id=\"contact-profile-selector\" name=\"profile_assign\" />\r\n"; - $o .= "<select id=\"contact-profile-selector\" $disabled name=\"profile-assign\" />\r\n"; + $r = q("SELECT profile_guid, profile_name FROM `profile` WHERE `uid` = %d", + intval($_SESSION['uid'])); - $r = q("SELECT `id`, `profile_name` FROM `profile` WHERE `uid` = %d", - intval($_SESSION['uid'])); - - if(count($r)) { + if($r) { foreach($r as $rr) { - $selected = (($rr['id'] == $current) ? " selected=\"selected\" " : ""); - $o .= "<option value=\"{$rr['id']}\" $selected >{$rr['profile_name']}</option>\r\n"; + $selected = (($rr['profile_guid'] == $current) ? " selected=\"selected\" " : ""); + $o .= "<option value=\"{$rr['profile_guid']}\" $selected >{$rr['profile_name']}</option>\r\n"; } } $o .= "</select>\r\n"; diff --git a/include/contact_widgets.php b/include/contact_widgets.php index e0c9ca0e8..758b7291b 100644 --- a/include/contact_widgets.php +++ b/include/contact_widgets.php @@ -1,15 +1,6 @@ -<?php +<?php /** @file */ -function follow_widget() { - return replace_macros(get_markup_template('follow.tpl'),array( - '$connect' => t('Add New Connection'), - '$desc' => t('Enter the channel address'), - '$hint' => t('Example: bob@example.com, http://example.com/barbara'), - '$follow' => t('Connect') - )); - -} function findpeople_widget() { require_once('include/Contact.php'); @@ -24,6 +15,8 @@ function findpeople_widget() { . '</div>' . $inv; } } + + $advanced_search = ((local_user() && get_pconfig(local_user(),'feature','expert')) ? t('Advanced') : false); return replace_macros(get_markup_template('peoplefind.tpl'),array( '$findpeople' => t('Find Channels'), @@ -32,49 +25,18 @@ function findpeople_widget() { '$hint' => t('Examples: Robert Morgenstein, Fishing'), '$findthem' => t('Find'), '$suggest' => t('Channel Suggestions'), - '$similar' => t('Similar Interests'), + '$similar' => '', // FIXME and uncomment when mod/match working // t('Similar Interests'), '$random' => t('Random Profile'), - '$inv' => t('Invite Friends') + '$inv' => t('Invite Friends'), + '$advanced_search' => $advanced_search, + '$advanced_hint' => t('Exammple: name=fred and country=iceland'), + '$find_advanced' => t('Advanced Find'), + '$loggedin' => local_user() )); } -function networks_widget($baseurl,$selected = '') { - - $a = get_app(); - - if(! local_user()) - return ''; - - - $r = q("select distinct(network) from contact where uid = %d and self = 0", - intval(local_user()) - ); - - $nets = array(); - if(count($r)) { - require_once('include/contact_selectors.php'); - foreach($r as $rr) { - if($rr['network']) - $nets[] = array('ref' => $rr['network'], 'name' => network_to_name($rr['network']), 'selected' => (($selected == $rr['network']) ? 'selected' : '' )); - } - } - - if(count($nets) < 2) - return ''; - - return replace_macros(get_markup_template('nets.tpl'),array( - '$title' => t('Networks'), - '$desc' => '', - '$sel_all' => (($selected == '') ? 'selected' : ''), - '$all' => t('All Networks'), - '$nets' => $nets, - '$base' => $baseurl, - - )); -} - function fileas_widget($baseurl,$selected = '') { $a = get_app(); @@ -105,15 +67,22 @@ function fileas_widget($baseurl,$selected = '') { function categories_widget($baseurl,$selected = '') { + $a = get_app(); + if(! feature_enabled($a->profile['profile_uid'],'categories')) return ''; - $a = get_app(); - $terms = array(); - $r = q("select distinct(term) from term where uid = %d and type = %d order by term asc", + $r = q("select distinct(term.term) + from term join item on term.oid = item.id + where item.uid = %d + and term.uid = item.uid + and term.type = %d + and item.author_xchan = '%s' + order by term.term asc", intval($a->profile['profile_uid']), - intval(TERM_CATEGORY) + intval(TERM_CATEGORY), + dbesc($a->profile['channel_hash']) ); if($r && count($r)) { foreach($r as $rr) @@ -139,60 +108,29 @@ function common_friends_visitor_widget($profile_uid) { if(local_user() == $profile_uid) return; - $cid = $zcid = 0; + $observer_hash = get_observer_hash(); - if(is_array($_SESSION['remote'])) { - foreach($_SESSION['remote'] as $visitor) { - if($visitor['uid'] == $profile_uid) { - $cid = $visitor['cid']; - break; - } - } - } - - if(! $cid) { - if(get_my_url()) { - $r = q("select id from contact where nurl = '%s' and uid = %d limit 1", - dbesc(normalise_link(get_my_url())), - intval($profile_uid) - ); - if(count($r)) - $cid = $r[0]['id']; - else { - $r = q("select id from gcontact where nurl = '%s' limit 1", - dbesc(normalise_link(get_my_url())) - ); - if(count($r)) - $zcid = $r[0]['id']; - } - } - } - - if($cid == 0 && $zcid == 0) - return; + if((! $observer_hash) || (! perm_is_allowed($profile_uid,$observer_hash,'view_contacts'))) + return; require_once('include/socgraph.php'); - if($cid) - $t = count_common_friends($profile_uid,$cid); - else - $t = count_common_friends_zcid($profile_uid,$zcid); + $t = count_common_friends($profile_uid,$observer_hash); if(! $t) return; - if($cid) - $r = common_friends($profile_uid,$cid,0,5,true); - else - $r = common_friends_zcid($profile_uid,$zcid,0,5,true); + $r = common_friends($profile_uid,$observer_hash,0,5,true); return replace_macros(get_markup_template('remote_friends_common.tpl'), array( - '$desc' => sprintf( tt("%d contact in common", "%d contacts in common", $t), $t), + '$desc' => sprintf( tt("%d connection in common", "%d connections in common", $t), $t), '$base' => $a->get_baseurl(), '$uid' => $profile_uid, - '$cid' => (($cid) ? $cid : '0'), + '$cid' => $observer, '$linkmore' => (($t > 5) ? 'true' : ''), '$more' => t('show more'), '$items' => $r )); -};
\ No newline at end of file +}; + + diff --git a/include/conversation.php b/include/conversation.php index ea2f84cbf..0ca3e88f0 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -1,9 +1,12 @@ -<?php +<?php /** @file */ + +require_once('include/items.php'); // Note: the code in 'item_extract_images' and 'item_redir_and_replace_images' // is identical to the code in mod/message.php for 'item_extract_images' and // 'item_redir_and_replace_images' -if(! function_exists('item_extract_images')) { + + function item_extract_images($body) { $saved_image = array(); @@ -43,18 +46,21 @@ function item_extract_images($body) { $new_body = $new_body . $orig_body; return array('body' => $new_body, 'images' => $saved_image); -}} +} + -if(! function_exists('item_redir_and_replace_images')) { function item_redir_and_replace_images($body, $images, $cid) { $origbody = $body; $newbody = ''; + $observer = get_app()->get_observer(); + $obhash = (($observer) ? $observer['xchan_hash'] : ''); + $obaddr = (($observer) ? $observer['xchan_addr'] : ''); + for($i = 0; $i < count($images); $i++) { $search = '/\[url\=(.*?)\]\[!#saved_image' . $i . '#!\]\[\/url\]' . '/is'; - $replace = '[url=' . z_path() . '/redir/' . $cid - . '?f=1&url=' . '$1' . '][!#saved_image' . $i . '#!][/url]' ; + $replace = '[url=' . magiclink_url($obhash,$obaddr,'$1') . '][!#saved_image' . $i . '#!][/url]' ; $img_end = strpos($origbody, '[!#saved_image' . $i . '#!][/url]') + strlen('[!#saved_image' . $i . '#!][/url]'); $process_part = substr($origbody, 0, $img_end); @@ -75,7 +81,7 @@ function item_redir_and_replace_images($body, $images, $cid) { } return $newbody; -}} +} @@ -85,13 +91,18 @@ function item_redir_and_replace_images($body, $images, $cid) { function localize_item(&$item){ - $extracted = item_extract_images($item['body']); - if($extracted['images']) - $item['body'] = item_redir_and_replace_images($extracted['body'], $extracted['images'], $item['contact-id']); - if (activity_match($item['verb'],ACTIVITY_LIKE) || activity_match($item['verb'],ACTIVITY_DISLIKE)){ + + if(! $item['object']) + return; - $obj= json_decode($item['object'],true); + if($item['item_flags'] & ITEM_THREAD_TOP) + return; + + $obj = json_decode_plus($item['object']); + if((! $obj) && ($item['object'])) { + logger('localize_item: failed to decode object: ' . print_r($item['object'],true)); + } if($obj['author'] && $obj['author']['link']) $author_link = get_rel_link($obj['author']['link'],'alternate'); @@ -102,32 +113,52 @@ function localize_item(&$item){ $item_url = get_rel_link($obj['link'],'alternate'); + $Bphoto = ''; + + switch($obj['type']) { + case ACTIVITY_OBJ_PHOTO: + $post_type = t('photo'); + break; + case ACTIVITY_OBJ_EVENT: + $post_type = t('event'); + break; + case ACTIVITY_OBJ_PERSON: + $post_type = t('channel'); + $author_name = $obj['title']; + if($obj['link']) { + $author_link = get_rel_link($obj['link'],'alternate'); + $Bphoto = get_rel_link($obj['link'],'photo'); + } + break; + case ACTIVITY_OBJ_THING: + $post_type = $obj['title']; + if($obj['owner']) { + if(array_key_exists('name',$obj['owner'])) + $obj['owner']['name']; + if(array_key_exists('link',$obj['owner'])) + $author_link = get_rel_link($obj['owner']['link'],'alternate'); + } + if($obj['link']) { + $Bphoto = get_rel_link($obj['link'],'photo'); + } + break; + + case ACTIVITY_OBJ_NOTE: + default: + $post_type = t('status'); + if($obj['mid'] != $obj['parent_mid']) + $post_type = t('comment'); + break; + } // If we couldn't parse something useful, don't bother translating. // We need something better than zid here, probably magic_link(), but it needs writing if($author_link && $author_name && $item_url) { - - $author = '[url=' . zid($item['author']['xchan_url']) . ']' . $item['author']['xchan_name'] . '[/url]'; - $objauthor = '[url=' . zid($author_link) . ']' . $author_name . '[/url]'; + $author = '[zrl=' . chanlink_url($item['author']['xchan_url']) . ']' . $item['author']['xchan_name'] . '[/zrl]'; + $objauthor = '[zrl=' . chanlink_url($author_link) . ']' . $author_name . '[/zrl]'; - switch($obj->type) { - case ACTIVITY_OBJ_PHOTO: - $post_type = t('photo'); - break; - case ACTIVITY_OBJ_EVENT: - $post_type = t('event'); - break; - case ACTIVITY_OBJ_NOTE: - default: - if(! ($item_flags & ITEM_THREAD_TOP)) - $post_type = t('comment'); - else - $post_type = t('status'); - break; - } - - $plink = '[url=' . zid($item_url) . ']' . $post_type . '[/url]'; + $plink = '[zrl=' . zid($item_url) . ']' . $post_type . '[/zrl]'; if(activity_match($item['verb'],ACTIVITY_LIKE)) { $bodyverb = t('%1$s likes %2$s\'s %3$s'); @@ -136,20 +167,26 @@ function localize_item(&$item){ $bodyverb = t('%1$s doesn\'t like %2$s\'s %3$s'); } $item['body'] = $item['localize'] = sprintf($bodyverb, $author, $objauthor, $plink); + if($Bphoto != "") + $item['body'] .= "\n\n\n" . '[zrl=' . chanlink_url($author_link) . '][zmg=80x80]' . $Bphoto . '[/zmg][/zrl]'; } + else { + logger('localize_item like failed: link ' . $author_link . ' name ' . $author_name . ' url ' . $item_url); + } } if (activity_match($item['verb'],ACTIVITY_FRIEND)) { - if ($item['obj_type']=="" || $item['obj_type']!== ACTIVITY_OBJ_PERSON) return; + +// if ($item['obj_type']=="" || $item['obj_type']!== ACTIVITY_OBJ_PERSON) return; $Aname = $item['author']['xchan_name']; $Alink = $item['author']['xchan_url']; - $obj= json_decode($item['object'],true); + $obj= json_decode_plus($item['object']); $Blink = $Bphoto = ''; @@ -160,15 +197,19 @@ function localize_item(&$item){ $Bname = $obj['title']; - $A = '[url=' . zid($Alink) . ']' . $Aname . '[/url]'; - $B = '[url=' . zid($Blink) . ']' . $Bname . '[/url]'; - if ($Bphoto!="") $Bphoto = '[url=' . zid($Blink) . '][img=80x80]' . $Bphoto . '[/img][/url]'; + $A = '[zrl=' . chanlink_url($Alink) . ']' . $Aname . '[/zrl]'; + $B = '[zrl=' . chanlink_url($Blink) . ']' . $Bname . '[/zrl]'; + if ($Bphoto!="") $Bphoto = '[zrl=' . chanlink_url($Blink) . '][zmg=80x80]' . $Bphoto . '[/zmg][/zrl]'; - $item['body'] = $item['localize'] = sprintf( t('%1$s is now friends with %2$s'), $A, $B); + $item['body'] = $item['localize'] = sprintf( t('%1$s is now connected with %2$s'), $A, $B); $item['body'] .= "\n\n\n" . $Bphoto; } if (stristr($item['verb'],ACTIVITY_POKE)) { + + // FIXME for obscured private posts, until then leave untranslated + return; + $verb = urldecode(substr($item['verb'],strpos($item['verb'],'#')+1)); if(! $verb) return; @@ -178,7 +219,7 @@ function localize_item(&$item){ $Alink = $item['author']['xchan_url']; - $obj= json_decode($item['object'],true); + $obj= json_decode_plus($item['object']); $Blink = $Bphoto = ''; @@ -188,9 +229,9 @@ function localize_item(&$item){ } $Bname = $obj['title']; - $A = '[url=' . zid($Alink) . ']' . $Aname . '[/url]'; - $B = '[url=' . zid($Blink) . ']' . $Bname . '[/url]'; - if ($Bphoto!="") $Bphoto = '[url=' . zid($Blink) . '][img=80x80]' . $Bphoto . '[/img][/url]'; + $A = '[zrl=' . chanlink_url($Alink) . ']' . $Aname . '[/zrl]'; + $B = '[zrl=' . chanlink_url($Blink) . ']' . $Bname . '[/zrl]'; + if ($Bphoto!="") $Bphoto = '[zrl=' . chanlink_url($Blink) . '][zmg=80x80]' . $Bphoto . '[/zmg][/zrl]'; // we can't have a translation string with three positions but no distinguishable text // So here is the translate string. @@ -215,9 +256,9 @@ function localize_item(&$item){ $Aname = $item['author']['xchan_name']; $Alink = $item['author']['xchan_url']; - $A = '[url=' . zid($Alink) . ']' . $Aname . '[/url]'; + $A = '[zrl=' . chanlink_url($Alink) . ']' . $Aname . '[/zrl]'; - $txt = t('%1$s is currently %2$s'); + $txt = t('%1$s is %2$s','mood'); $item['body'] = sprintf($txt, $A, t($verb)); } @@ -227,13 +268,13 @@ function localize_item(&$item){ if (activity_match($item['verb'],ACTIVITY_TAG)) { $r = q("SELECT * from `item`,`contact` WHERE - `item`.`contact-id`=`contact`.`id` AND `item`.`uri`='%s';", - dbesc($item['parent_uri'])); + `item`.`contact-id`=`contact`.`id` AND `item`.`mid`='%s';", + dbesc($item['parent_mid'])); if(count($r)==0) return; $obj=$r[0]; - $author = '[url=' . zid($item['author-link']) . ']' . $item['author-name'] . '[/url]'; - $objauthor = '[url=' . zid($obj['author-link']) . ']' . $obj['author-name'] . '[/url]'; + $author = '[zrl=' . zid($item['author-link']) . ']' . $item['author-name'] . '[/zrl]'; + $objauthor = '[zrl=' . zid($obj['author-link']) . ']' . $obj['author-name'] . '[/zrl]'; switch($obj['verb']){ case ACTIVITY_POST: @@ -248,17 +289,17 @@ function localize_item(&$item){ default: if($obj['resource_id']){ $post_type = t('photo'); - $m=array(); preg_match("/\[url=([^]]*)\]/", $obj['body'], $m); + $m=array(); preg_match("/\[[zu]rl=([^]]*)\]/", $obj['body'], $m); $rr['plink'] = $m[1]; } else { $post_type = t('status'); } } - $plink = '[url=' . $obj['plink'] . ']' . $post_type . '[/url]'; + $plink = '[zrl=' . $obj['plink'] . ']' . $post_type . '[/zrl]'; $parsedobj = parse_xml_string($xmlhead.$item['object']); - $tag = sprintf('#[url=%s]%s[/url]', $parsedobj->id, $parsedobj->content); + $tag = sprintf('#[zrl=%s]%s[/zrl]', $parsedobj->id, $parsedobj->content); $item['body'] = sprintf( t('%1$s tagged %2$s\'s %3$s with %4$s'), $author, $objauthor, $plink, $tag ); } @@ -275,7 +316,7 @@ function localize_item(&$item){ $obj = parse_xml_string($xmlhead.$item['object']); if(strlen($obj->id)) { - $r = q("select * from item where uri = '%s' and uid = %d limit 1", + $r = q("select * from item where mid = '%s' and uid = %d limit 1", dbesc($obj->id), intval($item['uid']) ); @@ -283,9 +324,9 @@ function localize_item(&$item){ $target = $r[0]; $Bname = $target['author-name']; $Blink = $target['author-link']; - $A = '[url=' . zid($Alink) . ']' . $Aname . '[/url]'; - $B = '[url=' . zid($Blink) . ']' . $Bname . '[/url]'; - $P = '[url=' . $target['plink'] . ']' . t('post/item') . '[/url]'; + $A = '[zrl=' . zid($Alink) . ']' . $Aname . '[/zrl]'; + $B = '[zrl=' . zid($Blink) . ']' . $Bname . '[/zrl]'; + $P = '[zrl=' . $target['plink'] . ']' . t('post/item') . '[/zrl]'; $item['body'] = sprintf( t('%1$s marked %2$s\'s %3$s as favorite'), $A, $B, $P)."\n"; } @@ -293,30 +334,35 @@ function localize_item(&$item){ } */ +/* $matches = null; - if(preg_match_all('/@\[url=(.*?)\]/is',$item['body'],$matches,PREG_SET_ORDER)) { - foreach($matches as $mtch) { - if(! strpos($mtch[1],'zid=')) - $item['body'] = str_replace($mtch[0],'@[url=' . zid($mtch[1]). ']',$item['body']); + if(strpos($item['body'],'[zrl') !== false) { + if(preg_match_all('/@\[zrl=(.*?)\]/is',$item['body'],$matches,PREG_SET_ORDER)) { + foreach($matches as $mtch) { + if(! strpos($mtch[1],'zid=')) + $item['body'] = str_replace($mtch[0],'@[zrl=' . zid($mtch[1]). ']',$item['body']); + } } } - // add zid's to public images - if(preg_match_all('/\[url=(.*?)\/photos\/(.*?)\/image\/(.*?)\]\[img(.*?)\]h(.*?)\[\/img\]\[\/url\]/is',$item['body'],$matches,PREG_SET_ORDER)) { - foreach($matches as $mtch) { - $item['body'] = str_replace($mtch[0],'[url=' . zid($mtch[1] . '/photos/' . $mtch[2] . '/image/' . $mtch[3] ,true) . '][img' . $mtch[4] . ']h' . $mtch[5] . '[/img][/url]',$item['body']); + if(strpos($item['body'],'[zmg') !== false) { + // add zid's to public images + if(preg_match_all('/\[zrl=(.*?)\/photos\/(.*?)\/image\/(.*?)\]\[zmg(.*?)\]h(.*?)\[\/zmg\]\[\/zrl\]/is',$item['body'],$matches,PREG_SET_ORDER)) { + foreach($matches as $mtch) { + $item['body'] = str_replace($mtch[0],'[zrl=' . zid( $mtch[1] . '/photos/' . $mtch[2] . '/image/' . $mtch[3]) . '][zmg' . $mtch[4] . ']h' . $mtch[5] . '[/zmg][/zrl]',$item['body']); + } } } - +*/ // add sparkle links to appropriate permalinks - $x = stristr($item['plink'],'/display/'); - if($x) { - $sparkle = false; - $y = best_link_url($item,$sparkle,true); - if($sparkle) - $item['plink'] = $y . '?f=&url=' . $item['plink']; - } +// $x = stristr($item['plink'],'/display/'); +// if($x) { +// $sparkle = false; +// $y = best_link_url($item,$sparkle,true); + // if($sparkle) +// $item['plink'] = $y . '?f=&url=' . $item['plink']; +// } } /** @@ -339,12 +385,13 @@ function count_descendants($item) { function visible_activity($item) { - if(activity_match($item['verb'],ACTIVITY_LIKE) || activity_match($item['verb'],ACTIVITY_DISLIKE)) + // likes can apply to other things besides posts. Check if they are post children, in which case we handle them specially + + if((activity_match($item['verb'],ACTIVITY_LIKE) || activity_match($item['verb'],ACTIVITY_DISLIKE)) && ($item['mid'] != $item['parent_mid'])) return false; return true; } - /** * "Render" a conversation or list of items for HTML display. * There are two major forms of display: @@ -356,15 +403,34 @@ function visible_activity($item) { * */ -if(!function_exists('conversation')) { -function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { + +function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional', $prepared_item = '') { $tstart = dba_timer(); + $t0 = $t1 = $t2 = $t3 = $t4 = $t5 = $t6 = null; + $content_html = ''; + $o = ''; require_once('bbcode.php'); $ssl_state = ((local_user()) ? true : false); + if(local_user()) + load_pconfig(local_user(),''); + + $arr_blocked = null; + + if(local_user()) { + $str_blocked = get_pconfig(local_user(),'system','blocked'); + if($str_blocked) { + $arr_blocked = explode(',',$str_blocked); + for($x = 0; $x < count($arr_blocked); $x ++) + $arr_blocked[$x] = trim($arr_blocked[$x]); + } + + } + + $profile_owner = 0; $page_writeable = false; $live_update_div = ''; @@ -373,6 +439,9 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { $previewing = (($preview) ? ' preview ' : ''); if($mode === 'network') { + + $t1 = dba_timer(); + $profile_owner = local_user(); $page_writeable = true; @@ -397,6 +466,7 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { . ((x($_GET,'cmin')) ? '&cmin=' . $_GET['cmin'] : '') . ((x($_GET,'cmax')) ? '&cmax=' . $_GET['cmax'] : '') . ((x($_GET,'file')) ? '&file=' . $_GET['file'] : '') + . ((x($_GET,'uri')) ? '&uri=' . $_GET['uri'] : '') . "'; var profile_page = " . $a->pager['page'] . "; </script>\r\n"; } @@ -420,23 +490,33 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { } } + } + elseif($mode === 'display') { + $profile_owner = local_user(); + $page_writeable = false; + $live_update_div = '<div id="live-display"></div>' . "\r\n"; } - elseif($mode === 'display') { + elseif($mode === 'page') { $profile_owner = $a->profile['uid']; $page_writeable = ($profile_owner == local_user()); - - $live_update_div = '<div id="live-display"></div>' . "\r\n"; - + $live_update_div = '<div id="live-page"></div>' . "\r\n"; } - else if($mode === 'search') { + + elseif($mode === 'search') { $live_update_div = '<div id="live-search"></div>' . "\r\n"; } - + elseif($mode === 'photos') { + $profile_onwer = $a->profile['profile_uid']; + $page_writeable = ($profile_owner == local_user()); + $live_update_div = '<div id="live-photos"></div>' . "\r\n"; + // for photos we've already formatted the top-level item (the photo) + $content_html = $a->data['photo_html']; + } $page_dropping = ((local_user() && local_user() == $profile_owner) ? true : false); @@ -459,9 +539,6 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { $items = $cb['items']; - $cmnt_tpl = get_markup_template('comment_item.tpl'); - $hide_comments_tpl = get_markup_template('hide_comments.tpl'); - $alike = array(); $dlike = array(); @@ -472,17 +549,31 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { $page_template = get_markup_template("conversation.tpl"); - if($items && count($items)) { + if($items) { if($mode === 'network-new' || $mode === 'search' || $mode === 'community') { // "New Item View" on network page or search page results // - just loop through the items and format them minimally for display + //$tpl = get_markup_template('search_item.tpl'); $tpl = 'search_item.tpl'; foreach($items as $item) { + + if($arr_blocked) { + $blocked = false; + foreach($arr_blocked as $b) { + if(($b) && ($item['author_xchan'] == $b)) { + $blocked = true; + break; + } + } + if($blocked) + continue; + } + $threadsid++; $comment = ''; @@ -509,17 +600,6 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { $tags=array(); $hashtags = array(); $mentions = array(); - foreach(explode(',',$item['tag']) as $tag){ - $tag = trim($tag); - if ($tag!="") { - $t = bbcode($tag); - $tags[] = $t; - if($t[0] == '#') - $hashtags[] = $t; - elseif($t[0] == '@') - $mentions[] = $t; - } - } $sp = false; $profile_link = best_link_url($item,$sp); @@ -539,10 +619,7 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { $profile_avatar = $item['author']['xchan_photo_m']; - $locate = array('location' => $item['location'], 'coord' => $item['coord'], 'html' => ''); - call_hooks('render_location',$locate); - - $location = ((strlen($locate['html'])) ? $locate['html'] : render_location_google($locate)); + $location = format_location($item); localize_item($item); if($mode === 'network-new') @@ -559,12 +636,21 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { ); $star = false; - $isstarred = "unstarred"; + $isstarred = "unstarred icon-star-empty"; - $lock = false; + $lock = (($item['item_private'] || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])) + ? t('Private Message') + : false + ); + $likebuttons = false; $shareable = false; + $verified = (($item['item_flags'] & ITEM_VERIFIED) ? t('Message is verified') : ''); + $unverified = ''; + + + $tags=array(); $terms = get_terms_oftype($item['term'],array(TERM_HASHTAG,TERM_MENTION,TERM_UNKNOWN)); if(count($terms)) @@ -577,20 +663,23 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { //$tmp_item = replace_macros($tpl,array( $tmp_item = array( 'template' => $tpl, - 'tags' => $tags, + 'toplevel' => 'toplevel_item', + 'mode' => $mode, 'id' => (($preview) ? 'P0' : $item['item_id']), 'linktitle' => sprintf( t('View %s\'s profile @ %s'), $profile_name, $profile_url), 'profile_url' => $profile_link, 'item_photo_menu' => item_photo_menu($item), - 'name' => template_escape($profile_name), + 'name' => $profile_name, 'sparkle' => $sparkle, 'lock' => $lock, 'thumb' => $profile_avatar, - 'title' => template_escape($item['title']), - 'body' => template_escape($body), - 'tags' => template_escape($tags), - 'hashtags' => template_escape($hashtags), - 'mentions' => template_escape($mentions), + 'title' => $item['title'], + 'body' => $body, + 'tags' => $tags, + 'hashtags' => $hashtags, + 'mentions' => $mentions, + 'verified' => $verified, + 'unverified' => $unverified, 'txt_cats' => t('Categories:'), 'txt_folders' => t('Filed under:'), 'has_cats' => ((count($categories)) ? 'true' : ''), @@ -598,17 +687,20 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { 'categories' => $categories, 'folders' => $folders, - 'text' => strip_tags(template_escape($body)), + 'text' => strip_tags($body), 'ago' => relative_date($item['created']), 'app' => $item['app'], 'str_app' => sprintf( t(' from %s'), $item['app']), - 'localtime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'c'), - 'location' => template_escape($location), + 'isotime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'c'), + 'localtime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'r'), + 'editedtime' => (($item['edited'] != $item['created']) ? sprintf( t('last edited: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['edited'], 'r')) : ''), + 'expiretime' => (($item['expires'] !== '0000-00-00 00:00:00') ? sprintf( t('Expires: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['expires'], 'r')):''), + 'location' => $location, 'indent' => '', - 'owner_name' => template_escape($owner_name), + 'owner_name' => $owner_name, 'owner_url' => $owner_url, 'owner_photo' => $owner_photo, - 'plink' => get_plink($item), + 'plink' => get_plink($item,false), 'edpost' => false, 'isstarred' => $isstarred, 'star' => $star, @@ -617,7 +709,7 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { 'like' => '', 'dislike' => '', 'comment' => '', - 'conv' => (($preview) ? '' : array('href'=> $a->get_baseurl($ssl_state) . '/display/' . $nickname . '/' . $item['id'], 'title'=> t('View in context'))), + 'conv' => (($preview) ? '' : array('href'=> z_root() . '/display/' . $item['mid'], 'title'=> t('View in context'))), 'previewing' => $previewing, 'wait' => t('Please wait'), 'thread_level' => 1, @@ -626,20 +718,28 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { $arr = array('item' => $item, 'output' => $tmp_item); call_hooks('display_item', $arr); - $threads[$threadsid]['id'] = $item['item_id']; - $threads[$threadsid]['items'] = array($arr['output']); +// $threads[$threadsid]['id'] = $item['item_id']; + $threads[] = $arr['output']; } } else { + // Normal View +// logger('conv: items: ' . print_r($items,true)); require_once('include/ConversationObject.php'); require_once('include/ItemObject.php'); - $conv = new Conversation($mode, $preview); + $conv = new Conversation($mode, $preview, $prepared_item); + + // In the display mode we don't have a profile owner. + + if($mode === 'display' && $items) + $conv->set_profile_owner($items[0]['uid']); + // get all the topmost parents // this shouldn't be needed, as we should have only them in our array @@ -648,39 +748,68 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { $threads = array(); foreach($items as $item) { - // Can we put this after the visibility check? + // Check for any blocked authors + + if($arr_blocked) { + $blocked = false; + foreach($arr_blocked as $b) { + if(($b) && ($item['author_xchan'] == $b)) { + $blocked = true; + break; + } + } + if($blocked) + continue; + } + + // Check all the kids too + + if($arr_blocked && $item['children']) { + for($d = 0; $d < count($item['children']); $d ++) { + foreach($arr_blocked as $b) { + if(($b) && ($item['children'][$d]['author_xchan'] == $b)) + $item['children'][$d]['author_blocked'] = true; + } + } + } + + + like_puller($a,$item,$alike,'like'); - if(feature_enabled($profile_uid,'dislike')) + if(feature_enabled($profile_owner,'dislike')) like_puller($a,$item,$dlike,'dislike'); - // Only add what is visible - if($item['network'] === NETWORK_MAIL && local_user() != $item['uid']) { - continue; - } if(! visible_activity($item)) { continue; } - - - $item['pagedrop'] = $page_dropping; - - if($item['id'] == $item['parent']) { +// $tx1 = dba_timer(); $item_object = new Item($item); $conv->add_thread($item_object); + if($page_mode === 'list') + $item_object->set_template('conv_list.tpl'); + +// $tx2 = dba_timer(); +// if($mode === 'network') +// profiler($tx1,$tx2,'add thread ' . $item['id']); } } - + $t2 = dba_timer(); $threads = $conv->get_template_data($alike, $dlike); if(!$threads) { logger('[ERROR] conversation : Failed to get template data.', LOGGER_DEBUG); $threads = array(); } - + $t3 = dba_timer(); + if($mode === 'network') { + profiler($t1,$t2,'Conversation prepare'); + profiler($t2,$t3,'Conversation get_template'); + } + } } @@ -696,8 +825,18 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { $threads = null; } - $o = replace_macros($page_template, array( + if($page_mode === 'preview') + logger('preview: ' . print_r($threads,true)); + +// Do not un-comment if smarty3 is in use +// logger('page_template: ' . $page_template); + +// logger('nouveau: ' . print_r($threads,true)); + + + $o .= replace_macros($page_template, array( '$baseurl' => $a->get_baseurl($ssl_state), + '$photo_item' => $content_html, '$live_update' => $live_update_div, '$remove' => t('remove'), '$mode' => $mode, @@ -707,10 +846,18 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { '$dropping' => ($page_dropping?t('Delete Selected Items'):False), )); + if($mode === 'network') { + $t4 = dba_timer(); + profiler($t3,$t4,'conversation template'); + } + + if($page_mode === 'preview') + logger('preview: ' . $o); + return $o; -}} +} function best_link_url($item) { @@ -743,13 +890,19 @@ function best_link_url($item) { } -if(! function_exists('item_photo_menu')){ + function item_photo_menu($item){ $a = get_app(); $contact = null; $ssl_state = false; + $sub_link=""; + $poke_link=""; + $contact_url=""; + $pm_url=""; + $vsrc_link = ""; + if(local_user()) { $ssl_state = true; if(! count($a->contacts)) @@ -758,25 +911,22 @@ function item_photo_menu($item){ $channel_hash = (($channel) ? $channel['channel_hash'] : ''); } - $sub_link=""; - $poke_link=""; - $contact_url=""; - $pm_url=""; - - if((local_user()) && local_user() == $item['uid'] && $item['parent'] == $item['id'] - && $channel && ($channel_hash != $item['author_xchan'])) { - $sub_link = 'javascript:dosubthread(' . $item['id'] . '); return false;'; + if((local_user()) && local_user() == $item['uid']) { + $vsrc_link = 'javascript:viewsrc(' . $item['id'] . '); return false;'; + if($item['parent'] == $item['id'] && $channel && ($channel_hash != $item['author_xchan'])) { + $sub_link = 'javascript:dosubthread(' . $item['id'] . '); return false;'; + } } - $profile_link = z_root() . "/chanview/?f=&hash=" . $item['author_xchan']; - $pm_url = $a->get_baseurl($ssl_state) . '/message/new/?f=&hash=' . $item['author_xchan']; + $profile_link = chanlink_hash($item['author_xchan']); + $pm_url = $a->get_baseurl($ssl_state) . '/mail/new/?f=&hash=' . $item['author_xchan']; if($a->contacts && array_key_exists($item['author_xchan'],$a->contacts)) $contact = $a->contacts[$item['author_xchan']]; if($contact) { $poke_link = $a->get_baseurl($ssl_state) . '/poke/?f=&c=' . $contact['abook_id']; - $contact_url = $a->get_baseurl($ssl_state) . '/connnections/' . $contact['abook_id']; + $contact_url = $a->get_baseurl($ssl_state) . '/connedit/' . $contact['abook_id']; $posts_link = $a->get_baseurl($ssl_state) . '/network/?cid=' . $contact['abook_id']; $clean_url = normalise_link($item['author-link']); @@ -784,11 +934,12 @@ function item_photo_menu($item){ } $menu = Array( + t("View Source") => $vsrc_link, t("Follow Thread") => $sub_link, t("View Status") => $status_link, t("View Profile") => $profile_link, t("View Photos") => $photos_link, - t("Network Posts") => $posts_link, + t("Matrix Activity") => $posts_link, t("Edit Contact") => $contact_url, t("Send PM") => $pm_url, t("Poke") => $poke_link @@ -810,9 +961,9 @@ function item_photo_menu($item){ elseif ($v!="") $o .= "<li><a href=\"$v\">$k</a></li>\n"; } return $o; -}} +} + -if(! function_exists('like_puller')) { function like_puller($a,$item,&$arr,$mode) { $url = ''; @@ -820,16 +971,10 @@ function like_puller($a,$item,&$arr,$mode) { $verb = (($mode === 'like') ? ACTIVITY_LIKE : ACTIVITY_DISLIKE); if((activity_match($item['verb'],$verb)) && ($item['id'] != $item['parent'])) { - $url = $item['author']['xchan_url']; - if((local_user()) && (local_user() == $item['uid']) && ($item['network'] === 'dfrn') && (! $item['self']) && (link_compare($item['author-link'],$item['url']))) { - $url = $a->get_baseurl(true) . '/redir/' . $item['contact-id']; - $sparkle = ' class="sparkle" '; - } - else - $url = zid($url); + $url = chanlink_url($item['author']['xchan_url']); if(! $item['thr_parent']) - $item['thr_parent'] = $item['parent_uri']; + $item['thr_parent'] = $item['parent_mid']; if(! ((isset($arr[$item['thr_parent'] . '-l'])) && (is_array($arr[$item['thr_parent'] . '-l'])))) $arr[$item['thr_parent'] . '-l'] = array(); @@ -837,10 +982,10 @@ function like_puller($a,$item,&$arr,$mode) { $arr[$item['thr_parent']] = 1; else $arr[$item['thr_parent']] ++; - $arr[$item['thr_parent'] . '-l'][] = '<a href="'. $url . '"'. $sparkle .'>' . $item['author']['xchan_name'] . '</a>'; + $arr[$item['thr_parent'] . '-l'][] = '<a href="'. $url . '">' . $item['author']['xchan_name'] . '</a>'; } return; -}} +} // Format the like/dislike text for a profile item // $cnt = number of people who like/dislike the item @@ -849,7 +994,7 @@ function like_puller($a,$item,&$arr,$mode) { // $id = item id // returns formatted text -if(! function_exists('format_like')) { + function format_like($cnt,$arr,$type,$id) { $o = ''; if($cnt == 1) @@ -857,9 +1002,9 @@ function format_like($cnt,$arr,$type,$id) { else { $spanatts = 'class="fakelink" onclick="openClose(\'' . $type . 'list-' . $id . '\');"'; $o .= (($type === 'like') ? - sprintf( t('<span %1$s>%2$d people</span> like this.'), $spanatts, $cnt) + sprintf( tt('<span %1$s>%2$d people</span> like this.','<span %1$s>%2$d people</span> like this.',$cnt), $spanatts, $cnt) : - sprintf( t('<span %1$s>%2$d people</span> don\'t like this.'), $spanatts, $cnt) ); + sprintf( tt('<span %1$s>%2$d people</span> don\'t like this.','<span %1$s>%2$d people</span> don\'t like this.',$cnt), $spanatts, $cnt) ); $o .= EOL ; $total = count($arr); if($total >= MAX_LIKERS) @@ -868,24 +1013,56 @@ function format_like($cnt,$arr,$type,$id) { $arr[count($arr)-1] = t('and') . ' ' . $arr[count($arr)-1]; $str = implode(', ', $arr); if($total >= MAX_LIKERS) - $str .= sprintf( t(', and %d other people'), $total - MAX_LIKERS ); + $str .= sprintf( tt(', and %d other people',', and %d other people',$total - MAX_LIKERS), $total - MAX_LIKERS ); $str = (($type === 'like') ? sprintf( t('%s like this.'), $str) : sprintf( t('%s don\'t like this.'), $str)); $o .= "\t" . '<div id="' . $type . 'list-' . $id . '" style="display: none;" >' . $str . '</div>'; } return $o; -}} +} function status_editor($a,$x,$popup=false) { $o = ''; - $geotag = (($x['allow_location']) ? get_markup_template('jot_geotag.tpl') : ''); + $geotag = (($x['allow_location']) ? replace_macros(get_markup_template('jot_geotag.tpl'), array()) : ''); $plaintext = true; + if(feature_enabled(local_user(),'richtext')) $plaintext = false; + $mimeselect = ''; + if(array_key_exists('mimetype',$x) && $x['mimetype']) { + if($x['mimetype'] != 'text/bbcode') + $plaintext = true; + if($x['mimetype'] === 'choose') { + $mimeselect = mimetype_select($x['profile_uid']); + } + else + $mimeselect = '<input type="hidden" name="mimetype" value="' . $x['mimetype'] . '" />'; + } + + $layoutselect = ''; + if(array_key_exists('layout',$x) && $x['layout']) { + if($x['layout'] === 'choose') { + $layoutselect = layout_select($x['profile_uid']); + } + else + $layoutselect = '<input type="hidden" name="layout_mid" value="' . $x['layout'] . '" />'; + } + + + if(array_key_exists('channel_select',$x) && $x['channel_select']) { + require_once('include/identity.php'); + $id_select = identity_selector(); + } + else + $id_select = ''; + + + $webpage = ((x($x,'webpage')) ? $x['webpage'] : ''); + $tpl = get_markup_template('jot-header.tpl'); $a->page['htmlhead'] .= replace_macros($tpl, array( @@ -900,7 +1077,8 @@ function status_editor($a,$x,$popup=false) { '$audurl' => t("Please enter an audio link/URL:"), '$term' => t('Tag term:'), '$fileas' => t('Save to Folder:'), - '$whereareu' => t('Where are you right now?') + '$whereareu' => t('Where are you right now?'), + '$expireswhen' => t('Expires YYYY-MM-DD HH:MM') )); @@ -909,13 +1087,27 @@ function status_editor($a,$x,$popup=false) { $jotplugins = ''; $jotnets = ''; + + $preview = ((feature_enabled($x['profile_uid'],'preview')) ? t('Preview') : ''); + if(x($x,'nopreview')) + $preview = ''; + + $cipher = get_pconfig($x['profile_uid'],'system','default_cipher'); + if(! $cipher) + $cipher = 'aes256'; + call_hooks('jot_tool', $jotplugins); call_hooks('jot_networks', $jotnets); $o .= replace_macros($tpl,array( - '$return_path' => $a->query_string, + '$return_path' => ((x($x,'return_path')) ? $x['return_path'] : $a->query_string), '$action' => $a->get_baseurl(true) . '/item', '$share' => (x($x,'button') ? $x['button'] : t('Share')), + '$webpage' => $webpage, + '$placeholdpagetitle' => ((x($x,'ptlabel')) ? $x['ptlabel'] : t('Page link title')), + '$pagetitle' => (x($x,'pagetitle') ? $x['pagetitle'] : ''), + '$id_select' => $id_select, + '$id_seltext' => t('Post as'), '$upload' => t('Upload photo'), '$shortupload' => t('upload photo'), '$attach' => t('Attach file'), @@ -930,31 +1122,41 @@ function status_editor($a,$x,$popup=false) { '$shortsetloc' => t('set location'), '$noloc' => t('Clear browser location'), '$shortnoloc' => t('clear location'), - '$title' => "", + '$title' => ((x($x,'title')) ? htmlspecialchars($x['title'], ENT_COMPAT,'UTF-8') : ''), '$placeholdertitle' => t('Set title'), - '$catsenabled' => ((feature_enabled($x['profile_uid'],'categories')) ? 'categories' : ''), + '$catsenabled' => ((feature_enabled($x['profile_uid'],'categories') && (! $webpage)) ? 'categories' : ''), '$category' => "", '$placeholdercategory' => t('Categories (comma-separated list)'), '$wait' => t('Please wait'), '$permset' => t('Permission settings'), '$shortpermset' => t('permissions'), '$ptyp' => (($notes_cid) ? 'note' : 'wall'), - '$content' => '', + '$content' => ((x($x,'body')) ? htmlspecialchars($x['body'], ENT_COMPAT,'UTF-8') : ''), '$post_id' => '', '$baseurl' => $a->get_baseurl(true), '$defloc' => $x['default_location'], '$visitor' => $x['visitor'], - '$pvisit' => (($notes_cid) ? 'none' : $x['visitor']), '$public' => t('Public post'), '$jotnets' => $jotnets, '$emtitle' => t('Example: bob@example.com, mary@example.com'), '$lockstate' => $x['lockstate'], '$acl' => $x['acl'], + '$mimeselect' => $mimeselect, + '$layoutselect' => $layoutselect, + '$showacl' => ((array_key_exists('showacl',$x)) ? $x['showacl'] : true), '$bang' => $x['bang'], '$profile_uid' => $x['profile_uid'], - '$preview' => ((feature_enabled($x['profile_uid'],'preview')) ? t('Preview') : ''), - '$sourceapp' => t($a->sourcename), + '$preview' => $preview, + '$source' => ((x($x,'source')) ? $x['source'] : ''), '$jotplugins' => $jotplugins, + '$defexpire' => '', + '$feature_expire' => ((feature_enabled($x['profile_uid'],'content_expire') && (! $webpage)) ? true : false), + '$expires' => t('Set expiration date'), + '$feature_encrypt' => ((feature_enabled($x['profile_uid'],'content_encrypt') && (! $webpage)) ? true : false), + '$encrypt' => t('Encrypt text'), + '$cipher' => $cipher, + '$expiryModalOK' => t('OK'), + '$expiryModalCANCEL' => t('Cancel') )); @@ -972,12 +1174,12 @@ function get_item_children($arr, $parent) { foreach($arr as $item) { if($item['id'] != $item['parent']) { if(get_config('system','thread_allow')) { - // Fallback to parent_uri if thr_parent is not set + // Fallback to parent_mid if thr_parent is not set $thr_parent = $item['thr_parent']; if($thr_parent == '') - $thr_parent = $item['parent_uri']; + $thr_parent = $item['parent_mid']; - if($thr_parent == $parent['uri']) { + if($thr_parent == $parent['mid']) { $item['children'] = get_item_children($arr, $item); $children[] = $item; } @@ -1025,25 +1227,18 @@ function conv_sort($arr,$order) { usort($parents,'sort_thr_created'); elseif(stristr($order,'commented')) usort($parents,'sort_thr_commented'); + elseif(stristr($order,'ascending')) + usort($parents,'sort_thr_created_rev'); + if(count($parents)) foreach($parents as $i=>$_x) $parents[$i]['children'] = get_item_children($arr, $_x); - /*foreach($arr as $x) { - if($x['id'] != $x['parent']) { - $p = find_thread_parent_index($parents,$x); - if($p !== false) - $parents[$p]['children'][] = $x; - } - }*/ if(count($parents)) { foreach($parents as $k => $v) { if(count($parents[$k]['children'])) { $parents[$k]['children'] = sort_item_children($parents[$k]['children']); - /*$y = $parents[$k]['children']; - usort($y,'sort_thr_created_rev'); - $parents[$k]['children'] = $y;*/ } } } @@ -1054,8 +1249,6 @@ function conv_sort($arr,$order) { $ret[] = $x; if(count($x['children'])) add_children_to_list($x['children'], $ret); - /*foreach($x['children'] as $y) - $ret[] = $y;*/ } } @@ -1082,14 +1275,301 @@ function find_thread_parent_index($arr,$x) { return false; } -function render_location_google($item) { - $location = (($item['location']) ? '<a target="map" title="' . $item['location'] . '" href="http://maps.google.com/?q=' . urlencode($item['location']) . '">' . $item['location'] . '</a>' : ''); - $coord = (($item['coord']) ? '<a target="map" title="' . $item['coord'] . '" href="http://maps.google.com/?q=' . urlencode($item['coord']) . '">' . $item['coord'] . '</a>' : ''); +function format_location($item) { + + if(strpos($item['location'],'#') === 0) { + $location = substr($item['location'],1); + $location = ((strpos($location,'[') !== false) ? bbcode($location) : $location); + } + else { + $locate = array('location' => $item['location'], 'coord' => $item['coord'], 'html' => ''); + call_hooks('render_location',$locate); + $location = ((strlen($locate['html'])) ? $locate['html'] : render_location_default($locate)); + } + return $location; +} + +function render_location_default($item) { + + $location = $item['location']; + $coord = $item['coord']; + if($coord) { if($location) - $location .= '<br /><span class="smalltext">(' . $coord . ')</span>'; + $location .= ' <span class="smalltext">(' . $coord . ')</span>'; else $location = '<span class="smalltext">' . $coord . '</span>'; } return $location; } + + + +function prepare_page($item) { + $a = get_app(); + $naked = ((get_pconfig($item['uid'],'system','nakedpage')) ? 1 : 0); + $observer = $a->get_observer(); + //240 chars is the longest we can have before we start hitting problems with suhosin sites + $preview = substr(urlencode($item['body']), 0, 240); + $link = z_root() . '/' . $a->cmd; + if(array_key_exists('webpage',$a->layout) && array_key_exists('authored',$a->layout['webpage'])) { + if($a->layout['webpage']['authored'] === 'none') + $naked = 1; + // ... other possible options + } + + // prepare_body calls unobscure() as a side effect. Do it here so that + // the template will get passed an unobscured title. + + $body = prepare_body($item,true); + + return replace_macros(get_markup_template('page_display.tpl'),array( + '$author' => (($naked) ? '' : $item['author']['xchan_name']), + '$auth_url' => (($naked) ? '' : zid($item['author']['xchan_url'])), + '$date' => (($naked) ? '' : datetime_convert('UTC',date_default_timezone_get(),$item['created'],'Y-m-d H:i')), + '$title' => smilies(bbcode($item['title'])), + '$body' => $body, + '$preview' => $preview, + '$link' => $link, + )); +} + + +function network_tabs() { + $a = get_app(); + $no_active=''; + $starred_active = ''; + $new_active = ''; + $all_active = ''; + $search_active = ''; + $conv_active = ''; + $spam_active = ''; + $postord_active = ''; + $public_active = ''; + + if(x($_GET,'new')) { + $new_active = 'active'; + } + + if(x($_GET,'search')) { + $search_active = 'active'; + } + + if(x($_GET,'star')) { + $starred_active = 'active'; + } + + if(x($_GET,'conv')) { + $conv_active = 'active'; + } + + if(x($_GET,'spam')) { + $spam_active = 'active'; + } + + if(x($_GET,'fh')) { + $public_active = 'active'; + } + + + + if (($new_active == '') + && ($starred_active == '') + && ($conv_active == '') + && ($search_active == '') + && ($spam_active == '') + && ($public_active == '')) { + $no_active = 'active'; + } + + if ($no_active=='active' && x($_GET,'order')) { + switch($_GET['order']){ + case 'post': $postord_active = 'active'; $no_active=''; break; + case 'comment' : $all_active = 'active'; $no_active=''; break; + } + } + + if ($no_active=='active') $all_active='active'; + + $cmd = $a->cmd; + + // tabs + $tabs = array(); + + if(! get_config('system','disable_discover_tab')) { + $tabs[] = array( + 'label' => t('Discover'), + 'url'=>$a->get_baseurl(true) . '/' . $cmd . '?f=&fh=1' . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : '') . ((x($_GET,'gid')) ? '&gid=' . $_GET['gid'] : ''), + 'sel'=> $public_active, + 'title'=> t('Imported public streams'), + ); + } + + $tabs[] = array( + 'label' => t('Commented Order'), + 'url'=>$a->get_baseurl(true) . '/' . $cmd . '?f=&order=comment' . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : '') . ((x($_GET,'gid')) ? '&gid=' . $_GET['gid'] : ''), + 'sel'=>$all_active, + 'title'=> t('Sort by Comment Date'), + ); + + $tabs[] = array( + 'label' => t('Posted Order'), + 'url'=>$a->get_baseurl(true) . '/' . $cmd . '?f=&order=post' . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : '') . ((x($_GET,'gid')) ? '&gid=' . $_GET['gid'] : ''), + 'sel'=>$postord_active, + 'title' => t('Sort by Post Date'), + ); + + if(feature_enabled(local_user(),'personal_tab')) { + $tabs[] = array( + 'label' => t('Personal'), + 'url' => $a->get_baseurl(true) . '/' . $cmd . '?f=' . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : '') . '&conv=1', + 'sel' => $conv_active, + 'title' => t('Posts that mention or involve you'), + ); + } + + if(feature_enabled(local_user(),'new_tab')) { + $tabs[] = array( + 'label' => t('New'), + 'url' => $a->get_baseurl(true) . '/' . $cmd . '?f=' . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : '') . '&new=1' . ((x($_GET,'gid')) ? '&gid=' . $_GET['gid'] : ''), + 'sel' => $new_active, + 'title' => t('Activity Stream - by date'), + ); + } + + if(feature_enabled(local_user(),'star_posts')) { + $tabs[] = array( + 'label' => t('Starred'), + 'url'=>$a->get_baseurl(true) . '/' . $cmd . ((x($_GET,'cid')) ? '/?f=&cid=' . $_GET['cid'] : '') . '&star=1', + 'sel'=>$starred_active, + 'title' => t('Favourite Posts'), + ); + } + // Not yet implemented + + if(feature_enabled(local_user(),'spam_filter')) { + $tabs[] = array( + 'label' => t('Spam'), + 'url'=>$a->get_baseurl(true) . '/network?f=&spam=1', + 'sel'=> $spam_active, + 'title' => t('Posts flagged as SPAM'), + ); + } + + $arr = array('tabs' => $tabs); + call_hooks('network_tabs', $arr); + + $tpl = get_markup_template('common_tabs.tpl'); + + return replace_macros($tpl,array('$tabs' => $arr['tabs'])); + +} + + + +function profile_tabs($a, $is_owner=False, $nickname=Null){ + //echo "<pre>"; var_dump($a->user); killme(); + + + $channel = $a->get_channel(); + + if (is_null($nickname)) + $nickname = $channel['channel_address']; + + $uid = (($a->profile['profile_uid']) ? $a->profile['profile_uid'] : local_user()); + + if(x($_GET,'tab')) + $tab = notags(trim($_GET['tab'])); + + $url = $a->get_baseurl() . '/channel/' . $nickname; + $pr = $a->get_baseurl() . '/profile/' . $nickname; + + $tabs = array( + array( + 'label' => t('Channel'), + 'url' => $url, + 'sel' => ((argv(0) == 'channel') ? 'active' : ''), + 'title' => t('Status Messages and Posts'), + 'id' => 'status-tab', + ), + ); + + $p = get_all_perms($uid,get_observer_hash()); + + if($p['view_profile']) { + $tabs[] = array( + 'label' => t('About'), + 'url' => $pr, + 'sel' => ((argv(0) == 'profile') ? 'active' : ''), + 'title' => t('Profile Details'), + 'id' => 'profile-tab', + ); + } + if($p['view_photos']) { + $tabs[] = array( + 'label' => t('Photos'), + 'url' => $a->get_baseurl() . '/photos/' . $nickname, + 'sel' => ((argv(0) == 'photos') ? 'active' : ''), + 'title' => t('Photo Albums'), + 'id' => 'photo-tab', + ); + } + if($p['view_storage']) { + $tabs[] = array( + 'label' => t('Files'), + 'url' => $a->get_baseurl() . '/cloud/' . $nickname . ((get_observer_hash()) ? '' : '?f=&davguest=1'), + 'sel' => ((argv(0) == 'cloud') ? 'active' : ''), + 'title' => t('Files and Storage'), + 'id' => 'files-tab', + ); + } + + require_once('include/chat.php'); + $chats = chatroom_list($uid); + if (count($chats)) { + $tabs[] = array( + 'label' => t('Chatrooms'), + 'url' => $a->get_baseurl() . '/chat/' . $nickname, + 'sel' => ((argv(0) == 'chat') ? 'active' : '' ), + 'title' => t('Chatrooms'), + 'id' => 'chat-tab', + ); + } + + if($is_owner) { + $tabs[] = array( + 'label' => t('Bookmarks'), + 'url' => $a->get_baseurl() . '/bookmarks', + 'sel' => ((argv(0) == 'bookmarks') ? 'active' : ''), + 'title' => t('Saved Bookmarks'), + 'id' => 'bookmarks-tab', + ); + } + + + if($is_owner && feature_enabled($uid,'webpages')) { + $tabs[] = array( + 'label' => t('Webpages'), + 'url' => $a->get_baseurl() . '/webpages/' . $nickname, + 'sel' => ((argv(0) == 'webpages') ? 'active' : ''), + 'title' => t('Manage Webpages'), + 'id' => 'webpages-tab', + ); + } + + else { + // FIXME + // we probably need a listing of events that were created by + // this channel and are visible to the observer + + + } + + + $arr = array('is_owner' => $is_owner, 'nickname' => $nickname, 'tab' => (($tab) ? $tab : false), 'tabs' => $tabs); + call_hooks('profile_tabs', $arr); + + $tpl = get_markup_template('common_tabs.tpl'); + + return replace_macros($tpl,array('$tabs' => $arr['tabs'])); +} diff --git a/include/cronhooks.php b/include/cronhooks.php index 9ff8141c4..a314593d2 100644 --- a/include/cronhooks.php +++ b/include/cronhooks.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ require_once('boot.php'); require_once('include/cli_startup.php'); diff --git a/include/crypto.php b/include/crypto.php index a646910a1..33cdc10c0 100644 --- a/include/crypto.php +++ b/include/crypto.php @@ -1,12 +1,11 @@ -<?php - -require_once('library/ASNValue.class.php'); -require_once('library/asn1.php'); +<?php /** @file */ function rsa_sign($data,$key,$alg = 'sha256') { if(! $key) return 'no key'; $sig = ''; + if(intval(OPENSSL_ALGO_SHA256) && $alg === 'sha256') + $alg = OPENSSL_ALGO_SHA256; openssl_sign($data,$sig,$key,$alg); return $sig; } @@ -16,168 +15,12 @@ function rsa_verify($data,$sig,$key,$alg = 'sha256') { if(! $key) return false; + if(intval(OPENSSL_ALGO_SHA256) && $alg === 'sha256') + $alg = OPENSSL_ALGO_SHA256; $verify = openssl_verify($data,$sig,$key,$alg); return $verify; } - -function DerToPem($Der, $Private=false) -{ - //Encode: - $Der = base64_encode($Der); - //Split lines: - $lines = str_split($Der, 65); - $body = implode("\n", $lines); - //Get title: - $title = $Private? 'RSA PRIVATE KEY' : 'PUBLIC KEY'; - //Add wrapping: - $result = "-----BEGIN {$title}-----\n"; - $result .= $body . "\n"; - $result .= "-----END {$title}-----\n"; - - return $result; -} - -function DerToRsa($Der) -{ - //Encode: - $Der = base64_encode($Der); - //Split lines: - $lines = str_split($Der, 64); - $body = implode("\n", $lines); - //Get title: - $title = 'RSA PUBLIC KEY'; - //Add wrapping: - $result = "-----BEGIN {$title}-----\n"; - $result .= $body . "\n"; - $result .= "-----END {$title}-----\n"; - - return $result; -} - - -function pkcs8_encode($Modulus,$PublicExponent) { - //Encode key sequence - $modulus = new ASNValue(ASNValue::TAG_INTEGER); - $modulus->SetIntBuffer($Modulus); - $publicExponent = new ASNValue(ASNValue::TAG_INTEGER); - $publicExponent->SetIntBuffer($PublicExponent); - $keySequenceItems = array($modulus, $publicExponent); - $keySequence = new ASNValue(ASNValue::TAG_SEQUENCE); - $keySequence->SetSequence($keySequenceItems); - //Encode bit string - $bitStringValue = $keySequence->Encode(); - $bitStringValue = chr(0x00) . $bitStringValue; //Add unused bits byte - $bitString = new ASNValue(ASNValue::TAG_BITSTRING); - $bitString->Value = $bitStringValue; - //Encode body - $bodyValue = "\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00" . $bitString->Encode(); - $body = new ASNValue(ASNValue::TAG_SEQUENCE); - $body->Value = $bodyValue; - //Get DER encoded public key: - $PublicDER = $body->Encode(); - return $PublicDER; -} - - -function pkcs1_encode($Modulus,$PublicExponent) { - //Encode key sequence - $modulus = new ASNValue(ASNValue::TAG_INTEGER); - $modulus->SetIntBuffer($Modulus); - $publicExponent = new ASNValue(ASNValue::TAG_INTEGER); - $publicExponent->SetIntBuffer($PublicExponent); - $keySequenceItems = array($modulus, $publicExponent); - $keySequence = new ASNValue(ASNValue::TAG_SEQUENCE); - $keySequence->SetSequence($keySequenceItems); - //Encode bit string - $bitStringValue = $keySequence->Encode(); - return $bitStringValue; -} - - -function metopem($m,$e) { - $der = pkcs8_encode($m,$e); - $key = DerToPem($der,false); - return $key; -} - - -function pubrsatome($key,&$m,&$e) { - require_once('library/asn1.php'); - - $lines = explode("\n",$key); - unset($lines[0]); - unset($lines[count($lines)]); - $x = base64_decode(implode('',$lines)); - - $r = ASN_BASE::parseASNString($x); - - $m = base64url_decode($r[0]->asnData[0]->asnData); - $e = base64url_decode($r[0]->asnData[1]->asnData); -} - - -function rsatopem($key) { - pubrsatome($key,$m,$e); - return(metopem($m,$e)); -} - -function pemtorsa($key) { - pemtome($key,$m,$e); - return(metorsa($m,$e)); -} - -function pemtome($key,&$m,&$e) { - $lines = explode("\n",$key); - unset($lines[0]); - unset($lines[count($lines)]); - $x = base64_decode(implode('',$lines)); - - $r = ASN_BASE::parseASNString($x); - - $m = base64url_decode($r[0]->asnData[1]->asnData[0]->asnData[0]->asnData); - $e = base64url_decode($r[0]->asnData[1]->asnData[0]->asnData[1]->asnData); -} - -function metorsa($m,$e) { - $der = pkcs1_encode($m,$e); - $key = DerToRsa($der); - return $key; -} - -function salmon_key($pubkey) { - pemtome($pubkey,$m,$e); - return 'RSA' . '.' . base64url_encode($m,true) . '.' . base64url_encode($e,true) ; -} - - - -if(! function_exists('aes_decrypt')) { -function aes_decrypt($val,$ky) -{ - $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; - for($a=0;$a<strlen($ky);$a++) - $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a])); - $mode = MCRYPT_MODE_ECB; - $enc = MCRYPT_RIJNDAEL_128; - $dec = @mcrypt_decrypt($enc, $key, $val, $mode, @mcrypt_create_iv( @mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM ) ); - return rtrim($dec,(( ord(substr($dec,strlen($dec)-1,1))>=0 and ord(substr($dec, strlen($dec)-1,1))<=16)? chr(ord( substr($dec,strlen($dec)-1,1))):null)); -}} - - -if(! function_exists('aes_encrypt')) { -function aes_encrypt($val,$ky) -{ - $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; - for($a=0;$a<strlen($ky);$a++) - $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a])); - $mode=MCRYPT_MODE_ECB; - $enc=MCRYPT_RIJNDAEL_128; - $val=str_pad($val, (16*(floor(strlen($val) / 16)+(strlen($val) % 16==0?2:1))), chr(16-(strlen($val) % 16))); - return mcrypt_encrypt($enc, $key, $val, $mode, mcrypt_create_iv( mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM)); -}} - - function pkcs5_pad ($text, $blocksize) { $pad = $blocksize - (strlen($text) % $blocksize); @@ -210,17 +53,41 @@ function AES256CBC_decrypt($data,$key,$iv) { str_pad($iv,16,"\0"))); } +function crypto_encapsulate($data,$pubkey,$alg='aes256cbc') { + if($alg === 'aes256cbc') + return aes_encapsulate($data,$pubkey); + +} + + function aes_encapsulate($data,$pubkey) { + if(! $pubkey) + logger('aes_encapsulate: no key. data: ' . $data); $key = random_string(32,RANDOM_STRING_TEXT); $iv = random_string(16,RANDOM_STRING_TEXT); $result['data'] = base64url_encode(AES256CBC_encrypt($data,$key,$iv),true); - openssl_public_encrypt($key,$k,$pubkey); - $result['key'] = base64url_encode($k,true); + // log the offending call so we can track it down + if(! openssl_public_encrypt($key,$k,$pubkey)) { + $x = debug_backtrace(); + logger('aes_encapsulate: RSA failed. ' . print_r($x[0],true)); + } + $result['alg'] = 'aes256cbc'; + $result['key'] = base64url_encode($k,true); openssl_public_encrypt($iv,$i,$pubkey); $result['iv'] = base64url_encode($i,true); return $result; } +function crypto_unencapsulate($data,$prvkey) { + if(! $data) + return; + $alg = ((array_key_exists('alg',$data)) ? $data['alg'] : 'aes256cbc'); + if($alg === 'aes256cbc') + return aes_unencapsulate($data,$prvkey); + +} + + function aes_unencapsulate($data,$prvkey) { openssl_private_decrypt(base64url_decode($data['key']),$k,$prvkey); openssl_private_decrypt(base64url_decode($data['iv']),$i,$prvkey); diff --git a/include/datetime.php b/include/datetime.php index a573e43a2..0214b9e4c 100644 --- a/include/datetime.php +++ b/include/datetime.php @@ -1,8 +1,8 @@ -<?php +<?php /** @file */ // two-level sort for timezones. -if(! function_exists('timezone_cmp')) { + function timezone_cmp($a, $b) { if(strstr($a,'/') && strstr($b,'/')) { if ( t($a) == t($b)) return 0; @@ -12,10 +12,10 @@ function timezone_cmp($a, $b) { if(strstr($b,'/')) return 1; if ( t($a) == t($b)) return 0; return ( t($a) < t($b)) ? -1 : 1; -}} +} // emit a timezone selector grouped (primarily) by continent -if(! function_exists('select_timezone')) { + function select_timezone($current = 'America/Los_Angeles') { $timezone_identifiers = DateTimeZone::listIdentifiers(); @@ -52,13 +52,13 @@ function select_timezone($current = 'America/Los_Angeles') { } $o .= '</optgroup></select>'; return $o; -}} +} // return a select using 'field_select_raw' template, with timezones // groupped (primarily) by continent // arguments follow convetion as other field_* template array: // 'name', 'label', $value, 'help' -if (!function_exists('field_timezone')){ + function field_timezone($name='timezone', $label='', $current = 'America/Los_Angeles', $help){ $options = select_timezone($current); $options = str_replace('<select id="timezone_select" name="timezone">','', $options); @@ -69,7 +69,7 @@ function field_timezone($name='timezone', $label='', $current = 'America/Los_Ang '$field' => array($name, $label, $current, $help, $options), )); -}} +} // General purpose date parse/convert function. // $from = source timezone @@ -77,7 +77,7 @@ function field_timezone($name='timezone', $label='', $current = 'America/Los_Ang // $s = some parseable date/time string // $fmt = output format -if(! function_exists('datetime_convert')) { + function datetime_convert($from = 'UTC', $to = 'UTC', $s = 'now', $fmt = "Y-m-d H:i:s") { // Defaults to UTC if nothing is set, but throws an exception if set to empty string. @@ -124,7 +124,7 @@ function datetime_convert($from = 'UTC', $to = 'UTC', $s = 'now', $fmt = "Y-m-d $d->setTimeZone($to_obj); return($d->format($fmt)); -}} +} // wrapper for date selector, tailored for use in birthday fields @@ -180,7 +180,7 @@ function datesel_format($f) { // $m = already selected month // $d = already selected day -if(! function_exists('datesel')) { + function datesel($f,$pre,$ymin,$ymax,$allow_blank,$y,$m,$d) { $o = ''; @@ -231,9 +231,9 @@ function datesel($f,$pre,$ymin,$ymax,$allow_blank,$y,$m,$d) { $o .= "</select>"; return $o; -}} +} + -if(! function_exists('timesel')) { function timesel($pre,$h,$m) { $o = ''; @@ -250,7 +250,7 @@ function timesel($pre,$h,$m) { $o .= "</select>"; return $o; -}} +} @@ -264,7 +264,7 @@ function timesel($pre,$h,$m) { // Results relative to current timezone // Limited to range of timestamps -if(! function_exists('relative_date')) { + function relative_date($posted_date,$format = null) { $localtime = datetime_convert('UTC',date_default_timezone_get(),$posted_date); @@ -300,7 +300,7 @@ function relative_date($posted_date,$format = null) { return sprintf( $format,$r, (($r == 1) ? $str[0] : $str[1])); } } -}} +} @@ -341,7 +341,7 @@ function age($dob,$owner_tz = '',$viewer_tz = '') { // $month[1] = 'January'; // to match human usage. -if(! function_exists('get_dim')) { + function get_dim($y,$m) { $dim = array( 0, @@ -353,7 +353,7 @@ function get_dim($y,$m) { if(((($y % 4) == 0) && (($y % 100) != 0)) || (($y % 400) == 0)) return 29; return $dim[2]; -}} +} // Returns the first day in month for a given month, year @@ -361,11 +361,11 @@ function get_dim($y,$m) { // returns 0 = Sunday through 6 = Saturday // Months start at 1. -if(! function_exists('get_first_dim')) { + function get_first_dim($y,$m) { $d = sprintf('%04d-%02d-01 00:00', intval($y), intval($m)); return datetime_convert('UTC','UTC',$d,'w'); -}} +} // output a calendar for the given month, year. // if $links are provided (array), e.g. $links[12] => 'http://mylink' , @@ -376,8 +376,6 @@ function get_first_dim($y,$m) { // TODO: provide (prev,next) links, define class variations for different size calendars - -if(! function_exists('cal')) { function cal($y = 0,$m = 0, $links = false, $class='') { @@ -442,6 +440,80 @@ function cal($y = 0,$m = 0, $links = false, $class='') { $o .= '</tr></table>'."\r\n"; return $o; -}} +} + + +/** + * Return the next birthday, converted from the owner's timezone to UTC. + * This makes it globally portable. + * If the provided birthday lacks a month and or day, return an empty string. + * A missing year is acceptable. + */ + + +function z_birthday($dob,$tz,$format="Y-m-d H:i:s") { + if(! strlen($tz)) + $tz = 'UTC'; + $birthday = ''; + $tmp_dob = substr($dob,5); + $tmp_d = substr($dob,8); + if(intval($tmp_dob) && intval($tmp_d)) { + $y = datetime_convert($tz,$tz,'now','Y'); + $bd = $y . '-' . $tmp_dob . ' 00:00'; + $t_dob = strtotime($bd); + $now = strtotime(datetime_convert($tz,$tz,'now')); + if($t_dob < $now) + $bd = $y + 1 . '-' . $tmp_dob . ' 00:00'; + $birthday = datetime_convert($tz,'UTC',$bd,$format); + } + + return $birthday; + +} + +/** + * + * Create a birthday event for any connections with a birthday in the next 1-2 weeks. + * Update the year so that we don't create another event until next year. + * + */ + + +function update_birthdays() { + + require_once('include/event.php'); + require_once('include/permissions.php'); + + $r = q("SELECT * FROM abook left join xchan on abook_xchan = xchan_hash + WHERE abook_dob > utc_timestamp() + interval 7 day and abook_dob < utc_timestamp() + interval 14 day"); + if($r) { + foreach($r as $rr) { + + if(! perm_is_allowed($rr['abook_channel'],$rr['xchan_hash'],'send_stream')) + continue; + + $ev = array(); + $ev['uid'] = $rr['abook_channel']; + $ev['account'] = $rr['abook_account']; + $ev['event_xchan'] = $rr['xchan_hash']; + $ev['start'] = datetime_convert('UTC','UTC', $rr['abook_dob']); + $ev['finish'] = datetime_convert('UTC','UTC', $rr['abook_dob'] . ' + 1 day '); + $ev['adjust'] = 1; + $ev['summary'] = sprintf( t('%1$s\'s birthday'), $rr['xchan_name']); + $ev['description'] = sprintf( t('Happy Birthday %1$s'), + '[zrl=' . $rr['xchan_url'] . ']' . $rr['xchan_name'] . '[/zrl]') ; + $ev['type'] = 'birthday'; + + $z = event_store_event($ev); + if($z) { + $item_id = event_store_item($ev,$z); + q("update abook set abook_dob = '%s' where abook_id = %d limit 1", + dbesc(intval($rr['abook_dob']) + 1 . substr($rr['abook_dob'],4)), + intval($rr['abook_id']) + ); + } + } + } +}
\ No newline at end of file diff --git a/include/dba.php b/include/dba.php deleted file mode 100644 index d1502af12..000000000 --- a/include/dba.php +++ /dev/null @@ -1,289 +0,0 @@ -<?php - -require_once('include/datetime.php'); - -/** - * - * MySQL database class - * - * For debugging, insert 'dbg(1);' anywhere in the program flow. - * dbg(0); will turn it off. Logging is performed at LOGGER_DATA level. - * When logging, all binary info is converted to text and html entities are escaped so that - * the debugging stream is safe to view within both terminals and web pages. - * - */ - -if(! class_exists('dba')) { -class dba { - - private $debug = 0; - private $db; - public $mysqli = true; - public $connected = false; - public $error = false; - - function __construct($server,$user,$pass,$db,$install = false) { - - $server = trim($server); - $user = trim($user); - $pass = trim($pass); - $db = trim($db); - - if (!(strlen($server) && strlen($user))){ - $this->connected = false; - $this->db = null; - return; - } - - if($install) { - if(strlen($server) && ($server !== 'localhost') && ($server !== '127.0.0.1')) { - if(! dns_get_record($server, DNS_A + DNS_CNAME + DNS_PTR)) { - $this->error = sprintf( t('Cannot locate DNS info for database server \'%s\''), $server); - $this->connected = false; - $this->db = null; - return; - } - } - } - - if(class_exists('mysqli')) { - $this->db = @new mysqli($server,$user,$pass,$db); - if(! mysqli_connect_errno()) { - $this->connected = true; - } - } - else { - $this->mysqli = false; - $this->db = mysql_connect($server,$user,$pass); - if($this->db && mysql_select_db($db,$this->db)) { - $this->connected = true; - } - } - if(! $this->connected) { - $this->db = null; - if(! $install) - system_unavailable(); - } - } - - public function getdb() { - return $this->db; - } - - public function q($sql) { - global $a; - - if((! $this->db) || (! $this->connected)) - return false; - - $this->error = ''; - - if(x($a->config,'system') && x($a->config['system'],'db_log')) - $stamp1 = microtime(true); - - if($this->mysqli) - $result = @$this->db->query($sql); - else - $result = @mysql_query($sql,$this->db); - - if(x($a->config,'system') && x($a->config['system'],'db_log')) { - $stamp2 = microtime(true); - $duration = round($stamp2-$stamp1, 3); - if ($duration > $a->config["system"]["db_loglimit"]) { - $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); - @file_put_contents($a->config["system"]["db_log"], $duration."\t". - basename($backtrace[1]["file"])."\t". - $backtrace[1]["line"]."\t".$backtrace[2]["function"]."\t". - substr($sql, 0, 2000)."\n", FILE_APPEND); - } - } - - if($this->mysqli) { - if($this->db->errno) - $this->error = $this->db->error; - } - elseif(mysql_errno($this->db)) - $this->error = mysql_error($this->db); - - if(strlen($this->error)) { - logger('dba: ' . $this->error); - } - - if($this->debug) { - - $mesg = ''; - - if($result === false) - $mesg = 'false'; - elseif($result === true) - $mesg = 'true'; - else { - if($this->mysqli) - $mesg = $result->num_rows . ' results' . EOL; - else - $mesg = mysql_num_rows($result) . ' results' . EOL; - } - - $str = 'SQL = ' . printable($sql) . EOL . 'SQL returned ' . $mesg - . (($this->error) ? ' error: ' . $this->error : '') - . EOL; - - logger('dba: ' . $str ); - } - - /** - * If dbfail.out exists, we will write any failed calls directly to it, - * regardless of any logging that may or may nor be in effect. - * These usually indicate SQL syntax errors that need to be resolved. - */ - - if($result === false) { - logger('dba: ' . printable($sql) . ' returned false.' . "\n" . $this->error); - if(file_exists('dbfail.out')) - file_put_contents('dbfail.out', datetime_convert() . "\n" . printable($sql) . ' returned false' . "\n" . $this->error . "\n", FILE_APPEND); - } - - if(($result === true) || ($result === false)) - return $result; - - $r = array(); - if($this->mysqli) { - if($result->num_rows) { - while($x = $result->fetch_array(MYSQLI_ASSOC)) - $r[] = $x; - $result->free_result(); - } - } - else { - if(mysql_num_rows($result)) { - while($x = mysql_fetch_array($result, MYSQL_ASSOC)) - $r[] = $x; - mysql_free_result($result); - } - } - - - if($this->debug) - logger('dba: ' . printable(print_r($r, true))); - return($r); - } - - public function dbg($dbg) { - $this->debug = $dbg; - } - - public function escape($str) { - if($this->db && $this->connected) { - if($this->mysqli) - return @$this->db->real_escape_string($str); - else - return @mysql_real_escape_string($str,$this->db); - } - } - - function __destruct() { - if ($this->db) - if($this->mysqli) - $this->db->close(); - else - mysql_close($this->db); - } -}} - -if(! function_exists('printable')) { -function printable($s) { - $s = preg_replace("~([\x01-\x08\x0E-\x0F\x10-\x1F\x7F-\xFF])~",".", $s); - $s = str_replace("\x00",'.',$s); - if(x($_SERVER,'SERVER_NAME')) - $s = escape_tags($s); - return $s; -}} - -// Procedural functions -if(! function_exists('dbg')) { -function dbg($state) { - global $db; - if($db) - $db->dbg($state); -}} - -if(! function_exists('dbesc')) { -function dbesc($str) { - global $db; - if($db && $db->connected) - return($db->escape($str)); - else - return(str_replace("'","\\'",$str)); -}} - - - -// Function: q($sql,$args); -// Description: execute SQL query with printf style args. -// Example: $r = q("SELECT * FROM `%s` WHERE `uid` = %d", -// 'user', 1); - -if(! function_exists('q')) { -function q($sql) { - - global $db; - $args = func_get_args(); - unset($args[0]); - - if($db && $db->connected) { - $stmt = vsprintf($sql,$args); - if($stmt === false) - logger('dba: vsprintf error: ' . print_r(debug_backtrace(),true)); - return $db->q($stmt); - } - - /** - * - * This will happen occasionally trying to store the - * session data after abnormal program termination - * - */ - logger('dba: no database: ' . print_r($args,true)); - return false; - -}} - -/** - * - * Raw db query, no arguments - * - */ - -if(! function_exists('dbq')) { -function dbq($sql) { - - global $db; - if($db && $db->connected) - $ret = $db->q($sql); - else - $ret = false; - return $ret; -}} - - -// Caller is responsible for ensuring that any integer arguments to -// dbesc_array are actually integers and not malformed strings containing -// SQL injection vectors. All integer array elements should be specifically -// cast to int to avoid trouble. - - -if(! function_exists('dbesc_array_cb')) { -function dbesc_array_cb(&$item, $key) { - if(is_string($item)) - $item = dbesc($item); -}} - - -if(! function_exists('dbesc_array')) { -function dbesc_array(&$arr) { - if(is_array($arr) && count($arr)) { - array_walk($arr,'dbesc_array_cb'); - } -}} - - diff --git a/include/dba/dba_driver.php b/include/dba/dba_driver.php new file mode 100755 index 000000000..c829c3714 --- /dev/null +++ b/include/dba/dba_driver.php @@ -0,0 +1,167 @@ +<?php /** @file */ + +function dba_factory($server, $port,$user,$pass,$db,$install = false) { + $dba = null; + + if(class_exists('mysqli')) { + if (is_null($port)) $port = ini_get("mysqli.default_port"); + require_once('include/dba/dba_mysqli.php'); + $dba = new dba_mysqli($server, $port,$user,$pass,$db,$install); + } + else { + if (is_null($port)) $port = "3306"; + require_once('include/dba/dba_mysql.php'); + $dba = new dba_mysql($server, $port,$user,$pass,$db,$install); + } + + return $dba; +} + + +abstract class dba_driver { + + protected $debug = 0; + protected $db; + public $connected = false; + public $error = false; + + abstract function connect($server, $port, $user,$pass,$db); + abstract function q($sql); + abstract function escape($str); + abstract function close(); + + function __construct($server, $port, $user,$pass,$db,$install = false) { + if(($install) && (! $this->install($server, $port, $user,$pass,$db))) { + return; + } + $this->connect($server, $port, $user,$pass,$db); + } + + + function install($server,$user,$pass,$db) { + if (!(strlen($server) && strlen($user))){ + $this->connected = false; + $this->db = null; + return false; + } + + if(strlen($server) && ($server !== 'localhost') && ($server !== '127.0.0.1')) { + if(! dns_get_record($server, DNS_A + DNS_CNAME + DNS_PTR)) { + $this->error = sprintf( t('Cannot locate DNS info for database server \'%s\''), $server); + $this->connected = false; + $this->db = null; + return false; + } + } + return true; + } + + + function dbg($dbg) { + $this->debug = $dbg; + } + + function __destruct() { + if($this->db && $this->connected) { + $this->close(); + } + } + +} + + + +function printable($s) { + $s = preg_replace("~([\x01-\x08\x0E-\x0F\x10-\x1F\x7F-\xFF])~",".", $s); + $s = str_replace("\x00",'.',$s); + if(x($_SERVER,'SERVER_NAME')) + $s = escape_tags($s); + return $s; +} + +// Procedural functions + +function dbg($state) { + global $db; + if($db) + $db->dbg($state); +} + + +function dbesc($str) { + global $db; + if($db && $db->connected) + return($db->escape($str)); + else + return(str_replace("'","\\'",$str)); +} + + + +// Function: q($sql,$args); +// Description: execute SQL query with printf style args. +// Example: $r = q("SELECT * FROM `%s` WHERE `uid` = %d", +// 'user', 1); + + +function q($sql) { + + global $db; + $args = func_get_args(); + unset($args[0]); + + if($db && $db->connected) { + $stmt = vsprintf($sql,$args); + if($stmt === false) + logger('dba: vsprintf error: ' . print_r(debug_backtrace(),true)); + return $db->q($stmt); + } + + /** + * + * This will happen occasionally trying to store the + * session data after abnormal program termination + * + */ + logger('dba: no database: ' . print_r($args,true)); + return false; + +} + +/** + * + * Raw db query, no arguments + * + */ + + +function dbq($sql) { + + global $db; + if($db && $db->connected) + $ret = $db->q($sql); + else + $ret = false; + return $ret; +} + + +// Caller is responsible for ensuring that any integer arguments to +// dbesc_array are actually integers and not malformed strings containing +// SQL injection vectors. All integer array elements should be specifically +// cast to int to avoid trouble. + + + +function dbesc_array_cb(&$item, $key) { + if(is_string($item)) + $item = dbesc($item); +} + + + +function dbesc_array(&$arr) { + if(is_array($arr) && count($arr)) { + array_walk($arr,'dbesc_array_cb'); + } +} diff --git a/include/dba/dba_mysql.php b/include/dba/dba_mysql.php new file mode 100755 index 000000000..f5a2a47ba --- /dev/null +++ b/include/dba/dba_mysql.php @@ -0,0 +1,63 @@ +<?php + +require_once('include/dba/dba_driver.php'); + + +class dba_mysql extends dba_driver { + + function connect($server, $port, $user,$pass,$db) { + $this->db = mysql_connect($server.":".$port,$user,$pass); + if($this->db && mysql_select_db($db,$this->db)) { + $this->connected = true; + } + if($this->connected) { + return true; + } + return false; + } + + + function q($sql) { + if((! $this->db) || (! $this->connected)) + return false; + + $this->error = ''; + $result = @mysql_query($sql,$this->db); + + + if(mysql_errno($this->db)) + $this->error = mysql_error($this->db); + + if($result === false || $this->error) { + logger('dba_mysql: ' . printable($sql) . ' returned false.' . "\n" . $this->error); + if(file_exists('dbfail.out')) + file_put_contents('dbfail.out', datetime_convert() . "\n" . printable($sql) . ' returned false' . "\n" . $this->error . "\n", FILE_APPEND); + } + + if(($result === true) || ($result === false)) + return $result; + + $r = array(); + if(mysql_num_rows($result)) { + while($x = mysql_fetch_array($result,MYSQL_ASSOC)) + $r[] = $x; + mysql_free_result($result); + if($this->debug) + logger('dba_mysql: ' . printable(print_r($r,true))); + } + return $r; + } + + function escape($str) { + if($this->db && $this->connected) { + return @mysql_real_escape_string($str,$this->db); + } + } + + function close() { + if($this->db) + mysql_close($this->db); + $this->connected = false; + } + +} diff --git a/include/dba/dba_mysqli.php b/include/dba/dba_mysqli.php new file mode 100755 index 000000000..19907705b --- /dev/null +++ b/include/dba/dba_mysqli.php @@ -0,0 +1,76 @@ +<?php /** @file */ + +require_once('include/dba/dba_driver.php'); + +class dba_mysqli extends dba_driver { + + function connect($server, $port, $user,$pass,$db) { + if($port) + $this->db = new mysqli($server,$user,$pass,$db, $port); + else + $this->db = new mysqli($server,$user,$pass,$db); + + if(! mysqli_connect_errno()) { + $this->connected = true; + } + if($this->connected) { + return true; + } + $this->error = $this->db->connect_error; + return false; + } + + function q($sql) { + if((! $this->db) || (! $this->connected)) + return false; + + $this->error = ''; + $result = $this->db->query($sql); + + if($this->db->errno) + $this->error = $this->db->error; + + + if($this->error) { + logger('dba_mysqli: ERROR: ' . printable($sql) . "\n" . $this->error); + if(file_exists('dbfail.out')) { + file_put_contents('dbfail.out', datetime_convert() . "\n" . printable($sql) . "\n" . $this->error . "\n", FILE_APPEND); + } + } + + if(($result === true) || ($result === false)) { + if($this->debug) { + logger('dba_mysqli: DEBUG: returns ' . (($result) ? 'true' : 'false')); + } + return $result; + } + + if($this->debug) { + logger('dba_mysqli: DEBUG: ' . printable($sql) . ' returned ' . $result->num_rows . ' results.'); + } + + $r = array(); + if($result->num_rows) { + while($x = $result->fetch_array(MYSQLI_ASSOC)) + $r[] = $x; + $result->free_result(); + if($this->debug) { + logger('dba_mysqli: ' . printable(print_r($r,true))); + } + } + return $r; + } + + function escape($str) { + if($this->db && $this->connected) { + return @$this->db->real_escape_string($str); + } + } + + function close() { + if($this->db) + $this->db->close(); + $this->connected = false; + } + +}
\ No newline at end of file diff --git a/include/deliver.php b/include/deliver.php index 471ea580d..b0d15e1ef 100644 --- a/include/deliver.php +++ b/include/deliver.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ require_once('include/cli_startup.php'); require_once('include/zot.php'); @@ -23,11 +23,15 @@ function deliver_run($argv, $argc) { if($r[0]['outq_posturl'] === z_root() . '/post') { // local delivery // we should probably batch these and save a few delivery processes - $msg = array('body' => json_encode(array('pickup' => array(array('notify' => json_decode($r[0]['outq_notify'],true),'message' => json_decode($r[0]['outq_msg'],true)))))); - zot_import($msg); - $r = q("delete from outq where outq_hash = '%s' limit 1", - dbesc($argv[$x]) - ); + // If there is no outq_msg, this is a refresh_all message which does not require local handling + if($r[0]['outq_msg']) { + $msg = array('body' => json_encode(array('pickup' => array(array('notify' => json_decode($r[0]['outq_notify'],true),'message' => json_decode($r[0]['outq_msg'],true)))))); + + zot_import($msg,z_root()); + $r = q("delete from outq where outq_hash = '%s' limit 1", + dbesc($argv[$x]) + ); + } } else { $result = zot_zot($r[0]['outq_posturl'],$r[0]['outq_notify']); diff --git a/include/dir_fns.php b/include/dir_fns.php index 904d27189..71800cb47 100644 --- a/include/dir_fns.php +++ b/include/dir_fns.php @@ -1,14 +1,179 @@ -<?php +<?php /** @file */ require_once('include/permissions.php'); function find_upstream_directory($dirmode) { - return ''; + global $DIRECTORY_FALLBACK_SERVERS; + + $preferred = get_config('system','directory_server'); + if(! $preferred) { + + /** + * No directory has yet been set. For most sites, pick one at random + * from our list of directory servers. However, if we're a directory + * server ourself, point at the local instance + * We will then set this value so this should only ever happen once. + * Ideally there will be an admin setting to change to a different + * directory server if you don't like our choice or if circumstances change. + */ + + $dirmode = intval(get_config('system','directory_mode')); + if($dirmode == DIRECTORY_MODE_NORMAL) { + $toss = mt_rand(0,count($DIRECTORY_FALLBACK_SERVERS)); + $preferred = $DIRECTORY_FALLBACK_SERVERS[$toss]; + set_config('system','directory_server',$preferred); + } + else{ + set_config('system','directory_server',z_root()); + } + } + return array('url' => $preferred); +} + +function dir_sort_links() { + + $o = replace_macros(get_markup_template('dir_sort_links.tpl'), array( + '$header' => t('Sort Options'), + '$normal' => t('Alphabetic'), + '$reverse' => t('Reverse Alphabetic'), + '$date' => t('Newest to Oldest') + )); + return $o; +} + +function dir_safe_mode() { + $observer = get_observer_hash(); + if (! $observer) + return; + if ($observer) + $safe_mode = get_xconfig($observer,'directory','safe_mode'); + if($safe_mode === '0') + $toggle = t('Enable Safe Search'); + else + $toggle = t('Disable Safe Search'); + $o = replace_macros(get_markup_template('safesearch.tpl'), array( + '$safemode' => t('Safe Mode'), + '$toggle' => $toggle, + )); + + return $o; +} + +function sync_directories($dirmode) { + + if($dirmode == DIRECTORY_MODE_STANDALONE || $dirmode == DIRECTORY_MODE_NORMAL) + return; + + $r = q("select * from site where (site_flags & %d) and site_url != '%s'", + intval(DIRECTORY_MODE_PRIMARY|DIRECTORY_MODE_SECONDARY), + dbesc(z_root()) + ); + + // If there are no directory servers, setup the fallback master + + if((! $r) && (z_root() != DIRECTORY_FALLBACK_MASTER)) { + $r = array( + 'site_url' => DIRECTORY_FALLBACK_MASTER, + 'site_flags' => DIRECTORY_MODE_PRIMARY, + 'site_update' => '0000-00-00 00:00:00', + 'site_directory' => DIRECTORY_FALLBACK_MASTER . '/dirsearch' + ); + $x = q("insert into site ( site_url, site_flags, site_update, site_directory ) + values ( '%s', %d', '%s', '%s' ) ", + dbesc($r[0]['site_url']), + intval($r[0]['site_flags']), + dbesc($r[0]['site_update']), + dbesc($r[0]['site_directory']) + ); + + $r = q("select * from site where (site_flags & %d) and site_url != '%s'", + intval(DIRECTORY_MODE_PRIMARY|DIRECTORY_MODE_SECONDARY), + dbesc(z_root()) + ); + + } + if(! $r) + return; + + foreach($r as $rr) { + if(! $rr['site_directory']) + continue; + $x = z_fetch_url($rr['site_directory'] . '?f=&sync=' . urlencode($rr['site_sync'])); + if(! $x['success']) + continue; + $j = json_decode($x['body'],true); + if((! $j['transactions']) || (! is_array($j['transactions']))) + continue; + + q("update site set site_sync = '%s' where site_url = '%s' limit 1", + dbesc(datetime_convert()), + dbesc($rr['site_url']) + ); + + logger('sync_directories: ' . $rr['site_url'] . ': ' . print_r($j,true), LOGGER_DATA); + + if(count($j['transactions'])) { + foreach($j['transactions'] as $t) { + $r = q("select * from updates where ud_guid = '%s' limit 1", + dbesc($t['transaction_id']) + ); + if($r) + continue; + $ud_flags = 0; + if(is_array($t['flags']) && in_array('deleted',$t['flags'])) + $ud_flags |= UPDATE_FLAGS_DELETED; + if(is_array($t['flags']) && in_array('forced',$t['flags'])) + $ud_flags |= UPDATE_FLAGS_FORCED; + + $z = q("insert into updates ( ud_hash, ud_guid, ud_date, ud_flags, ud_addr ) + values ( '%s', '%s', '%s', %d, '%s' ) ", + dbesc($t['hash']), + dbesc($t['transaction_id']), + dbesc($t['timestamp']), + intval($ud_flags), + dbesc($t['address']) + ); + } + } + } +} + + +function update_directory_entry($ud) { + + logger('update_directory_entry: ' . print_r($ud,true), LOGGER_DATA); + + if($ud['ud_addr'] && (! ($ud['ud_flags'] & UPDATE_FLAGS_DELETED))) { + $success = false; + $x = zot_finger($ud['ud_addr'],''); + if($x['success']) { + $j = json_decode($x['body'],true); + if($j) + $success = true; + $y = import_xchan($j,0); + } + if(! $success) { + $r = q("update updates set ud_last = '%s' where ud_addr = '%s'", + dbesc(datetime_convert()), + dbesc($ud['ud_addr']) + ); + } + } + } -function syncdirs($uid) { - $p = q("select channel.channel_hash, profile.* from profile left join channel on channel_id = uid where uid = %d and is_default = 1", +/** + * @function local_dir_update($uid,$force) + * push local channel updates to a local directory server + * + */ + +function local_dir_update($uid,$force) { + + logger('local_dir_update', LOGGER_DEBUG); + + $p = q("select channel.channel_hash, channel_address, channel_timezone, profile.* from profile left join channel on channel_id = uid where uid = %d and is_default = 1", intval($uid) ); @@ -19,6 +184,9 @@ function syncdirs($uid) { $profile['description'] = $p[0]['pdesc']; $profile['birthday'] = $p[0]['dob']; + if($age = age($p[0]['dob'],$p[0]['channel_timezone'],'')) + $profile['age'] = $age; + $profile['gender'] = $p[0]['gender']; $profile['marital'] = $p[0]['marital']; $profile['sexual'] = $p[0]['sexual']; @@ -26,6 +194,10 @@ function syncdirs($uid) { $profile['region'] = $p[0]['region']; $profile['postcode'] = $p[0]['postal_code']; $profile['country'] = $p[0]['country_name']; + $profile['about'] = $p[0]['about']; + $profile['homepage'] = $p[0]['homepage']; + $profile['hometown'] = $p[0]['hometown']; + if($p[0]['keywords']) { $tags = array(); $k = explode(' ',$p[0]['keywords']); @@ -37,9 +209,33 @@ function syncdirs($uid) { $profile['keywords'] = $tags; } - if(perm_is_allowed($uid,'','view_profile')) { - import_directory_profile($hash,$profile); + $hidden = (1 - intval($p[0]['publish'])); + + logger('hidden: ' . $hidden); + $r = q("select xchan_flags from xchan where xchan_hash = '%s' limit 1", + dbesc($p[0]['channel_hash']) + ); + + // Be careful - XCHAN_FLAGS_HIDDEN should evaluate to 1 + if(($r[0]['xchan_flags'] & XCHAN_FLAGS_HIDDEN) != $hidden) + $new_flags = $r[0]['xchan_flags'] ^ XCHAN_FLAGS_HIDDEN; + else + $new_flags = $r[0]['xchan_flags']; + + if($new_flags != $r[0]['xchan_flags']) { + + $r = q("update xchan set xchan_flags = %d where xchan_hash = '%s' limit 1", + intval($new_flags), + dbesc($p[0]['channel_hash']) + ); + + } + + $address = $p[0]['channel_address'] . '@' . get_app()->get_hostname(); + + if(perm_is_allowed($uid,'','view_profile')) { + import_directory_profile($hash,$profile,$address,0); } else { // they may have made it private @@ -52,6 +248,8 @@ function syncdirs($uid) { } } - // TODO send refresh zots to downstream directory servers + $ud_hash = random_string() . '@' . get_app()->get_hostname(); + update_modtime($hash,$ud_hash,$p[0]['channel_address'] . '@' . get_app()->get_hostname(),(($force) ? UPDATE_FLAGS_FORCED : UPDATE_FLAGS_UPDATED)); + } diff --git a/include/directory.php b/include/directory.php index d1862cb57..60070f7ec 100644 --- a/include/directory.php +++ b/include/directory.php @@ -1,4 +1,5 @@ -g<?php +<?php /** @file */ + require_once('boot.php'); require_once('include/zot.php'); require_once('include/cli_startup.php'); @@ -9,18 +10,25 @@ function directory_run($argv, $argc){ cli_startup(); - if($argc != 2) + if($argc < 2) return; + $force = false; + $pushall = true; + + if($argc > 2) { + if($argv[2] === 'force') + $force = true; + if($argv[2] === 'nopush') + $pushall = false; + } + + logger('directory update', LOGGER_DEBUG); + $dirmode = get_config('system','directory_mode'); if($dirmode === false) $dirmode = DIRECTORY_MODE_NORMAL; - if(($dirmode == DIRECTORY_MODE_PRIMARY) || ($dirmode == DIRECTORY_MODE_STANDALONE)) { - syncdirs($argv[1]); - return; - } - $x = q("select * from channel where channel_id = %d limit 1", intval($argv[1]) ); @@ -29,26 +37,64 @@ function directory_run($argv, $argc){ $channel = $x[0]; - // is channel profile visible to the public? - // FIXME - remove dir entry if permission is revoked - if(! perm_is_allowed($channel['channel_id'],null,'view_profile')) + if(($dirmode == DIRECTORY_MODE_PRIMARY) || ($dirmode == DIRECTORY_MODE_STANDALONE)) { + + local_dir_update($argv[1],$force); + + q("update channel set channel_dirdate = '%s' where channel_id = %d limit 1", + dbesc(datetime_convert()), + intval($channel['channel_id']) + ); + + // Now update all the connections + if($pushall) + proc_run('php','include/notifier.php','refresh_all',$channel['channel_id']); + return; + } $directory = find_upstream_directory($dirmode); + $url = $directory['url'] . '/post'; - if($directory) { - $url = $directory['url']; - } - else { - $url = DIRECTORY_FALLBACK_MASTER . '/post'; - } + // ensure the upstream directory is updated - $packet = zot_build_packet($channel,'refresh'); + $packet = zot_build_packet($channel,(($force) ? 'force_refresh' : 'refresh')); $z = zot_zot($url,$packet); // re-queue if unsuccessful + if(! $z['success']) { + + // FIXME - we aren't updating channel_dirdate if we have to queue + // the directory packet. That means we'll try again on the next poll run. + + $hash = random_string(); + q("insert into outq ( outq_hash, outq_account, outq_channel, outq_driver, outq_posturl, outq_async, outq_created, outq_updated, outq_notify, outq_msg ) + values ( '%s', %d, %d, '%s', '%s', %d, '%s', '%s', '%s', '%s' )", + dbesc($hash), + intval($channel['channel_account_id']), + intval($channel['channel_id']), + dbesc('zot'), + dbesc($url), + intval(1), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc($packet), + dbesc('') + ); + } + else { + q("update channel set channel_dirdate = '%s' where channel_id = %d limit 1", + dbesc(datetime_convert()), + intval($channel['channel_id']) + ); + } + + // Now update all the connections + if($pushall) + proc_run('php','include/notifier.php','refresh_all',$channel['channel_id']); + } if (array_search(__file__,get_included_files())===0){ diff --git a/include/enotify.php b/include/enotify.php index 5780d1836..8baf5c09f 100644 --- a/include/enotify.php +++ b/include/enotify.php @@ -1,21 +1,48 @@ -<?php +<?php /** @file */ function notification($params) { logger('notification: entry', LOGGER_DEBUG); + // throw a small amount of entropy into the system to breakup duplicates arriving at the same precise instant. + usleep(mt_rand(0,10000)); + + $a = get_app(); - // from here on everything is in the recipients language - push_lang($params['language']); + if($params['from_xchan']) { + $x = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($params['from_xchan']) + ); + } + if($params['to_xchan']) { + $y = q("select channel.*, account.* from channel left join account on channel_account_id = account_id + where channel_hash = '%s' and not (channel_pageflags & %d) limit 1", + dbesc($params['to_xchan']), + intval(PAGE_REMOVED) + ); + } + if($x & $y) { + $sender = $x[0]; + $recip = $y[0]; + } + else { + logger('notification: no sender or recipient.'); + logger('sender: ' . $params['from_xchan']); + logger('recip: ' . $params['to_xchan']); + return; + } + + // from here on everything is in the recipients language + push_lang($recip['account_language']); // should probably have a channel language - $banner = t('Red Notification'); - $product = FRIENDICA_PLATFORM; - $siteurl = $a->get_baseurl(true); - $thanks = t('Thank You,'); - $sitename = get_config('config','sitename'); + $banner = t('Red Matrix Notification'); + $product = t('redmatrix'); // RED_PLATFORM; + $siteurl = $a->get_baseurl(true); + $thanks = t('Thank You,'); + $sitename = get_config('system','sitename'); $site_admin = sprintf( t('%s Administrator'), $sitename); $sender_name = $product; @@ -23,47 +50,71 @@ function notification($params) { if(strpos($hostname,':')) $hostname = substr($hostname,0,strpos($hostname,':')); - $sender_email = t('noreply') . '@' . $hostname; + // Do not translate 'noreply' as it must be a legal 7-bit email address + $sender_email = 'noreply' . '@' . $hostname; + $additional_mail_header = ""; if(array_key_exists('item',$params)) { - $title = $params['item']['title']; - $body = $params['item']['body']; + require_once('include/conversation.php'); + // if it's a normal item... + if(array_key_exists('verb',$params['item'])) { + // localize_item() alters the original item so make a copy first + $i = $params['item']; + logger('calling localize'); + localize_item($i); + $title = $i['title']; + $body = $i['body']; + $private = $i['item_private']; + } + else { + $title = $params['item']['title']; + $body = $params['item']['body']; + } } else { $title = $body = ''; } + // e.g. "your post", "David's photo", etc. $possess_desc = t('%s <!item_type!>'); if($params['type'] == NOTIFY_MAIL) { - + logger('notification: mail'); $subject = sprintf( t('[Red:Notify] New mail received at %s'),$sitename); - $preamble = sprintf( t('%1$s sent you a new private message at %2$s.'),$params['source_name'],$sitename); - $epreamble = sprintf( t('%1$s sent you %2$s.'),'[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]', '[url=$itemlink]' . t('a private message') . '[/url]'); + $preamble = sprintf( t('%1$s, %2$s sent you a new private message at %3$s.'),$recip['channel_name'], $sender['xchan_name'],$sitename); + $epreamble = sprintf( t('%1$s sent you %2$s.'),'[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', '[zrl=$itemlink]' . t('a private message') . '[/zrl]'); $sitelink = t('Please visit %s to view and/or reply to your private messages.'); - $tsitelink = sprintf( $sitelink, $siteurl . '/message/' . $params['item']['id'] ); - $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '/message/' . $params['item']['id'] . '">' . $sitename . '</a>'); - $itemlink = $siteurl . '/message/' . $params['item']['id']; + $tsitelink = sprintf( $sitelink, $siteurl . '/mail/' . $params['item']['id'] ); + $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '/mail/' . $params['item']['id'] . '">' . $sitename . '</a>'); + $itemlink = $siteurl . '/mail/' . $params['item']['id']; } if($params['type'] == NOTIFY_COMMENT) { // logger("notification: params = " . print_r($params, true), LOGGER_DEBUG); - $parent_id = $params['parent']; + $itemlink = $params['link']; + - // Check to see if there was already a tag notify for this post. + // ignore like/unlike activity on posts - they probably require a sepearate notification preference + + if(array_key_exists('item',$params) && (! visible_activity($params['item']))) + return; + + $parent_mid = $params['parent_mid']; + + // Check to see if there was already a notify for this post. // If so don't create a second notification $p = null; - $p = q("select id from notify where type = %d and link = '%s' and uid = %d limit 1", - intval(NOTIFY_TAGSELF), + $p = q("select id from notify where link = '%s' and uid = %d limit 1", dbesc($params['link']), - intval($params['uid']) + intval($recip['channel_id']) ); - if($p and count($p)) { + if($p) { + logger('notification: comment already notified'); pop_lang(); return; } @@ -73,60 +124,69 @@ function notification($params) { $p = null; - if($params['otype'] === 'item' && $parent_id) { - $p = q("select * from item where id = %d and uid = %d limit 1", - intval($parent_id), - intval($params['uid']) + if($params['otype'] === 'item' && $parent_mid) { + $p = q("select * from item where mid = '%s' and uid = %d limit 1", + dbesc($parent_mid), + intval($recip['channel_id']) ); } + xchan_query($p); + + $item_post_type = item_post_type($p[0]); + $private = $p[0]['item_private']; + $parent_id = $p[0]['id']; + //$possess_desc = str_replace('<!item_type!>',$possess_desc); // "a post" - $dest_str = sprintf(t('%1$s commented on [url=%2$s]a %3$s[/url]'), - '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]', - $itemlink, - $item_post_type); + $dest_str = sprintf(t('%1$s, %2$s commented on [zrl=%3$s]a %4$s[/zrl]'), + $recip['channel_name'], + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', + $itemlink, + $item_post_type); // "George Bull's post" if($p) - $dest_str = sprintf(t('%1$s commented on [url=%2$s]%3$s\'s %4$s[/url]'), - '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]', - $itemlink, - $p[0]['author-name'], - $item_post_type); + $dest_str = sprintf(t('%1$s, %2$s commented on [zrl=%3$s]%4$s\'s %5$s[/zrl]'), + $recip['channel_name'], + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', + $itemlink, + $p[0]['author']['xchan_name'], + $item_post_type); // "your post" - if($p[0]['owner-name'] == $p[0]['author-name'] && $p[0]['wall']) - $dest_str = sprintf(t('%1$s commented on [url=%2$s]your %3$s[/url]'), - '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]', - $itemlink, - $item_post_type); + if($p[0]['owner']['xchan_name'] == $p[0]['author']['xchan_name'] && ($p[0]['item_flags'] & ITEM_WALL)) + $dest_str = sprintf(t('%1$s, %2$s commented on [zrl=%3$s]your %4$s[/zrl]'), + $recip['channel_name'], + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', + $itemlink, + $item_post_type); // Some mail softwares relies on subject field for threading. // So, we cannot have different subjects for notifications of the same thread. // Before this we have the name of the replier on the subject rendering // differents subjects for messages on the same thread. - $subject = sprintf( t('[Red:Notify] Comment to conversation #%1$d by %2$s'), $parent_id, $params['source_name']); - $preamble = sprintf( t('%s commented on an item/conversation you have been following.'), $params['source_name']); + $subject = sprintf( t('[Red:Notify] Comment to conversation #%1$d by %2$s'), $parent_id, $sender['xchan_name']); + $preamble = sprintf( t('%1$s, %2$s commented on an item/conversation you have been following.'), $recip['channel_name'], $sender['xchan_name']); $epreamble = $dest_str; $sitelink = t('Please visit %s to view and/or reply to the conversation.'); $tsitelink = sprintf( $sitelink, $siteurl ); $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '">' . $sitename . '</a>'); - $itemlink = $params['link']; } if($params['type'] == NOTIFY_WALL) { - $subject = sprintf( t('[Red:Notify] %s posted to your profile wall') , $params['source_name']); + $subject = sprintf( t('[Red:Notify] %s posted to your profile wall') , $sender['xchan_name']); - $preamble = sprintf( t('%1$s posted to your profile wall at %2$s') , $params['source_name'], $sitename); + $preamble = sprintf( t('%1$s, %2$s posted to your profile wall at %3$s') , $recip['channel_name'], $sender['xchan_name'], $sitename); - $epreamble = sprintf( t('%1$s posted to [url=%2$s]your wall[/url]') , - '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]', - $params['link']); + $epreamble = sprintf( t('%1$s, %2$s posted to [zrl=%3$s]your wall[/zrl]') , + $recip['channel_name'], + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', + $params['link']); $sitelink = t('Please visit %s to view and/or reply to the conversation.'); $tsitelink = sprintf( $sitelink, $siteurl ); @@ -135,11 +195,24 @@ function notification($params) { } if($params['type'] == NOTIFY_TAGSELF) { - $subject = sprintf( t('[Red:Notify] %s tagged you') , $params['source_name']); - $preamble = sprintf( t('%1$s tagged you at %2$s') , $params['source_name'], $sitename); - $epreamble = sprintf( t('%1$s [url=%2$s]tagged you[/url].') , - '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]', - $params['link']); + + $p = null; + $p = q("select id from notify where link = '%s' and uid = %d limit 1", + dbesc($params['link']), + intval($recip['channel_id']) + ); + if($p) { + logger('enotify: tag: already notified about this post'); + pop_lang(); + return; + } + + $subject = sprintf( t('[Red:Notify] %s tagged you') , $sender['xchan_name']); + $preamble = sprintf( t('%1$s, %2$s tagged you at %3$s') , $recip['channel_name'], $sender['xchan_name'], $sitename); + $epreamble = sprintf( t('%1$s, %2$s [zrl=%3$s]tagged you[/zrl].') , + $recip['channel_name'], + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', + $params['link']); $sitelink = t('Please visit %s to view and/or reply to the conversation.'); $tsitelink = sprintf( $sitelink, $siteurl ); @@ -149,11 +222,12 @@ function notification($params) { if($params['type'] == NOTIFY_POKE) { - $subject = sprintf( t('[Red:Notify] %1$s poked you') , $params['source_name']); - $preamble = sprintf( t('%1$s poked you at %2$s') , $params['source_name'], $sitename); - $epreamble = sprintf( t('%1$s [url=%2$s]poked you[/url].') , - '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]', - $params['link']); + $subject = sprintf( t('[Red:Notify] %1$s poked you') , $sender['xchan_name']); + $preamble = sprintf( t('%1$s, %2$s poked you at %3$s') , $recip['channel_name'], $sender['xchan_name'], $sitename); + $epreamble = sprintf( t('%1$s, %2$s [zrl=%2$s]poked you[/zrl].') , + $recip['channel_name'], + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', + $params['link']); $subject = str_replace('poked', t($params['activity']), $subject); $preamble = str_replace('poked', t($params['activity']), $preamble); @@ -166,11 +240,12 @@ function notification($params) { } if($params['type'] == NOTIFY_TAGSHARE) { - $subject = sprintf( t('[Red:Notify] %s tagged your post') , $params['source_name']); - $preamble = sprintf( t('%1$s tagged your post at %2$s') , $params['source_name'], $sitename); - $epreamble = sprintf( t('%1$s tagged [url=%2$s]your post[/url]') , - '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]', - $itemlink); + $subject = sprintf( t('[Red:Notify] %s tagged your post') , $sender['xchan_name']); + $preamble = sprintf( t('%1$s, %2$s tagged your post at %3$s') , $recip['channel_name'],$sender['xchan_name'], $sitename); + $epreamble = sprintf( t('%1$s, %2$s tagged [zrl=%3$s]your post[/zrl]') , + $recip['channel_name'], + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', + $itemlink); $sitelink = t('Please visit %s to view and/or reply to the conversation.'); $tsitelink = sprintf( $sitelink, $siteurl ); @@ -180,13 +255,14 @@ function notification($params) { if($params['type'] == NOTIFY_INTRO) { $subject = sprintf( t('[Red:Notify] Introduction received')); - $preamble = sprintf( t('You\'ve received an introduction from \'%1$s\' at %2$s'), $params['source_name'], $sitename); - $epreamble = sprintf( t('You\'ve received [url=%1$s]an introduction[/url] from %2$s.'), - $itemlink, - '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]'); - $body = sprintf( t('You may visit their profile at %s'),$params['source_link']); - - $sitelink = t('Please visit %s to approve or reject the introduction.'); + $preamble = sprintf( t('%1$s, you\'ve received an new connection request from \'%2$s\' at %3$s'), $recip['channel_name'], $sender['xchan_name'], $sitename); + $epreamble = sprintf( t('%1$s, you\'ve received [zrl=%2$s]a new connection request[/zrl] from %3$s.'), + $recip['channel_name'], + $itemlink, + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]'); + $body = sprintf( t('You may visit their profile at %s'),$sender['xchan_url']); + + $sitelink = t('Please visit %s to approve or reject the connection request.'); $tsitelink = sprintf( $sitelink, $siteurl ); $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '">' . $sitename . '</a>'); $itemlink = $params['link']; @@ -194,11 +270,12 @@ function notification($params) { if($params['type'] == NOTIFY_SUGGEST) { $subject = sprintf( t('[Red:Notify] Friend suggestion received')); - $preamble = sprintf( t('You\'ve received a friend suggestion from \'%1$s\' at %2$s'), $params['source_name'], $sitename); - $epreamble = sprintf( t('You\'ve received [url=%1$s]a friend suggestion[/url] for %2$s from %3$s.'), - $itemlink, - '[url=' . $params['item']['url'] . ']' . $params['item']['name'] . '[/url]', - '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]'); + $preamble = sprintf( t('%1$s, you\'ve received a friend suggestion from \'%2$s\' at %3$s'), $recip['channel_name'], $sender['xchan_name'], $sitename); + $epreamble = sprintf( t('%1$s, you\'ve received [zrl=%2$s]a friend suggestion[/zrl] for %3$s from %4$s.'), + $recip['channel_name'], + $itemlink, + '[zrl=' . $params['item']['url'] . ']' . $params['item']['name'] . '[/zrl]', + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]'); $body = t('Name:') . ' ' . $params['item']['name'] . "\n"; $body .= t('Photo:') . ' ' . $params['item']['photo'] . "\n"; @@ -255,32 +332,43 @@ function notification($params) { $datarray = array(); - $datarray['hash'] = $hash; - $datarray['name'] = $params['source_name']; - $datarray['url'] = $params['source_link']; - $datarray['photo'] = $params['source_photo']; - $datarray['date'] = datetime_convert(); - $datarray['uid'] = $params['uid']; - $datarray['link'] = $itemlink; - $datarray['parent'] = $parent_id; - $datarray['type'] = $params['type']; - $datarray['verb'] = $params['verb']; - $datarray['otype'] = $params['otype']; + $datarray['hash'] = $hash; + $datarray['name'] = $sender['xchan_name']; + $datarray['url'] = $sender['xchan_url']; + $datarray['photo'] = $sender['xchan_photo_s']; + $datarray['date'] = datetime_convert(); + $datarray['aid'] = $recip['channel_account_id']; + $datarray['uid'] = $recip['channel_id']; + $datarray['link'] = $itemlink; + $datarray['parent'] = $parent_mid; + $datarray['type'] = $params['type']; + $datarray['verb'] = $params['verb']; + $datarray['otype'] = $params['otype']; + $datarray['abort'] = false; + + $datarray['item'] = $params['item']; call_hooks('enotify_store', $datarray); + if($datarray['abort']) { + pop_lang(); + return; + } + + // create notification entry in DB - $r = q("insert into notify (hash,name,url,photo,date,uid,link,parent,type,verb,otype) - values('%s','%s','%s','%s','%s',%d,'%s',%d,%d,'%s','%s')", + $r = q("insert into notify (hash,name,url,photo,date,aid,uid,link,parent,type,verb,otype) + values('%s','%s','%s','%s','%s',%d,%d,'%s','%s',%d,'%s','%s')", dbesc($datarray['hash']), dbesc($datarray['name']), dbesc($datarray['url']), dbesc($datarray['photo']), dbesc($datarray['date']), + intval($datarray['aid']), intval($datarray['uid']), dbesc($datarray['link']), - intval($datarray['parent']), + dbesc($datarray['parent']), intval($datarray['type']), dbesc($datarray['verb']), dbesc($datarray['otype']) @@ -288,98 +376,107 @@ function notification($params) { $r = q("select id from notify where hash = '%s' and uid = %d limit 1", dbesc($hash), - intval($params['uid']) + intval($recip['channel_id']) ); if($r) $notify_id = $r[0]['id']; else { + logger('notification not found.'); pop_lang(); return; } $itemlink = $a->get_baseurl() . '/notify/view/' . $notify_id; - $msg = replace_macros($epreamble,array('$itemlink' => $itemlink)); + $msg = str_replace('$itemlink',$itemlink,$epreamble); + + // wretched hack, but we don't want to duplicate all the preamble variations and we also don't want to screw up a translation + + if(($a->language === 'en' || (! $a->language)) && strpos($msg,', ')) + $msg = substr($msg,strpos($msg,', ')+1); + $r = q("update notify set msg = '%s' where id = %d and uid = %d limit 1", dbesc($msg), intval($notify_id), - intval($params['uid']) + intval($datarray['uid']) ); // send email notification if notification preferences permit require_once('bbcode.php'); - if((intval($params['notify_flags']) & intval($params['type'])) || $params['type'] == NOTIFY_SYSTEM) { + if((intval($recip['channel_notifyflags']) & intval($params['type'])) || $params['type'] == NOTIFY_SYSTEM) { logger('notification: sending notification email'); - $id_for_parent = "${params['parent']}@${hostname}"; - // Is this the first email notification for this parent item and user? - - $r = q("select `id` from `notify-threads` where `master-parent-item` = %d and `receiver-uid` = %d limit 1", - intval($params['parent']), - intval($params['uid']) ); - - // If so, create the record of it and use a message-id smtp header. - - if(!$r) { - logger("notify_id:" . intval($notify_id). ", parent: " . intval($params['parent']) . "uid: " . -intval($params['uid']), LOGGER_DEBUG); - $r = q("insert into `notify-threads` (`notify-id`, `master-parent-item`, `receiver-uid`, `parent-item`) - values(%d,%d,%d,%d)", - intval($notify_id), - intval($params['parent']), - intval($params['uid']), - 0 ); - - $additional_mail_header .= "Message-ID: <${id_for_parent}>\n"; - $log_msg = "include/enotify: No previous notification found for this parent:\n" . - " parent: ${params['parent']}\n" . " uid : ${params['uid']}\n"; - logger($log_msg, LOGGER_DEBUG); - } + $textversion = strip_tags(html_entity_decode(bbcode(stripslashes(str_replace(array("\\r", "\\n"), array( "", "\n"), $body))),ENT_QUOTES,'UTF-8')); - // If not, just "follow" the thread. + $htmlversion = html_entity_decode(bbcode(stripslashes(str_replace(array("\\r","\\n"), array("","<br />\n"),$body))), ENT_QUOTES,'UTF-8'); - else { - $additional_mail_header = "References: <${id_for_parent}>\nIn-Reply-To: <${id_for_parent}>\n"; - logger("include/enotify: There's already a notification for this parent:\n" . print_r($r, true), LOGGER_DEBUG); - } + // use $_SESSION['zid_override'] to force zid() to use + // the recipient address instead of the current observer + + $_SESSION['zid_override'] = $recip['channel_address'] . '@' . get_app()->get_hostname(); + $_SESSION['zrl_override'] = z_root() . '/channel/' . $recip['channel_address']; + + $textversion = zidify_links($textversion); + $htmlversion = zidify_links($htmlversion); + + // unset when done to revert to normal behaviour + unset($_SESSION['zid_override']); + unset($_SESSION['zrl_override']); - $textversion = strip_tags(html_entity_decode(bbcode(stripslashes(str_replace(array("\\r\\n", "\\r", "\\n"), "\n", - $body))),ENT_QUOTES,'UTF-8')); - $htmlversion = html_entity_decode(bbcode(stripslashes(str_replace(array("\\r\\n", "\\r","\\n\\n" ,"\\n"), - "<br />\n",$body)))); $datarray = array(); - $datarray['banner'] = $banner; - $datarray['product'] = $product; - $datarray['preamble'] = $preamble; - $datarray['sitename'] = $sitename; - $datarray['siteurl'] = $siteurl; - $datarray['type'] = $params['type']; - $datarray['parent'] = $params['parent']; - $datarray['source_name'] = $params['source_name']; - $datarray['source_link'] = $params['source_link']; - $datarray['source_photo'] = $params['source_photo']; - $datarray['uid'] = $params['uid']; - $datarray['username'] = $params['to_name']; - $datarray['hsitelink'] = $hsitelink; - $datarray['tsitelink'] = $tsitelink; - $datarray['hitemlink'] = '<a href="' . $itemlink . '">' . $itemlink . '</a>'; - $datarray['titemlink'] = $itemlink; - $datarray['thanks'] = $thanks; - $datarray['site_admin'] = $site_admin; - $datarray['title'] = stripslashes($title); - $datarray['htmlversion'] = $htmlversion; - $datarray['textversion'] = $textversion; - $datarray['subject'] = $subject; - $datarray['headers'] = $additional_mail_header; + $datarray['banner'] = $banner; + $datarray['product'] = $product; + $datarray['preamble'] = $preamble; + $datarray['sitename'] = $sitename; + $datarray['siteurl'] = $siteurl; + $datarray['type'] = $params['type']; + $datarray['parent'] = $params['parent_mid']; + $datarray['source_name'] = $sender['xchan_name']; + $datarray['source_link'] = $sender['xchan_url']; + $datarray['source_photo'] = $sender['xchan_photo_s']; + $datarray['uid'] = $recip['channel_id']; + $datarray['username'] = $recip['channel_name']; + $datarray['hsitelink'] = $hsitelink; + $datarray['tsitelink'] = $tsitelink; + $datarray['hitemlink'] = '<a href="' . $itemlink . '">' . $itemlink . '</a>'; + $datarray['titemlink'] = $itemlink; + $datarray['thanks'] = $thanks; + $datarray['site_admin'] = $site_admin; + $datarray['title'] = stripslashes($title); + $datarray['htmlversion'] = $htmlversion; + $datarray['textversion'] = $textversion; + $datarray['subject'] = $subject; + $datarray['headers'] = $additional_mail_header; + $datarray['email_secure'] = false; call_hooks('enotify_mail', $datarray); + // Default to private - don't disclose message contents over insecure channels (such as email) + // Might be interesting to use GPG,PGP,S/MIME encryption instead + // but we'll save that for a clever plugin developer to implement + + if(! $datarray['email_secure']) { + switch($params['type']) { + case NOTIFY_WALL: + case NOTIFY_TAGSELF: + case NOTIFY_POKE: + case NOTIFY_COMMENT: + if(! $private) + break; + case NOTIFY_MAIL: + $datarray['textversion'] = $datarray['htmlversion'] = $datarray['title'] = ''; + break; + default: + break; + } + } + // load the template for private message notifications $tpl = get_markup_template('email_notify_html.tpl'); $email_html_body = replace_macros($tpl,array( @@ -425,13 +522,13 @@ intval($params['uid']), LOGGER_DEBUG); // use the EmailNotification library to send the message enotify::send(array( - 'fromName' => $sender_name, - 'fromEmail' => $sender_email, - 'replyTo' => $sender_email, - 'toEmail' => $params['to_email'], - 'messageSubject' => $datarray['subject'], - 'htmlVersion' => $email_html_body, - 'textVersion' => $email_text_body, + 'fromName' => $sender_name, + 'fromEmail' => $sender_email, + 'replyTo' => $sender_email, + 'toEmail' => $recip['account_email'], + 'messageSubject' => $datarray['subject'], + 'htmlVersion' => $email_html_body, + 'textVersion' => $email_text_body, 'additionalMailHeader' => $datarray['headers'], )); } @@ -489,7 +586,7 @@ class enotify { // send the message $res = mail( - $params['toEmail'], // send to address + $params['toEmail'], // send to address $messageSubject, // subject $multipartMessageBody, // message body $messageHeader // message headers @@ -497,4 +594,4 @@ class enotify { logger("notification: enotify::send returns " . $res, LOGGER_DEBUG); } } -?> + diff --git a/include/event.php b/include/event.php index e4a01cf7d..e198fe15c 100644 --- a/include/event.php +++ b/include/event.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ function format_event_html($ev) { @@ -8,14 +8,14 @@ function format_event_html($ev) { if(! ((is_array($ev)) && count($ev))) return ''; - $bd_format = t('l F d, Y \@ g:i A') ; // Friday January 18, 2011 @ 8 AM + $bd_format = t('l F d, Y \@ g:i A') ; // Friday January 18, 2011 @ 8:01 AM $o = '<div class="vevent">' . "\r\n"; $o .= '<p class="summary event-summary">' . bbcode($ev['summary']) . '</p>' . "\r\n"; - $o .= '<p class="description event-description">' . bbcode($ev['desc']) . '</p>' . "\r\n"; + $o .= '<p class="description event-description">' . bbcode($ev['description']) . '</p>' . "\r\n"; $o .= '<p class="event-start">' . t('Starts:') . ' <abbr class="dtstart" title="' . datetime_convert('UTC','UTC',$ev['start'], (($ev['adjust']) ? ATOM_TIME : 'Y-m-d\TH:i:s' )) @@ -45,74 +45,6 @@ function format_event_html($ev) { return $o; } -/* -function parse_event($h) { - - require_once('include/Scrape.php'); - require_once('library/HTMLPurifier.auto.php'); - require_once('include/html2bbcode'); - - $h = '<html><body>' . $h . '</body></html>'; - - $ret = array(); - - - try { - $dom = HTML5_Parser::parse($h); - } catch (DOMException $e) { - logger('parse_event: parse error: ' . $e); - } - - if(! $dom) - return $ret; - - $items = $dom->getElementsByTagName('*'); - - foreach($items as $item) { - if(attribute_contains($item->getAttribute('class'), 'vevent')) { - $level2 = $item->getElementsByTagName('*'); - foreach($level2 as $x) { - if(attribute_contains($x->getAttribute('class'),'dtstart') && $x->getAttribute('title')) { - $ret['start'] = $x->getAttribute('title'); - if(! strpos($ret['start'],'Z')) - $ret['adjust'] = true; - } - if(attribute_contains($x->getAttribute('class'),'dtend') && $x->getAttribute('title')) - $ret['finish'] = $x->getAttribute('title'); - - if(attribute_contains($x->getAttribute('class'),'description')) - $ret['desc'] = $x->textContent; - if(attribute_contains($x->getAttribute('class'),'location')) - $ret['location'] = $x->textContent; - } - } - } - - // sanitise - - if((x($ret,'desc')) && ((strpos($ret['desc'],'<') !== false) || (strpos($ret['desc'],'>') !== false))) { - $config = HTMLPurifier_Config::createDefault(); - $config->set('Cache.DefinitionImpl', null); - $purifier = new HTMLPurifier($config); - $ret['desc'] = html2bbcode($purifier->purify($ret['desc'])); - } - - if((x($ret,'location')) && ((strpos($ret['location'],'<') !== false) || (strpos($ret['location'],'>') !== false))) { - $config = HTMLPurifier_Config::createDefault(); - $config->set('Cache.DefinitionImpl', null); - $purifier = new HTMLPurifier($config); - $ret['location'] = html2bbcode($purifier->purify($ret['location'])); - } - - if(x($ret,'start')) - $ret['start'] = datetime_convert('UTC','UTC',$ret['start']); - if(x($ret,'finish')) - $ret['finish'] = datetime_convert('UTC','UTC',$ret['finish']); - - return $ret; -} -*/ - function format_event_bbcode($ev) { $o = ''; @@ -120,8 +52,8 @@ function format_event_bbcode($ev) { if($ev['summary']) $o .= '[event-summary]' . $ev['summary'] . '[/event-summary]'; - if($ev['desc']) - $o .= '[event-description]' . $ev['desc'] . '[/event-description]'; + if($ev['description']) + $o .= '[event-description]' . $ev['description'] . '[/event-description]'; if($ev['start']) $o .= '[event-start]' . $ev['start'] . '[/event-start]'; @@ -143,7 +75,7 @@ function format_event_bbcode($ev) { function bbtovcal($s) { $o = ''; $ev = bbtoevent($s); - if($ev['desc']) + if($ev['description']) $o = format_event_html($ev); return $o; } @@ -158,7 +90,7 @@ function bbtoevent($s) { $ev['summary'] = $match[1]; $match = ''; if(preg_match("/\[event\-description\](.*?)\[\/event\-description\]/is",$s,$match)) - $ev['desc'] = $match[1]; + $ev['description'] = $match[1]; $match = ''; if(preg_match("/\[event\-start\](.*?)\[\/event\-start\]/is",$s,$match)) $ev['start'] = $match[1]; @@ -177,10 +109,10 @@ function bbtoevent($s) { } -function sort_by_date($a) { - - usort($a,'ev_compare'); - return $a; +function sort_by_date($arr) { + if(is_array($arr)) + usort($arr,'ev_compare'); + return $arr; } @@ -190,63 +122,51 @@ function ev_compare($a,$b) { $date_b = (($b['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$b['start']) : $b['start']); if($date_a === $date_b) - return strcasecmp($a['desc'],$b['desc']); + return strcasecmp($a['description'],$b['description']); return strcmp($date_a,$date_b); } +function event_store_event($arr) { -function event_store($arr) { - - require_once('include/datetime.php'); - require_once('include/items.php'); - require_once('include/bbcode.php'); - - $a = get_app(); - - $arr['created'] = (($arr['created']) ? $arr['created'] : datetime_convert()); - $arr['edited'] = (($arr['edited']) ? $arr['edited'] : datetime_convert()); - $arr['type'] = (($arr['type']) ? $arr['type'] : 'event' ); - $arr['cid'] = ((intval($arr['cid'])) ? intval($arr['cid']) : 0); - $arr['message_id'] = (x($arr,'message_id') ? $arr['message_id'] : get_message_id()); - $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0); - - if($arr['cid']) - $c = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1", - intval($arr['cid']), - intval($arr['uid']) - ); - else - $c = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1", - intval($arr['uid']) - ); + $arr['created'] = (($arr['created']) ? $arr['created'] : datetime_convert()); + $arr['edited'] = (($arr['edited']) ? $arr['edited'] : datetime_convert()); + $arr['type'] = (($arr['type']) ? $arr['type'] : 'event' ); + $arr['event_xchan'] = (($arr['event_xchan']) ? $arr['event_xchan'] : ''); - if(count($c)) - $contact = $c[0]; // Existing event being modified - if($arr['id']) { + if($arr['id'] || $arr['event_hash']) { // has the event actually changed? - $r = q("SELECT * FROM `event` WHERE `id` = %d AND `uid` = %d LIMIT 1", - intval($arr['id']), - intval($arr['uid']) - ); - if((! count($r)) || ($r[0]['edited'] === $arr['edited'])) { - - // Nothing has changed. Grab the item id to return. - - $r = q("SELECT * FROM `item` WHERE `event-id` = %d AND `uid` = %d LIMIT 1", + if($arr['event_hash']) { + $r = q("SELECT * FROM event WHERE event_hash = '%s' AND uid = %d LIMIT 1", + dbesc($arr['event_hash']), + intval($arr['uid']) + ); + } + else { + $r = q("SELECT * FROM event WHERE id = %d AND uid = %d LIMIT 1", intval($arr['id']), intval($arr['uid']) ); - return((count($r)) ? $r[0]['id'] : 0); } + + if(! $r) + return false; + + if($r[0]['edited'] === $arr['edited']) { + // Nothing has changed. Return the ID. + return $r[0]; + } + + $event_hash = $r[0]['event_hash']; + // The event changed. Update it. $r = q("UPDATE `event` SET @@ -254,7 +174,7 @@ function event_store($arr) { `start` = '%s', `finish` = '%s', `summary` = '%s', - `desc` = '%s', + `description` = '%s', `location` = '%s', `type` = '%s', `adjust` = %d, @@ -269,7 +189,7 @@ function event_store($arr) { dbesc($arr['start']), dbesc($arr['finish']), dbesc($arr['summary']), - dbesc($arr['desc']), + dbesc($arr['description']), dbesc($arr['location']), dbesc($arr['type']), intval($arr['adjust']), @@ -278,58 +198,30 @@ function event_store($arr) { dbesc($arr['allow_gid']), dbesc($arr['deny_cid']), dbesc($arr['deny_gid']), - intval($arr['id']), - intval($arr['uid']) - ); - $r = q("SELECT * FROM `item` WHERE `event-id` = %d AND `uid` = %d LIMIT 1", - intval($arr['id']), + intval($r[0]['id']), intval($arr['uid']) ); - if(count($r)) { - $object = '<object><type>' . xmlify(ACTIVITY_OBJ_EVENT) . '</type><title></title><id>' . xmlify($arr['message_id']) . '</id>'; - $object .= '<content>' . xmlify(format_event_bbcode($arr)) . '</content>'; - $object .= '</object>' . "\n"; - - - q("UPDATE `item` SET `body` = '%s', `object` = '%s', `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s', `edited` = '%s', `private` = %d WHERE `id` = %d AND `uid` = %d LIMIT 1", - dbesc(format_event_bbcode($arr)), - dbesc($object), - dbesc($arr['allow_cid']), - dbesc($arr['allow_gid']), - dbesc($arr['deny_cid']), - dbesc($arr['deny_gid']), - dbesc($arr['edited']), - intval($arr['private']), - intval($r[0]['id']), - intval($arr['uid']) - ); - - $item_id = $r[0]['id']; - } - else - $item_id = 0; - - call_hooks("event_updated", $arr['id']); - - return $item_id; } else { // New event. Store it. - $r = q("INSERT INTO `event` ( `uid`,`account`,`cid`,`message_id`,`created`,`edited`,`start`,`finish`,`summary`, `desc`,`location`,`type`, - `adjust`,`nofinish`,`allow_cid`,`allow_gid`,`deny_cid`,`deny_gid`) - VALUES ( %d, %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s' ) ", - intval($arr['account']), + $hash = random_string(); + + + $r = q("INSERT INTO event ( uid,aid,event_xchan,event_hash,created,edited,start,finish,summary,description,location,type, + adjust,nofinish,allow_cid,allow_gid,deny_cid,deny_gid) + VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s' ) ", intval($arr['uid']), - intval($arr['cid']), - dbesc($arr['uri']), + intval($arr['account']), + dbesc($arr['event_xchan']), + dbesc($hash), dbesc($arr['created']), dbesc($arr['edited']), dbesc($arr['start']), dbesc($arr['finish']), dbesc($arr['summary']), - dbesc($arr['desc']), + dbesc($arr['description']), dbesc($arr['location']), dbesc($arr['type']), intval($arr['adjust']), @@ -340,65 +232,232 @@ function event_store($arr) { dbesc($arr['deny_gid']) ); + } + + $r = q("SELECT * FROM event WHERE event_hash = '%s' AND uid = %d LIMIT 1", + dbesc($hash), + intval($arr['uid']) + ); + if($r) + return $r[0]; + + return false; + +} + +function event_addtocal($item_id, $uid) { + + $c = q("select * from channel where channel_id = %d limit 1", + intval($uid) + ); - $r = q("SELECT * FROM `event` WHERE `hash` = '%s' AND `uid` = %d LIMIT 1", - dbesc($arr['hash']), + if(! $c) + return false; + + $channel = $c[0]; + + $r = q("select * from item where id = %d and uid = %d limit 1", + intval($item_id), + intval($channel['channel_id']) + ); + + if((! $r) || ($r[0]['obj_type'] !== ACTIVITY_OBJ_EVENT)) + return false; + + $item = $r[0]; + + $ev = bbtoevent($r[0]['body']); + + if(x($ev,'summary') && x($ev,'start')) { + $ev['event_xchan'] = $item['author_xchan']; + $ev['uid'] = $channel['channel_id']; + $ev['account'] = $channel['channel_account_id']; + $ev['edited'] = $item['edited']; + $ev['mid'] = $item['mid']; + $ev['private'] = $item['item_private']; + + // is this an edit? + + if($item['resource_type'] === 'event') { + $ev['event_hash'] = $item['resource_id']; + } + + $event = event_store_event($ev); + if($event) { + $r = q("update item set resource_id = '%s', resource_type = 'event' where id = %d and uid = %d limit 1", + dbesc($event['event_hash']), + intval($item['id']), + intval($channel['channel_id']) + ); + return true; + } + } + return false; +} + + + +function event_store_item($arr,$event) { + + require_once('include/datetime.php'); + require_once('include/items.php'); + require_once('include/bbcode.php'); + + $a = get_app(); + + $item = null; + + if($arr['mid'] && $arr['uid']) { + $i = q("select * from item where mid = '%s' and uid = %d limit 1", + dbesc($arr['mid']), + intval($arr['uid']) + ); + if($i) { + xchan_query($i); + $item = fetch_post_tags($i,true); + } + } + + $item_arr = array(); + $prefix = ''; + $birthday = false; + + if($event['type'] === 'birthday') { + $prefix = t('This event has been added to your calendar.'); + $birthday = true; + + // The event is created on your own site by the system, but appears to belong + // to the birthday person. It also isn't propagated - so we need to prevent + // folks from trying to comment on it. If you're looking at this and trying to + // fix it, you'll need to completely change the way birthday events are created + // and send them out from the source. This has its own issues. + + $item_arr['comment_policy'] = 'none'; + } + + $r = q("SELECT * FROM item left join xchan on author_xchan = xchan_hash WHERE resource_id = '%s' AND resource_type = 'event' and uid = %d LIMIT 1", + dbesc($event['event_hash']), + intval($arr['uid']) + ); + + if($r) { + $obj = json_encode(array( + 'type' => ACTIVITY_OBJ_EVENT, + 'id' => z_root() . '/event/' . $r[0]['resource_id'], + 'title' => $arr['summary'], + 'content' => format_event_bbcode($arr), + 'author' => array( + 'name' => $r[0]['xchan_name'], + 'address' => $r[0]['xchan_addr'], + 'guid' => $r[0]['xchan_guid'], + 'guid_sig' => $r[0]['xchan_guid_sig'], + 'link' => array( + array('rel' => 'alternate', 'type' => 'text/html', 'href' => $r[0]['xchan_url']), + array('rel' => 'photo', 'type' => $r[0]['xchan_photo_mimetype'], 'href' => $r[0]['xchan_photo_m'])), + ), + )); + + $private = (($arr['allow_cid'] || $arr['allow_gid'] || $arr['deny_cid'] || $arr['deny_gid']) ? 1 : 0); + + q("UPDATE item SET title = '%s', body = '%s', object = '%s', allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s', edited = '%s', item_flags = %d, item_private = %d WHERE id = %d AND uid = %d LIMIT 1", + dbesc($arr['summary']), + dbesc($prefix . format_event_bbcode($arr)), + dbesc($object), + dbesc($arr['allow_cid']), + dbesc($arr['allow_gid']), + dbesc($arr['deny_cid']), + dbesc($arr['deny_gid']), + dbesc($arr['edited']), + intval($r[0]['item_flags']), + intval($private), + intval($r[0]['id']), intval($arr['uid']) ); - if(count($r)) - $event = $r[0]; - $item_arr = array(); + $item_id = $r[0]['id']; + call_hooks('event_updated', $event['id']); + return $item_id; + } + else { + + $z = q("select * from channel where channel_hash = '%s' and channel_id = %d limit 1", + dbesc($event['event_xchan']), + intval($arr['uid']) + ); + $private = (($arr['allow_cid'] || $arr['allow_gid'] || $arr['deny_cid'] || $arr['deny_gid']) ? 1 : 0); + + + if($item) { + $item_arr['id'] = $item['id']; + } + else { + $wall = (($z) ? true : false); + + $item_flags = ITEM_THREAD_TOP; + if($wall) { + $item_flags |= ITEM_WALL; + $item_flags |= ITEM_ORIGIN; + } + $item_arr['item_flags'] = $item_flags; + + } + + if(! $arr['mid']) + $arr['mid'] = item_message_id(); + + $item_arr['aid'] = $z[0]['channel_account_id']; $item_arr['uid'] = $arr['uid']; - $item_arr['contact-id'] = $arr['cid']; - $item_arr['uri'] = $arr['message_id']; - $item_arr['parent_uri'] = $arr['message_id']; - $item_arr['type'] = 'activity'; - $item_arr['wall'] = (($arr['cid']) ? 0 : 1); - $item_arr['contact-id'] = $contact['id']; - $item_arr['owner-name'] = $contact['name']; - $item_arr['owner-link'] = $contact['url']; - $item_arr['owner-avatar'] = $contact['thumb']; - $item_arr['author-name'] = $contact['name']; - $item_arr['author-link'] = $contact['url']; - $item_arr['author-avatar'] = $contact['thumb']; - $item_arr['title'] = ''; + $item_arr['author_xchan'] = $arr['event_xchan']; + $item_arr['mid'] = $arr['mid']; + $item_arr['parent_mid'] = $arr['mid']; + + + $item_arr['owner_xchan'] = (($wall) ? $z[0]['channel_hash'] : $arr['event_xchan']); + $item_arr['author_xchan'] = $arr['event_xchan']; + $item_arr['title'] = $arr['summary']; $item_arr['allow_cid'] = $arr['allow_cid']; $item_arr['allow_gid'] = $arr['allow_gid']; $item_arr['deny_cid'] = $arr['deny_cid']; $item_arr['deny_gid'] = $arr['deny_gid']; - $item_arr['private'] = $arr['private']; - $item_arr['last-child'] = 1; - $item_arr['visible'] = 1; + $item_arr['item_private'] = $private; $item_arr['verb'] = ACTIVITY_POST; - $item_arr['obj_type'] = ACTIVITY_OBJ_EVENT; - $item_arr['origin'] = ((intval($arr['cid']) == 0) ? 1 : 0); - $item_arr['body'] = format_event_bbcode($event); + $item_arr['resource_type'] = 'event'; + $item_arr['resource_id'] = $event['event_hash']; - $item_arr['object'] = '<object><type>' . xmlify(ACTIVITY_OBJ_EVENT) . '</type><title></title><id>' . xmlify($arr['message_id']) . '</id>'; - $item_arr['object'] .= '<content>' . xmlify(format_event_bbcode($event)) . '</content>'; - $item_arr['object'] .= '</object>' . "\n"; + $item_arr['obj_type'] = ACTIVITY_OBJ_EVENT; - $item_id = item_store($item_arr); + $item_arr['body'] = $prefix . format_event_bbcode($arr); - $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", - intval($arr['uid']) + $item_arr['plink'] = z_root() . '/channel/' . $z[0]['channel_address'] . '/?f=&mid=' . $item_arr['mid']; + + $x = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($arr['event_xchan']) ); - if(count($r)) - $plink = $a->get_baseurl() . '/display/' . $r[0]['nickname'] . '/' . $item_id; + if($x) { + + $item_arr['object'] = json_encode(array( + 'type' => ACTIVITY_OBJ_EVENT, + 'id' => z_root() . '/event/' . $hash, + 'title' => $arr['summary'], + 'content' => format_event_bbcode($arr), + 'author' => array( + 'name' => $x[0]['xchan_name'], + 'address' => $x[0]['xchan_addr'], + 'guid' => $x[0]['xchan_guid'], + 'guid_sig' => $x[0]['xchan_guid_sig'], + 'link' => array( + array('rel' => 'alternate', 'type' => 'text/html', 'href' => $x[0]['xchan_url']), + array('rel' => 'photo', 'type' => $x[0]['xchan_photo_mimetype'], 'href' => $x[0]['xchan_photo_m'])), + ), + )); + } + $res = item_store($item_arr); - if($item_id) { - q("UPDATE `item` SET `plink` = '%s', `event-id` = %d WHERE `uid` = %d AND `id` = %d LIMIT 1", - dbesc($plink), - intval($event['id']), - intval($arr['uid']), - intval($item_id) - ); - } + $item_id = $res['item_id']; call_hooks("event_created", $event['id']); diff --git a/include/expire.php b/include/expire.php index 3a914a41d..442914a39 100644 --- a/include/expire.php +++ b/include/expire.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ require_once('boot.php'); require_once('include/cli_startup.php'); @@ -7,10 +7,20 @@ function expire_run($argv, $argc){ cli_startup(); + $r = q("select id from item where (item_restrict & %d) and not (item_restrict & %d) and changed < UTC_TIMESTAMP() - INTERVAL 10 DAY", + intval(ITEM_DELETED), + intval(ITEM_PENDING_REMOVE) + ); + if($r) { + foreach($r as $rr) { + drop_item($rr['id'],false,DROPITEM_PHASE2); + } + } + // physically remove anything that has been deleted for more than two months - $r = q("delete from item where item_flags & %d and changed < UTC_TIMESTAMP() - INTERVAL 60 DAY", - intval(ITEM_DELETED) + $r = q("delete from item where ( item_restrict & %d ) and changed < UTC_TIMESTAMP() - INTERVAL 36 DAY", + intval(ITEM_PENDING_REMOVE) ); // make this optional as it could have a performance impact on large sites @@ -29,6 +39,21 @@ function expire_run($argv, $argc){ } } + + $x = get_sys_channel(); + if($x) { + + // this should probably just fetch the channel_expire_days from the sys channel, + // but there's no convenient way to set it. + + $expire_days = get_config('externals','expire_days'); + if($expire_days === false) + $expire_days = 30; + if($expire_days) + item_expire($x['channel_id'],$expire_days); + } + + return; } diff --git a/include/externals.php b/include/externals.php new file mode 100644 index 000000000..1d9fd2902 --- /dev/null +++ b/include/externals.php @@ -0,0 +1,98 @@ +<?php /** @file */ + +require_once('boot.php'); +require_once('include/cli_startup.php'); +require_once('include/zot.php'); +require_once('include/identity.php'); + +function externals_run($argv, $argc){ + + cli_startup(); + $a = get_app(); + + + $total = 0; + $attempts = 0; + + // pull in some public posts + + + while($total == 0 && $attempts < 3) { + $arr = array('url' => ''); + call_hooks('externals_url_select',$arr); + + if($arr['url']) { + $url = $arr['url']; + } + else { + $r = q("select site_url, site_pull from site where site_url != '%s' and site_flags != %d order by rand() limit 1", + dbesc(z_root()), + intval(DIRECTORY_MODE_STANDALONE) + ); + if($r) + $url = $r[0]['site_url']; + } + + $attempts ++; + + if($url) { + if($r[0]['site_pull'] !== '0000-00-00 00:00:00') + $mindate = urlencode($r[0]['site_pull']); + else { + $days = get_config('externals','since_days'); + if($days === false) + $days = 15; + $mindate = urlencode(datetime_convert('','','now - ' . intval($days) . ' days')); + } + + $feedurl = $url . '/zotfeed?f=&mindate=' . $mindate; + + logger('externals: pulling public content from ' . $feedurl, LOGGER_DEBUG); + + $x = z_fetch_url($feedurl); + if(($x) && ($x['success'])) { + + q("update site set site_pull = '%s' where site_url = '%s' limit 1", + dbesc(datetime_convert()), + dbesc($url) + ); + + $j = json_decode($x['body'],true); + if($j['success'] && $j['messages']) { + $sys = get_sys_channel(); + foreach($j['messages'] as $message) { + $results = process_delivery(array('hash' => 'undefined'), get_item_elements($message), + array(array('hash' => $sys['xchan_hash'])), false, true); + $total ++; +// $z = q("select id from item where mid = '%s' and uid = %d limit 1", +// dbesc($message['message_id']), +// intval($sys['channel_id']) +// ); +$z = null; + if($z) { + $flag_bits = ITEM_WALL|ITEM_ORIGIN|ITEM_UPLINK; + // preserve the source + + $r = q("update item set source_xchan = owner_xchan where id = %d limit 1", + intval($z[0]['id']) + ); + + $r = q("update item set item_flags = ( item_flags | %d ), owner_xchan = '%s' + where id = %d limit 1", + intval($flag_bits), + dbesc($sys['xchan_hash']), + intval($z[0]['id']) + ); + } + } + logger('externals: import_public_posts: ' . $total . ' messages imported', LOGGER_DEBUG); + } + } + } + } +} + +if (array_search(__file__,get_included_files())===0){ + externals_run($argv,$argc); + killme(); +} diff --git a/include/fcontact.php b/include/fcontact.php deleted file mode 100644 index 8821a985f..000000000 --- a/include/fcontact.php +++ /dev/null @@ -1,41 +0,0 @@ -<?php - - - -function fcontact_store($url,$name,$photo) { - - $nurl = str_replace(array('https:','//www.'), array('http:','//'), $url); - - $r = q("SELECT `id` FROM `fcontact` WHERE `url` = '%s' LIMIT 1", - dbesc($nurl) - ); - - if(count($r)) - return $r[0]['id']; - - $r = q("INSERT INTO `fcontact` ( `url`, `name`, `photo` ) VALUES ( '%s', '%s', '%s' ) ", - dbesc($nurl), - dbesc($name), - dbesc($photo) - ); - - if($r) { - $r = q("SELECT `id` FROM `fcontact` WHERE `url` = '%s' LIMIT 1", - dbesc($nurl) - ); - if(count($r)) - return $r[0]['id']; - } - - return 0; -} - -function ffinder_store($uid,$cid,$fid) { - $r = q("INSERT INTO `ffinder` ( `uid`, `cid`, `fid` ) VALUES ( %d, %d, %d ) ", - intval($uid), - intval($cid), - intval($fid) - ); - return $r; -} - diff --git a/include/features.php b/include/features.php index 738de429f..cc8d457bc 100644 --- a/include/features.php +++ b/include/features.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ /* * Features management @@ -7,6 +7,8 @@ function feature_enabled($uid,$feature) { $x = get_pconfig($uid,'feature',$feature); + if($x === false) + $x = get_config('feature',$feature); $arr = array('uid' => $uid, 'feature' => $feature, 'enabled' => $x); call_hooks('feature_enabled',$arr); return($arr['enabled']); @@ -19,10 +21,18 @@ function get_features() { // General 'general' => array( t('General Features'), - array('expire', t('Content Expiration'), t('Remove old posts/comments after a period of time')), +// This is per post, and different from fixed expiration 'expire' which isn't working yet + array('content_expire', t('Content Expiration'), t('Remove posts/comments and/or private messages at a future time')), array('multi_profiles', t('Multiple Profiles'), t('Ability to create multiple profiles')), array('webpages', t('Web Pages'), t('Provide managed web pages on your channel')), - + array('private_notes', t('Private Notes'), t('Enables a tool to store notes and reminders')), +// prettyphoto has licensing issues and will no longer be provided in core - +// in any event this setting should probably be a theme option or plugin +// array('prettyphoto', t('Enhanced Photo Albums'), t('Enable photo album with enhanced features')), + //FIXME - needs a description, but how the hell do we explain this to normals? + array('sendzid', t('Extended Identity Sharing'), t('Share your identity with all websites on the internet. When disabled, identity is only shared with sites in the matrix.')), + array('expert', t('Expert Mode'), t('Enable Expert Mode to provide advanced configuration options')), + array('premium_channel', t('Premium Channel'), t('Allows you to set restrictions and terms on those that connect with your channel')), ), // Post composition @@ -30,6 +40,8 @@ function get_features() { t('Post Composition Features'), array('richtext', t('Richtext Editor'), t('Enable richtext editor')), array('preview', t('Post Preview'), t('Allow previewing posts and comments before publishing them')), + array('channel_sources', t('Channel Sources'), t('Automatically import channel content from other channels or feeds')), + array('content_encrypt', t('Even More Encryption'), t('Allow optional encryption of content end-to-end with a shared secret key')), ), // Network Tools @@ -41,21 +53,23 @@ function get_features() { array('personal_tab', t('Network Personal Tab'), t('Enable tab to display only Network posts that you\'ve interacted on')), array('new_tab', t('Network New Tab'), t('Enable tab to display all new Network activity')), array('affinity', t('Affinity Tool'), t('Filter stream activity by depth of relationships')), + array('suggest', t('Suggest Channels'), t('Show channel suggestions')), ), // Item tools 'tools' => array( t('Post/Comment Tools'), - array('multi_delete', t('Multiple Deletion'), t('Select and delete multiple posts/comments at once')), +// array('multi_delete', t('Multiple Deletion'), t('Select and delete multiple posts/comments at once')), array('edit_posts', t('Edit Sent Posts'), t('Edit and correct posts and comments after sending')), array('commtag', t('Tagging'), t('Ability to tag existing posts')), array('categories', t('Post Categories'), t('Add categories to your posts')), array('filing', t('Saved Folders'), t('Ability to file posts under folders')), array('dislike', t('Dislike Posts'), t('Ability to dislike posts/comments')), array('star_posts', t('Star Posts'), t('Ability to mark special posts with a star indicator')), + array('tagadelic', t('Tag Cloud'), t('Provide a personal tag cloud on your channel page')), ), ); call_hooks('get_features',$arr); return $arr; -}
\ No newline at end of file +} diff --git a/include/follow.php b/include/follow.php index 7fc4c2dc6..d98a58198 100644 --- a/include/follow.php +++ b/include/follow.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ // @@ -11,11 +11,13 @@ require_once('include/zot.php'); -function new_contact($uid,$url,$channel,$interactive = false) { +function new_contact($uid,$url,$channel,$interactive = false, $confirm = false) { $result = array('success' => false,'message' => ''); $a = get_app(); + $is_red = false; + if(! allowed_url($url)) { $result['message'] = t('Channel is blocked on this site.'); @@ -37,72 +39,110 @@ function new_contact($uid,$url,$channel,$interactive = false) { $ret = zot_finger($url,$channel); if($ret['success']) { - $j = json_decode($ret['body']); - + $is_red = true; + $j = json_decode($ret['body'],true); } - logger('follow: ' . $url . ' ' . print_r($j,true)); + if($is_red && $j) { - if(! ($j->success && $j->guid)) { - $result['message'] = t('Unable to communicate with requested channel.'); - return $result; - } + $my_perms = PERMS_W_STREAM|PERMS_W_MAIL; + logger('follow: ' . $url . ' ' . print_r($j,true), LOGGER_DEBUG); - // check service class limits - $r = q("select count(*) as total from abook where abook_channel = %d and not (abook_flags & %d) ", - intval($uid), - intval(ABOOK_FLAG_SELF) - ); - if($r) - $total_channels = $r[0]['total']; + if(! ($j['success'] && $j['guid'])) { + $result['message'] = t('Response from remote channel was incomplete.'); + logger('mod_follow: ' . $result['message']); + return $result; + } - if(! service_class_allows($uid,'total_channels',$total_channels)) { - $result['message'] = upgrade_message(); - return $result; - } + // Premium channel, set confirm before callback to avoid recursion - // do we have an xchan and hubloc? - // If not, create them. + if(array_key_exists('connect_url',$j) && (! $confirm)) + goaway(zid($j['connect_url'])); - $x = import_xchan_from_json($j); + // check service class limits - if(! $x['success']) - return $x; + $r = q("select count(*) as total from abook where abook_channel = %d and not (abook_flags & %d) ", + intval($uid), + intval(ABOOK_FLAG_SELF) + ); + if($r) + $total_channels = $r[0]['total']; - $xchan_hash = $x['hash']; + if(! service_class_allows($uid,'total_channels',$total_channels)) { + $result['message'] = upgrade_message(); + return $result; + } - $their_perms = 0; + // do we have an xchan and hubloc? + // If not, create them. - $global_perms = get_perms(); + $x = import_xchan($j); - if($j->permissions->data) { - $permissions = aes_unencapsulate(array( - 'data' => $j->permissions->data, - 'key' => $j->permissions->key, - 'iv' => $j->permissions->iv), - $channel['channel_prvkey']); - if($permissions) - $permissions = json_decode($permissions); - logger('decrypted permissions: ' . print_r($permissions,true), LOGGER_DATA); - } - else - $permissions = $j->permissions; + if(array_key_exists('deleted',$j) && intval($j['deleted'])) { + $result['message'] = t('Channel was deleted and no longer exists.'); + return $result; + } + + if(! $x['success']) + return $x; + + $xchan_hash = $x['hash']; + + + $their_perms = 0; - foreach($permissions as $k => $v) { - if($v) { - $their_perms = $their_perms | intval($global_perms[$k][1]); + $global_perms = get_perms(); + + if( array_key_exists('permissions',$j) && array_key_exists('data',$j['permissions'])) { + $permissions = crypto_unencapsulate(array( + 'data' => $j['permissions']['data'], + 'key' => $j['permissions']['key'], + 'iv' => $j['permissions']['iv']), + $channel['channel_prvkey']); + if($permissions) + $permissions = json_decode($permissions,true); + logger('decrypted permissions: ' . print_r($permissions,true), LOGGER_DATA); + } + else + $permissions = $j['permissions']; + + foreach($permissions as $k => $v) { + if($v) { + $their_perms = $their_perms | intval($global_perms[$k][1]); + } } } + else { + + // attempt network auto-discovery + + $my_perms = 0; + $their_perms = 0; + $xchan_hash = ''; + + + + + } + + if(! $xchan_hash) { + $result['message'] = t('Channel discovery failed.'); + logger('follow: ' . $result['message']); + return $result; + } if((local_user()) && $uid == local_user()) { $aid = get_account_id(); - $hash = $a->observer['xchan_hash']; + $hash = get_observer_hash(); + $ch = $a->get_channel(); + $default_group = $ch['channel_default_group']; + } else { - $r = q("select * from channel where uid = %d limit 1", + $r = q("select * from channel where channel_id = %d limit 1", intval($uid) ); if(! $r) { @@ -111,6 +151,7 @@ function new_contact($uid,$url,$channel,$interactive = false) { } $aid = $r[0]['channel_account_id']; $hash = $r[0]['channel_hash']; + $default_group = $r[0]['channel_default_group']; } if($hash == $xchan_hash) { @@ -129,12 +170,13 @@ function new_contact($uid,$url,$channel,$interactive = false) { ); } else { - $r = q("insert into abook ( abook_account, abook_channel, abook_xchan, abook_their_perms, abook_created, abook_updated ) - values( %d, %d, '%s', %d, '%s', '%s' ) ", + $r = q("insert into abook ( abook_account, abook_channel, abook_xchan, abook_their_perms, abook_my_perms, abook_created, abook_updated ) + values( %d, %d, '%s', %d, %d, '%s', '%s' ) ", intval($aid), intval($uid), dbesc($xchan_hash), intval($their_perms), + intval($my_perms), dbesc(datetime_convert()), dbesc(datetime_convert()) ); @@ -148,67 +190,25 @@ function new_contact($uid,$url,$channel,$interactive = false) { dbesc($xchan_hash), intval($uid) ); - if($r) + if($r) { $result['abook'] = $r[0]; - - // Then send a ping/message to the other side - - -/* - - $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `batch`, `notify`, `poll`, `poco`, `name`, `nick`, `photo`, `network`, `pubkey`, `rel`, `priority`, - `writable`, `hidden`, `blocked`, `readonly`, `pending` ) - VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, %d, 0, 0, 0) ", - intval($uid), - dbesc(datetime_convert()), - dbesc($ret['url']), - dbesc(normalise_link($ret['url'])), - dbesc($ret['addr']), - dbesc($ret['alias']), - dbesc($ret['batch']), - dbesc($ret['notify']), - dbesc($ret['poll']), - dbesc($ret['poco']), - dbesc($ret['name']), - dbesc($ret['nick']), - dbesc($ret['photo']), - dbesc($ret['network']), - dbesc($ret['pubkey']), - intval($new_relation), - intval($ret['priority']), - intval($writeable), - intval($hidden) - ); + if($is_red) + proc_run('php', 'include/notifier.php', 'permission_update', $result['abook']['abook_id']); } - $r = q("SELECT * FROM `contact` WHERE `url` = '%s' AND `uid` = %d LIMIT 1", - dbesc($ret['url']), - intval($uid) - ); + $arr = array('channel_id' => $uid, 'abook' => $result['abook']); - if(! count($r)) { - $result['message'] .= t('Unable to retrieve contact information.') . EOL; - return $result; - } - - $contact = $r[0]; - $contact_id = $r[0]['id']; + call_hooks('follow', $arr); + /** If there is a default group for this channel, add this member to it */ - $g = q("select def_gid from user where uid = %d limit 1", - intval($uid) - ); - if($g && intval($g[0]['def_gid'])) { + if($default_group) { require_once('include/group.php'); - group_add_member($uid,'',$contact_id,$g[0]['def_gid']); + $g = group_rec_byhash($uid,$default_group); + if($g) + group_add_member($uid,'',$xchan_hash,$g['id']); } -*/ - - $result['success'] = true; return $result; - - - } diff --git a/include/friendica_smarty.php b/include/friendica_smarty.php new file mode 100755 index 000000000..12a789c9a --- /dev/null +++ b/include/friendica_smarty.php @@ -0,0 +1,110 @@ +<?php /** @file */ +require_once 'include/ITemplateEngine.php'; +require_once("library/Smarty/libs/Smarty.class.php"); + + +class FriendicaSmarty extends Smarty { + + public $filename; + + function __construct() { + parent::__construct(); + + $a = get_app(); + $theme = current_theme(); + + // setTemplateDir can be set to an array, which Smarty will parse in order. + // The order is thus very important here + $template_dirs = array('theme' => "view/theme/$theme/tpl/"); + if( x($a->theme_info,"extends") ) + $template_dirs = $template_dirs + array('extends' => "view/theme/".$a->theme_info["extends"]."/tpl/"); + $template_dirs = $template_dirs + array('base' => 'view/tpl/'); + $this->setTemplateDir($template_dirs); + + $basecompiledir = $a->config['system']['smarty3_folder']; + + $this->setCompileDir($basecompiledir.'/compiled/'); + $this->setConfigDir($basecompiledir.'/config/'); + $this->setCacheDir($basecompiledir.'/cache/'); + + $this->left_delimiter = $a->get_template_ldelim('smarty3'); + $this->right_delimiter = $a->get_template_rdelim('smarty3'); + + // Don't report errors so verbosely + $this->error_reporting = E_ALL & ~E_NOTICE; + } + + function parsed($template = '') { + if($template) { + return $this->fetch('string:' . $template); + } + return $this->fetch('file:' . $this->filename); + } +} + + + +class FriendicaSmartyEngine implements ITemplateEngine { + static $name ="smarty3"; + + public function __construct(){ + $a = get_app(); + $basecompiledir = ((array_key_exists('smarty3_folder',$a->config['system'])) ? $a->config['system']['smarty3_folder'] : ''); + if (!$basecompiledir) $basecompiledir = dirname(__dir__)."/view/tpl/smarty3"; + if (!is_dir($basecompiledir)) { + echo "<b>ERROR:</b> folder <tt>$basecompiledir</tt> does not exist."; killme(); + } + if(!is_writable($basecompiledir)){ + echo "<b>ERROR:</b> folder <tt>$basecompiledir</tt> must be writable by webserver."; killme(); + } + $a->config['system']['smarty3_folder'] = $basecompiledir; + } + + // ITemplateEngine interface + public function replace_macros($s, $r) { + $template = ''; + if(gettype($s) === 'string') { + $template = $s; + $s = new FriendicaSmarty(); + } + foreach($r as $key=>$value) { + if($key[0] === '$') { + $key = substr($key, 1); + } + $s->assign($key, $value); + } + return $s->parsed($template); + } + + public function get_markup_template($file, $root=''){ + $template_file = theme_include($file, $root); + if($template_file) { + $template = new FriendicaSmarty(); + $template->filename = $template_file; + + return $template; + } + return ""; + } + + public function get_intltext_template($file, $root='') { + $a = get_app(); + + if(file_exists("view/{$a->language}/$file")) + $template_file = "view/{$a->language}/$file"; + elseif(file_exists("view/en/$file")) + $template_file = "view/en/$file"; + else + $template_file = theme_include($file,$root); + if($template_file) { + $template = new FriendicaSmarty(); + $template->filename = $template_file; + + return $template; + } + return ""; + } + + + +} diff --git a/include/gprobe.php b/include/gprobe.php index e66635302..48c1c8e14 100644 --- a/include/gprobe.php +++ b/include/gprobe.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ require_once('include/cli_startup.php'); require_once('include/zot.php'); @@ -15,13 +15,13 @@ function gprobe_run($argv, $argc){ $url = hex2bin($argv[1]); $r = q("select * from xchan where xchan_addr = '%s' limit 1", - dbesc(normalise_link($url)) + dbesc($url) ); if(! $r) { $x = zot_finger($url,null); - if($x) { - $j = json_decode($x,true); + if($x['success']) { + $j = json_decode($x['body'],true); $y = import_xchan($j); } } diff --git a/include/group.php b/include/group.php index 6568af0c7..56f177ab0 100644 --- a/include/group.php +++ b/include/group.php @@ -1,7 +1,7 @@ -<?php +<?php /** @file */ -function group_add($uid,$name) { +function group_add($uid,$name,$public = 0) { $ret = false; if(x($uid) && x($name)) { @@ -14,11 +14,11 @@ function group_add($uid,$name) { // access lists. What we're doing here is reviving the dead group, but old content which // was restricted to this group may now be seen by the new group members. - $z = q("SELECT * FROM `group` WHERE `id` = %d LIMIT 1", + $z = q("SELECT * FROM `groups` WHERE `id` = %d LIMIT 1", intval($r) ); if(count($z) && $z[0]['deleted']) { - $r = q("UPDATE `group` SET `deleted` = 0 WHERE `uid` = %d AND `name` = '%s' LIMIT 1", + $r = q("UPDATE `groups` SET `deleted` = 0 WHERE `uid` = %d AND `name` = '%s' LIMIT 1", intval($uid), dbesc($name) ); @@ -31,16 +31,17 @@ function group_add($uid,$name) { $dups = false; $hash = random_string() . $name; - $r = q("SELECT id FROM group WHERE hash = '%s' LIMIT 1", dbesc($hash)); + $r = q("SELECT id FROM `groups` WHERE hash = '%s' LIMIT 1", dbesc($hash)); if($r) $dups = true; } while($dups == true); - $r = q("INSERT INTO `group` ( hash, uid, name ) - VALUES( '%s', %d, '%s' ) ", + $r = q("INSERT INTO `groups` ( hash, uid, visible, name ) + VALUES( '%s', %d, %d, '%s' ) ", dbesc($hash), intval($uid), + intval($public), dbesc($name) ); $ret = $r; @@ -52,40 +53,43 @@ function group_add($uid,$name) { function group_rmv($uid,$name) { $ret = false; if(x($uid) && x($name)) { - $r = q("SELECT id FROM `group` WHERE `uid` = %d AND `name` = '%s' LIMIT 1", + $r = q("SELECT id, hash FROM `groups` WHERE `uid` = %d AND `name` = '%s' LIMIT 1", intval($uid), dbesc($name) ); - if(count($r)) + if($r) { $group_id = $r[0]['id']; + $group_hash = $r[0]['hash']; + } + if(! $group_id) return false; // remove group from default posting lists - $r = q("SELECT channel_default_gid, channel_allow_gid, channel_deny_gid FROM channel WHERE channel_id = %d LIMIT 1", + $r = q("SELECT channel_default_group, channel_allow_gid, channel_deny_gid FROM channel WHERE channel_id = %d LIMIT 1", intval($uid) ); if($r) { $user_info = $r[0]; $change = false; - if($user_info['channel_default_gid'] == $group_id) { - $user_info['channel_default_gid'] = 0; + if($user_info['channel_default_group'] == $group_hash) { + $user_info['channel_default_group'] = ''; $change = true; } if(strpos($user_info['channel_allow_gid'], '<' . $group_id . '>') !== false) { - $user_info['channel_allow_gid'] = str_replace('<' . $group_id . '>', '', $user_info['channel_allow_gid']); + $user_info['channel_allow_gid'] = str_replace('<' . $group_hash . '>', '', $user_info['channel_allow_gid']); $change = true; } if(strpos($user_info['channel_deny_gid'], '<' . $group_id . '>') !== false) { - $user_info['channel_deny_gid'] = str_replace('<' . $group_id . '>', '', $user_info['channel_deny_gid']); + $user_info['channel_deny_gid'] = str_replace('<' . $group_hash . '>', '', $user_info['channel_deny_gid']); $change = true; } if($change) { - q("UPDATE channel SET channel_default_gid = %d, channel_allow_gid = '%s', channel_deny_gid = '%s' + q("UPDATE channel SET channel_default_group = '%s', channel_allow_gid = '%s', channel_deny_gid = '%s' WHERE channel_id = %d", - intval($user_info['channel_default_gid']), + intval($user_info['channel_default_group']), dbesc($user_info['channel_allow_gid']), dbesc($user_info['channel_deny_gid']), intval($uid) @@ -100,7 +104,7 @@ function group_rmv($uid,$name) { ); // remove group - $r = q("UPDATE `group` SET `deleted` = 1 WHERE `uid` = %d AND `name` = '%s' LIMIT 1", + $r = q("UPDATE `groups` SET `deleted` = 1 WHERE `uid` = %d AND `name` = '%s' LIMIT 1", intval($uid), dbesc($name) ); @@ -115,7 +119,7 @@ function group_rmv($uid,$name) { function group_byname($uid,$name) { if((! $uid) || (! strlen($name))) return false; - $r = q("SELECT * FROM `group` WHERE `uid` = %d AND `name` = '%s' LIMIT 1", + $r = q("SELECT * FROM `groups` WHERE `uid` = %d AND `name` = '%s' LIMIT 1", intval($uid), dbesc($name) ); @@ -124,6 +128,19 @@ function group_byname($uid,$name) { return false; } + +function group_rec_byhash($uid,$hash) { + if((! $uid) || (! strlen($hash))) + return false; + $r = q("SELECT * FROM `groups` WHERE `uid` = %d AND `hash` = '%s' LIMIT 1", + intval($uid), + dbesc($hash) + ); + if($r) + return $r[0]; + return false; +} + function group_rmv_member($uid,$name,$member) { $gid = group_byname($uid,$name); if(! $gid) @@ -169,12 +186,13 @@ function group_add_member($uid,$name,$member,$gid = 0) { function group_get_members($gid) { $ret = array(); if(intval($gid)) { - $r = q("SELECT abook.*,xchan.*,group_member.* FROM `group_member` + $r = q("SELECT * FROM `group_member` LEFT JOIN abook ON abook_xchan = `group_member`.`xchan` left join xchan on xchan_hash = abook_xchan - WHERE `gid` = %d AND `group_member`.`uid` = %d and not ( abook_flags & %d ) and not ( abook_flags & %d ) and not ( abook_flags & %d ) ORDER BY xchan_name ASC ", + WHERE `gid` = %d AND abook_channel = %d and `group_member`.`uid` = %d and not ( xchan_flags & %d ) and not ( abook_flags & %d ) and not ( abook_flags & %d ) ORDER BY xchan_name ASC ", intval($gid), intval(local_user()), - intval(ABOOK_FLAG_SELF), + intval(local_user()), + intval(XCHAN_FLAGS_DELETED), intval(ABOOK_FLAG_BLOCKED), intval(ABOOK_FLAG_PENDING) ); @@ -184,18 +202,18 @@ function group_get_members($gid) { return $ret; } -function mini_group_select($uid,$gid = 0) { +function mini_group_select($uid,$group = '') { $grps = array(); $o = ''; - $r = q("SELECT * FROM `group` WHERE `deleted` = 0 AND `uid` = %d ORDER BY `name` ASC", + $r = q("SELECT * FROM `groups` WHERE `deleted` = 0 AND `uid` = %d ORDER BY `name` ASC", intval($uid) ); - $grps[] = array('name' => '', 'id' => '0', 'selected' => ''); + $grps[] = array('name' => '', 'hash' => '0', 'selected' => ''); if(count($r)) { foreach($r as $rr) { - $grps[] = array('name' => $rr['name'], 'id' => $rr['id'], 'selected' => (($gid == $rr['id']) ? 'true' : '')); + $grps[] = array('name' => $rr['name'], 'id' => $rr['hash'], 'selected' => (($group == $rr['hash']) ? 'true' : '')); } } @@ -211,7 +229,7 @@ function mini_group_select($uid,$gid = 0) { -function group_side($every="contacts",$each="group",$edit = false, $group_id = 0, $cid = '') { +function group_side($every="connections",$each="group",$edit = false, $group_id = 0, $cid = '',$mode = 1) { $o = ''; @@ -221,15 +239,14 @@ function group_side($every="contacts",$each="group",$edit = false, $group_id = 0 $groups = array(); $groups[] = array( - 'text' => t('All Connections'), + 'text' => t('All Channels'), 'id' => 0, 'selected' => (($group_id == 0) ? 'group-selected' : ''), 'href' => $every, ); - - $r = q("SELECT * FROM `group` WHERE `deleted` = 0 AND `uid` = %d ORDER BY `name` ASC", + $r = q("SELECT * FROM `groups` WHERE `deleted` = 0 AND `uid` = %d ORDER BY `name` ASC", intval($_SESSION['uid']) ); $member_of = array(); @@ -255,7 +272,7 @@ function group_side($every="contacts",$each="group",$edit = false, $group_id = 0 'cid' => $cid, 'text' => $rr['name'], 'selected' => $selected, - 'href' => (($each === 'network') ? $each.'?f=&gid='.$rr['id'] : $each."/".$rr['id']), + 'href' => (($mode == 0) ? $each.'?f=&gid='.$rr['id'] : $each."/".$rr['id']) . ((x($_GET,'new')) ? '&new=' . $_GET['new'] : '') . ((x($_GET,'order')) ? '&order=' . $_GET['order'] : ''), 'edit' => $groupedit, 'ismember' => in_array($rr['id'],$member_of), ); @@ -280,12 +297,15 @@ function group_side($every="contacts",$each="group",$edit = false, $group_id = 0 function expand_groups($a) { if(! (is_array($a) && count($a))) return array(); - stringify_array_elms($groups); - $groups = implode(',', $a); - $groups = dbesc($groups); - $r = q("SELECT xchan FROM `group_member` WHERE `gid` IN ( $groups )"); + $x = $a; + stringify_array_elms($x,true); + $groups = implode(',', $x); + + if($groups) + $r = q("SELECT xchan FROM group_member WHERE gid IN ( select id from `groups` where hash in ( $groups ))"); $ret = array(); - if(count($r)) + + if($r) foreach($r as $rr) $ret[] = $rr['xchan']; return $ret; @@ -294,7 +314,7 @@ function expand_groups($a) { function member_of($c) { - $r = q("SELECT `group`.`name`, `group`.`id` FROM `group` LEFT JOIN `group_member` ON `group_member`.`gid` = `group`.`id` WHERE `group_member`.`xchan` = '%s' AND `group`.`deleted` = 0 ORDER BY `group`.`name` ASC ", + $r = q("SELECT `groups`.`name`, `groups`.`id` FROM `groups` LEFT JOIN `group_member` ON `group_member`.`gid` = `groups`.`id` WHERE `group_member`.`xchan` = '%s' AND `groups`.`deleted` = 0 ORDER BY `groups`.`name` ASC ", dbesc($c) ); diff --git a/include/html2bbcode.php b/include/html2bbcode.php index 985c36eaa..df430e6c7 100644 --- a/include/html2bbcode.php +++ b/include/html2bbcode.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ /* html2bbcode.php Converter for HTML to BBCode @@ -287,4 +287,4 @@ function html2bbcode($message) return(trim($message)); } -?> + diff --git a/include/html2plain.php b/include/html2plain.php index e5615f8ba..2f5be7f69 100644 --- a/include/html2plain.php +++ b/include/html2plain.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ require_once "html2bbcode.php"; function breaklines($line, $level, $wraplength = 75) @@ -205,7 +205,7 @@ function html2plain($html, $wraplength = 75, $compact = false) if (!$compact) { $counter = 1; foreach ($urls as $id=>$url) - if (strpos($message, $url) == false) + if ($url && strpos($message, $url) === false) $message .= "\n".$url." "; //$message .= "\n[".($counter++)."] ".$url; } @@ -219,4 +219,4 @@ function html2plain($html, $wraplength = 75, $compact = false) return(trim($message)); } -?> + diff --git a/include/hubloc.php b/include/hubloc.php new file mode 100644 index 000000000..35d9dbeb1 --- /dev/null +++ b/include/hubloc.php @@ -0,0 +1,33 @@ +<?php /** @file */ + + + +function prune_hub_reinstalls() { + + $r = q("select site_url from site where true"); + if($r) { + foreach($r as $rr) { + $x = q("select count(*) as t, hubloc_sitekey, max(hubloc_connected) as c from hubloc where hubloc_url = '%s' group by hubloc_sitekey order by c", + dbesc($rr['site_url']) + ); + + // see if this url has more than one sitekey, indicating it has been re-installed. + + if(count($x) > 1) { + + $d1 = datetime_convert('UTC','UTC',$x[0]['c']); + $d2 = datetime_convert('UTC','UTC','now - 3 days'); + + // allow some slop period, say 3 days - just in case this is a glitch or transient occurrence + // Then remove any hublocs pointing to the oldest entry. + + if($d1 < $d2) { + logger('prune_hub_reinstalls: removing dead hublocs at ' . $rr['site_url']); + $y = q("delete from hubloc where hubloc_sitekey = '%s'", + dbesc($x[0]['hubloc_sitekey']) + ); + } + } + } + } +}
\ No newline at end of file diff --git a/include/identity.php b/include/identity.php index 2c3730cbc..1cbe43b1e 100644 --- a/include/identity.php +++ b/include/identity.php @@ -1,14 +1,30 @@ -<?php +<?php /** @file */ require_once('include/zot.php'); require_once('include/crypto.php'); +/** + * @function identity_check_service_class($account_id) + * 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 array + * 'success' => boolean true if creating a new channel is allowed for this account + * 'message' => if success is false, optional error text + */ + + function identity_check_service_class($account_id) { $ret = array('success' => false, $message => ''); - $r = q("select count(channel_id) as total from channel were channel_account_id = %d ", - intval($account_id) + $r = q("select count(channel_id) as total from channel where channel_account_id = %d and not ( channel_pageflags & %d ) ", + intval($account_id), + intval(PAGE_REMOVED) ); if(! ($r && count($r))) { $ret['message'] = t('Unable to obtain identity information from database'); @@ -25,10 +41,122 @@ function identity_check_service_class($account_id) { } -// Required: name, nickname, account_id +/** + * @function validate_channelname($name) + * Determine if the channel name is allowed when creating a new channel. + * This action is pluggable. + * + * @param string $name + * + * @returns nil return if name is valid, or string describing the error state. + * + * 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. + * + */ + +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']; + return; +} + + +/** + * @function create_sys_channel() + * 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' => PAGE_SYSTEM, + 'publish' => 0, + 'xchanflags' => XCHAN_FLAGS_SYSTEM + )); +} + +function get_sys_channel() { + $r = q("select * from channel left join xchan on channel_hash = xchan_hash where (channel_pageflags & %d) limit 1", + intval(PAGE_SYSTEM) + ); + if($r) + return $r[0]; + return false; +} + +function is_sys_channel($channel_id) { + $r = q("select channel_pageflags from channel where channel_id = %d limit 1", + intval($channel_id) + ); + if(($r) && ($r[0]['channel_pageflags'] & PAGE_SYSTEM)) + return true; + return false; +} + -// optional: pageflags +/** + * @channel_total() + * Return the total number of channels on this site. No filtering is performed except to check PAGE_REMOVED + * + * @returns int + * on error returns boolean false + * + */ + +function channel_total() { + $r = q("select channel_id from channel where not ( channel_pageflags & %d )", + intval(PAGE_REMOVED) + ); + if(is_array($r)) + return count($r); + return false; +} + + +/** + * @function create_identity($arr) + * 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 + * 'name' => full name of channel + * 'nickname' => "email/url-compliant" nickname + * 'account_id' => 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(); @@ -38,10 +166,30 @@ function create_identity($arr) { $ret['message'] = t('No account identifier'); return $ret; } + $ret=identity_check_service_class($arr['account_id']); + if (!$ret['success']) { + return $ret; + } + + $nick = mb_strtolower(trim($arr['nickname'])); + if(! $nick) { + $ret['message'] = t('Nickname is required.'); + return $ret; + } - $nick = trim($arr['nickname']); $name = escape_tags($arr['name']); $pageflags = ((x($arr,'pageflags')) ? intval($arr['pageflags']) : PAGE_NORMAL); + $xchanflags = ((x($arr,'xchanflags')) ? intval($arr['xchanflags']) : XCHAN_FLAGS_NORMAL); + $name_error = validate_channelname($arr['name']); + if($name_error) { + $ret['message'] = $name_error; + return $ret; + } + + if($nick === 'sys' && (! ($pageflags & PAGE_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.'); @@ -55,14 +203,34 @@ function create_identity($arr) { $sig = base64url_encode(rsa_sign($guid,$key['prvkey'])); $hash = base64url_encode(hash('whirlpool',$guid . $sig,true)); - // Force primary until importation works, then we'll offer a choice + // 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']); + + $perms_sql = ''; + + $defperms = site_default_perms(); + $global_perms = get_perms(); + foreach($defperms as $p => $v) { + $perms_keys .= ', ' . $global_perms[$p][0]; + $perms_vals .= ', ' . intval($v); + } + + $expire = get_config('system', 'default_expire_days'); + $expire = (($expire===false)? '0': $expire); + $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 ) - values ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d ) ", + channel_hash, channel_prvkey, channel_pubkey, channel_pageflags, channel_expire_days $perms_keys ) + values ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d $perms_vals ) ", intval($arr['account_id']), intval($primary), @@ -73,32 +241,28 @@ function create_identity($arr) { dbesc($hash), dbesc($key['prvkey']), dbesc($key['pubkey']), - intval(PAGE_NORMAL) + intval($pageflags), + intval($expire) ); + + + $r = q("select * from channel where channel_account_id = %d and channel_guid = '%s' limit 1", intval($arr['account_id']), dbesc($guid) ); - if(! ($r && count($r))) { + if(! $r) { $ret['message'] = t('Unable to retrieve created identity'); return $ret; } $ret['channel'] = $r[0]; - set_default_login_identity($arr['account_id'],$ret['channel']['channel_id'],false); - - // Ensure that there is a host keypair. - - if((! get_config('system','pubkey')) && (! get_config('system','prvkey'))) { - $hostkey = new_keypair(4096); - set_config('system','pubkey',$hostkey['pubkey']); - set_config('system','prvkey',$hostkey['prvkey']); - } - + 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. @@ -122,7 +286,7 @@ function create_identity($arr) { $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_name, xchan_network, xchan_photo_date, xchan_name_date ) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", + $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_flags ) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d)", dbesc($hash), dbesc($guid), dbesc($sig), @@ -132,23 +296,27 @@ function create_identity($arr) { 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()) + dbesc(datetime_convert()), + intval($xchanflags) ); // 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, name, photo, thumb) - VALUES ( %d, %d, '%s', '%s', %d, '%s', '%s', '%s') ", + $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}") @@ -165,32 +333,970 @@ function create_identity($arr) { intval(ABOOK_FLAG_SELF) ); + if(intval($ret['channel']['channel_account_id'])) { - // Create a group with no members. This allows somebody to use it - // right away as a default group for new contacts. + // Create a group with no members. 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')); + require_once('include/group.php'); + group_add($newuid, t('Friends')); + + call_hooks('register_account', $newuid); + + proc_run('php','include/directory.php', $ret['channel']['channel_id']); + } - call_hooks('register_account', $newuid); - $ret['success'] = true; return $ret; } -// set default identity for account_id to channel_id -// if $force is false only do this if there is no current default + +/** + * @function set_default_login_identity($account_id, $channel_id, $force = true) + * 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 + * + * @returns nil + */ 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) && (count($r)) && ((! intval($r[0]['account_default_channel'])) || $force)) { - $r = q("update account set account_default_channel = %d where account_id = %d limit 1", - intval($channel_id), - 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 limit 1", + intval($channel_id), + intval($account_id) + ); + } + } +} + +/** + * @function identity_basic_export($channel_id) + * 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 + * + * + * @returns array + * See function for details + * + */ + +function identity_basic_export($channel_id) { + + /* + * Red basic channel export + */ + + $ret = array(); + + $ret['compatibility'] = array('project' => RED_PLATFORM, '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 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' => base64url_encode($r[0]['data'])); + } + + + return $ret; +} + + + +/** + * + * @function : profile_load(&$a, $nickname, $profile) + * Generate + * @param App $a + * @param string $nickname + * @param string $profile + * + * Summary: 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. + * + */ + + +function profile_load(&$a, $nickname, $profile = '') { + + logger('profile_load: ' . $nickname . (($profile) ? ' profile: ' . $profile : '')); + + $user = q("select channel_id from channel where channel_address = '%s' 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 not ( channel_pageflags & %d ) + AND profile.is_default = 1 LIMIT 1", + dbesc($nickname), + intval(PAGE_REMOVED) + ); + } + + + if(! $p) { + logger('profile error: ' . $a->query_string, LOGGER_DEBUG); + notice( t('Requested profile is not available.') . EOL ); + $a->error = 404; + return; + } + + $z = q("select xchan_photo_date from xchan where xchan_hash = '%s' limit 1", + dbesc($p[0]['channel_hash']) + ); + if($z) + $p[0]['picdate'] = $z[0]['xchan_photo_date']; + + + // fetch user tags if this isn't the default profile + + if(! $p[0]['is_default']) { + $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_user()) { + $a->profile['channel_mobile_theme'] = get_pconfig(local_user(),'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); +// } + + return; +} + +function profile_create_sidebar(&$a,$connect = true) { + + $block = (((get_config('system','block_public')) && (! local_user()) && (! remote_user())) ? true : false); + + $a->set_widget('profile',profile_sidebar($a->profile, $block, $connect)); + return; +} + + +/** + * + * Function: profile_sidebar + * + * Formats a profile for display in the sidebar. + * It is very difficult to templatise the HTML completely + * because of all the conditional logic. + * + * @parameter: array $profile + * + * Returns HTML string stuitable 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; + $address = false; + $pdesc = true; + + if((! is_array($profile)) && (! count($profile))) + return $o; + + + head_set_icon($profile['thumb']); + + $is_owner = (($profile['uid'] == local_user()) ? true : 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(), + ); + + + if(feature_enabled(local_user(),'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_user()); + + + if($r) { + foreach($r as $rr) { + $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:'); + + $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_user()) && (! remote_user())) || $block ) { + $location = $pdesc = $gender = $marital = $homepage = $online = False; + } + + $firstname = ((strpos($profile['name'],' ')) + ? trim(substr($profile['name'],0,strpos($profile['name'],' '))) : $profile['name']); + $lastname = (($firstname === $profile['name']) ? '' : trim(substr($profile['name'],strlen($firstname)))); + + $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'); + + $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, + '$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_user()) + 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_user()), + 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_user()) + 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_user()), + 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) { + + 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_wall')) { + $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'] !== '0000-00-00 00:00:00') { + $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 ); + + + $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; +} + +/** + * @function zid_init(&$a) + * 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. + * + * @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_user()) { + $r = q("select * from hubloc where hubloc_addr = '%s' order by hubloc_connected desc limit 1", + dbesc($tmp_str) + ); + if($r && remote_user() && remote_user() === $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.'); + } + } +} + +/** + * @function zid($s,$address = '') + * 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_user()) { + if((get_pconfig(local_user(),'system','always_my_theme')) || (! $uid)) + return local_user(); + } + if(! $uid) { + $x = get_sys_channel(); + if($x) + return $x['channel_id']; + } + return $uid; +} + +/** +* @function get_default_profile_photo($size = 175) +* Retrieves the path of the default_profile_photo for this system +* with the specified size. +* @param int $size +* one of (175, 80, 48) +* @returns string +* +*/ + +function get_default_profile_photo($size = 175) { + $scheme = get_config('system','default_profile_photo'); + if(! $scheme) + $scheme = 'rainbow_man'; + return 'images/default_profile_photos/' . $scheme . '/' . $size . '.jpg'; +} + + +/** + * + * @function is_foreigner($s) + * Test whether a given identity is NOT a member of the Red Matrix + * @param string $s; + * xchan_hash of the identity in question + * + * @returns boolean true or false + * + */ + +function is_foreigner($s) { + return((strpbrk($s,'.:@')) ? true : false); +} + + +/** + * + * @function is_member($s) + * Test whether a given identity is a member of the Red Matrix + * @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_user() && ! remote_user()) + 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); + +} + + +function identity_selector() { + if(local_user()) { + $r = q("select channel.*, xchan.* from channel left join xchan on channel.channel_hash = xchan.xchan_hash where channel.channel_account_id = %d and not ( channel_pageflags & %d ) order by channel_name ", + intval(get_account_id()), + intval(PAGE_REMOVED) + ); + if(count($r) > 1) { + $selected_channel = null; + $account = get_app()->get_account(); + $o = replace_macros(get_markup_template('channel_id_select.tpl'),array( + '$channels' => $r, + '$selected' => local_user() + )); + return $o; + } + } + + return ''; +} diff --git a/include/iquery.php b/include/iquery.php deleted file mode 100644 index 0d51134a4..000000000 --- a/include/iquery.php +++ /dev/null @@ -1,139 +0,0 @@ -<?php - - -function network_query($a,$arr) { - - - $parent_options = ''; - $child_options = ''; - - $ordering = (($arr['order'] === 'post') ? "`created`" : "`commented`") . " DESC "; - - $itemspage = get_pconfig($arr['uid'],'system','itemspage'); - $a->set_pager_itemspage(((intval($itemspage)) ? $itemspage : 40)); - - $pager_sql = ((intval($arr['update'])) ? '' : sprintf(" LIMIT %d, %d ",intval($a->pager['start']), intval($a->pager['itemspage']))); - - $arr['cmin'] = ((x($arr,'cmin')) ? $arr['cmin'] : 0); - $arr['cmax'] = ((x($arr,'cmax')) ? $arr['cmax'] : 0); - - $simple_update = (($arr['update']) ? " and `item`.`unseen` = 1 " : ''); - - if($arr['new']) { - - // "New Item View" - show all items unthreaded in reverse created date order - - $items = q("SELECT `item`.*, `item`.`id` AS `item_id`, - `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, `contact`.`writable`, - `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn_id`, `contact`.`self`, - `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid` - FROM `item`, `contact` - WHERE `item`.`uid` = %d AND `item`.`visible` = 1 - AND `item`.`deleted` = 0 and `item`.`moderated` = 0 - $simple_update - AND `contact`.`closeness` >= %d and `contact`.`closeness` <= %d - AND `contact`.`id` = `item`.`contact-id` - AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 - $sql_extra $sql_nets - ORDER BY `item`.`received` DESC $pager_sql ", - intval($arr['uid']), - intval($arr['cmin']), - intval($arr['cmax']) - - ); - - $items = fetch_post_tags($items); - return $items; - - } - if($update) { - $r = q("SELECT `parent` AS `item_id`, `contact`.`uid` AS `contact_uid` - FROM `item` LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - WHERE `item`.`uid` = %d AND `item`.`visible` = 1 AND - `contact`.`closeness` >= %d and `contact`.`closeness` <= %d - (`item`.`deleted` = 0 OR item.verb = '" . ACTIVITY_LIKE ."' OR item.verb = '" . ACTIVITY_DISLIKE . "') - and `item`.`moderated` = 0 $simple_update - AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 - $sql_extra3 $sql_extra $sql_nets ", - intval($arr['uid']), - intval($arr['cmin']), - intval($arr['cmax']) - ); - } - else { - $r = q("SELECT `item`.`id` AS `item_id`, `contact`.`uid` AS `contact_uid` - FROM `item` LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - WHERE `item`.`uid` = %d AND `item`.`visible` = 1 AND `item`.`deleted` = 0 - AND `item`.`moderated` = 0 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 - AND `contact`.`closeness` >= %d and `contact`.`closeness` <= %d - AND `item`.`parent` = `item`.`id` - $sql_extra3 $sql_extra $sql_nets - ORDER BY `item`.$ordering $pager_sql ", - intval($arr['uid']), - intval($arr['cmin']), - intval($arr['cmax']) - ); - } - - // Then fetch all the children of the parents that are on this page - - $parents_arr = array(); - $parents_str = ''; - - if(count($r)) { - foreach($r as $rr) - if(! in_array($rr['item_id'],$parents_arr)) - $parents_arr[] = $rr['item_id']; - $parents_str = implode(', ', $parents_arr); - - $items = q("SELECT `item`.*, `item`.`id` AS `item_id`, - `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`alias`, - `contact`.`rel`, `contact`.`writable`, - `contact`.`network`, `contact`.`thumb`, `contact`.`self`, - `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid` - FROM `item`, `contact` - WHERE `item`.`uid` = %d AND `item`.`visible` = 1 AND `item`.`deleted` = 0 - AND `item`.`moderated` = 0 AND `contact`.`id` = `item`.`contact-id` - AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 - AND `item`.`parent` IN ( %s ) - $sql_extra ", - intval($arr['uid']), - dbesc($parents_str) - ); - - $items = fetch_post_tags($items); - - $items = conv_sort($items,$ordering); - } - else { - $items = array(); - } - - return $items; -} - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/include/items.php b/include/items.php index 8e4e2dd8d..b80f18b72 100755 --- a/include/items.php +++ b/include/items.php @@ -1,272 +1,494 @@ -<?php +<?php /** @file */ require_once('include/bbcode.php'); require_once('include/oembed.php'); require_once('include/crypto.php'); -require_once('include/Photo.php'); - +require_once('include/photo/photo_driver.php'); +require_once('include/permissions.php'); function collect_recipients($item,&$private) { require_once('include/group.php'); + $private = ((intval($item['item_private'])) ? true : false); + $recipients = array(); + + // if the post is marked private but there are no recipients, only add the author and owner + // as recipients. The ACL for the post may live on the hub of a different clone. We need to + // get the post to that hub. + if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid']) { + + // it is private + $allow_people = expand_acl($item['allow_cid']); $allow_groups = expand_groups(expand_acl($item['allow_gid'])); + + $recipients = array_unique(array_merge($allow_people,$allow_groups)); + + // if you specifically deny somebody but haven't allowed anybody, we'll allow everybody in your + // address book minus the denied connections. The post is still private and can't be seen publicly + // as that would allow the denied person to see the post by logging out. + + if((! $item['allow_cid']) && (! $item['allow_gid'])) { + $r = q("select * from abook where abook_channel = %d and not (abook_flags & %d) and not (abook_flags & %d) and not (abook_flags & %d)", + intval($item['uid']), + intval(ABOOK_FLAG_SELF), + intval(ABOOK_FLAG_PENDING), + intval(ABOOK_FLAG_ARCHIVED) + ); + + if($r) { + foreach($r as $rr) { + $recipients[] = $rr['abook_xchan']; + } + } + } + $deny_people = expand_acl($item['deny_cid']); $deny_groups = expand_groups(expand_acl($item['deny_gid'])); - $recipients = array_unique(array_merge($allow_people,$allow_groups)); $deny = array_unique(array_merge($deny_people,$deny_groups)); $recipients = array_diff($recipients,$deny); $private = true; } else { - $recipients = array(); - $r = q("select * from abook where abook_channel = %d and not (abook_flags & %d) and not (abook_flags & %d)", - intval($item['uid']), - intval(ABOOK_FLAG_SELF), - intval(ABOOK_FLAG_PENDING) - ); - if($r) { - foreach($r as $rr) { - // FIXME check permissions of each - $recipients[] = $rr['abook_xchan']; + if(! $private) { + $r = q("select abook_xchan from abook where abook_channel = %d and not (abook_flags & %d) and not (abook_flags & %d) and not (abook_flags & %d)", + intval($item['uid']), + intval(ABOOK_FLAG_SELF), + intval(ABOOK_FLAG_PENDING), + intval(ABOOK_FLAG_ARCHIVED) + ); + if($r) { + foreach($r as $rr) { + $recipients[] = $rr['abook_xchan']; + } } } - $private = false; } + + // This is a somewhat expensive operation but important. + // Don't send this item to anybody who isn't allowed to see it + + $recipients = check_list_permissions($item['uid'],$recipients,'view_stream'); + + // remove any upstream recipients from our list. + // If it is ourself we'll add it back in a second. + // This should prevent complex delivery chains from getting overly complex by not + // sending to anybody who is on our list of those who sent it to us. + + if($item['route']) { + $route = explode(',',$item['route']); + if(count($route)) { + $route = array_unique($route); + $recipients = array_diff($recipients,$route); + } + } + + // add ourself just in case we have nomadic clones that need to get a copy. + + $recipients[] = $item['author_xchan']; + if($item['owner_xchan'] != $item['author_xchan']) + $recipients[] = $item['owner_xchan']; return $recipients; + } +/** + * @function can_comment_on_post($observer_xchan,$item); + * + * This function examines the comment_policy attached to an item and decides if the current observer has + * sufficient privileges to comment. This will normally be called on a remote site where perm_is_allowed() + * will not be suitable because the post owner does not have a local channel_id. + * Generally we should look at the item - in particular the author['book_flags'] and see if ABOOK_FLAG_SELF is set. + * If it is, you should be able to use perm_is_allowed( ... 'post_comments'), and if it isn't you need to call + * can_comment_on_post() + */ -function get_public_feed($channel,$params) { +function can_comment_on_post($observer_xchan,$item) { - $type = 'xml'; - $begin = '0000-00-00 00:00:00'; - $end = ''; - $start = 0; - $records = 40; - $direction = 'desc'; +// logger('can_comment_on_post: comment_policy: ' . $item['comment_policy'], LOGGER_DEBUG); - if(is_array($params)) { - $type = ((x($params,'type')) ? $params['type'] : $type); - $begin = ((x($params,'begin')) ? $params['begin'] : $begin); - $end = ((x($params,'end')) ? $params['end'] : $end); - $start = ((x($params,'start')) ? $params['start'] : $start); - $records = ((x($params,'records')) ? $params['records'] : $records); - $direction = ((x($params,'direction')) ? $params['direction'] : $direction); - } - - switch($type) { - case 'json': - header("Content-type: application/atom+json"); + if(! $observer_xchan) + return false; + if($item['comment_policy'] === 'none') + return false; + if($observer_xchan === $item['author_xchan'] || $observer_xchan === $item['owner_xchan']) + return true; + switch($item['comment_policy']) { + case 'self': + if($observer_xchan === $item['author_xchan'] || $observer_xchan === $item['owner_xchan']) + return true; + break; + case 'public': + // We don't allow public comments yet, until a policy + // for dealing with anonymous comments is in place with + // a means to moderate comments. Until that time, return + // false. + return false; + break; + case 'contacts': + case '': + if(array_key_exists('owner',$item)) { + if(($item['owner']['abook_xchan']) && ($item['owner']['abook_their_perms'] & PERMS_W_COMMENT)) + return true; + } break; - case 'xml': default: - header("Content-type: application/atom+xml"); break; } + if(strstr($item['comment_policy'],'network:') && strstr($item['comment_policy'],'red')) + return true; + if(strstr($item['comment_policy'],'site:') && strstr($item['comment_policy'],get_app()->get_hostname())) + return true; + + return false; +} +/** + * @function add_source_route($iid,$hash) + * Adds $hash to the item source route specified by $iid + * @param integer $iid + * item['id'] of target item + * @param string $hash + * xchan_hash of the channel that sent the item + * Modifies item pointed to by $iid + * + * $item['route'] contains a comma-separated list of xchans that sent the current message, + * somewhat analogous to the * Received: header line in email. We can use this to perform + * loop detection and to avoid sending a particular item to any "upstream" sender (they + * already have a copy because they sent it to us). + * + */ +function add_source_route($iid,$hash) { +// logger('add_source_route ' . $iid . ' ' . $hash, LOGGER_DEBUG); + if((! $iid) || (! $hash)) + return; + $r = q("select route from item where id = %d limit 1", + intval($iid) + ); + if($r) { + $new_route = (($r[0]['route']) ? $r[0]['route'] . ',' : '') . $hash; + q("update item set route = '%s' where id = %d limit 1", + (dbesc($new_route)), + intval($iid) + ); + } +} -} -function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) { +/** + * @function red_zrl_callback + * preg_match function when fixing 'naked' links in mod item.php + * Check if we've got a hubloc for the site and use a zrl if we do, a url if we don't. + * Remove any existing zid= param which may have been pasted by mistake - and will have + * the author's credentials. zid's are dynamic and can't really be passed around like + * that. + */ - $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic - $public_feed = (($dfrn_id) ? false : true); - $starred = false; // not yet implemented, possible security issues - $converse = false; +function red_zrl_callback($matches) { + $m = @parse_url($matches[2]); + $zrl = false; + if($m['host']) { + $r = q("select hubloc_url from hubloc where hubloc_host = '%s' limit 1", + dbesc($m['host']) + ); + if($r) + $zrl = true; + } - if($public_feed && $a->argc > 2) { - for($x = 2; $x < $a->argc; $x++) { - if($a->argv[$x] == 'converse') - $converse = true; - if($a->argv[$x] == 'starred') - $starred = true; - if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1])) - $category = $a->argv[$x+1]; - } + $t = strip_zids($matches[2]); + if($t !== $matches[2]) { + $zrl = true; + $matches[2] = $t; } - + if($matches[1] === '#^') + $matches[1] = ''; + if($zrl) + return $matches[1] . '#^[zrl=' . $matches[2] . ']' . $matches[2] . '[/zrl]'; + return $matches[1] . '#^[url=' . $matches[2] . ']' . $matches[2] . '[/url]'; +} - // default permissions - anonymous user - $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' "; +// If we've got a url or zrl tag with a naked url somewhere in the link text, +// escape it with quotes unless the naked url is a linked photo. - $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags` - FROM `contact` LEFT JOIN `user` ON `user`.`uid` = `contact`.`uid` - WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1", - dbesc($owner_nick) - ); +function red_escape_zrl_callback($matches) { - if(! count($r)) - killme(); + // Uncertain why the url/zrl forms weren't picked up by the non-greedy regex. - $owner = $r[0]; - $owner_id = $owner['user_uid']; - $owner_nick = $owner['nickname']; + if((strpos($matches[3],'zmg') !== false) || (strpos($matches[3],'img') !== false) || (strpos($matches[3],'zrl') !== false) || (strpos($matches[3],'url') !== false)) + return $matches[0]; + return '[' . $matches[1] . 'rl' . $matches[2] . ']' . $matches[3] . '"' . $matches[4] . '"' . $matches[5] . '[/' . $matches[6] . 'rl]'; +} - $birthday = feed_birthday($owner_id,$owner['timezone']); +function red_escape_codeblock($m) { + return '[$b64' . $m[2] . base64_encode($m[1]) . '[/' . $m[2] . ']'; +} - if(! $public_feed) { +function red_unescape_codeblock($m) { + return '[' . $m[2] . base64_decode($m[1]) . '[/' . $m[2] . ']'; + +} - $sql_extra = ''; - switch($direction) { - case (-1): - $sql_extra = sprintf(" AND `issued_id` = '%s' ", dbesc($dfrn_id)); - $my_id = $dfrn_id; - break; - case 0: - $sql_extra = sprintf(" AND `issued_id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id)); - $my_id = '1:' . $dfrn_id; - break; - case 1: - $sql_extra = sprintf(" AND `dfrn_id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id)); - $my_id = '0:' . $dfrn_id; - break; - default: - return false; - break; // NOTREACHED - } - $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1", - intval($owner_id) +function red_zrlify_img_callback($matches) { + $m = @parse_url($matches[2]); + $zrl = false; + if($m['host']) { + $r = q("select hubloc_url from hubloc where hubloc_host = '%s' limit 1", + dbesc($m['host']) ); + if($r) + $zrl = true; + } - if(! count($r)) - killme(); + $t = strip_zids($matches[2]); + if($t !== $matches[2]) { + $zrl = true; + $matches[2] = $t; + } - $contact = $r[0]; - require_once('include/security.php'); - $groups = init_groups_visitor($contact['id']); + if($zrl) + return '[zmg' . $matches[1] . ']' . $matches[2] . '[/zmg]'; + return $matches[0]; +} - if(count($groups)) { - for($x = 0; $x < count($groups); $x ++) - $groups[$x] = '<' . intval($groups[$x]) . '>' ; - $gs = implode('|', $groups); - } + + + +/** + * @function post_activity_item($arr) + * + * post an activity + * + * @param array $arr + * + * In its simplest form one needs only to set $arr['body'] to post a note to the logged in channel's wall. + * Much more complex activities can be created. Permissions are checked. No filtering, tag expansion + * or other processing is performed. + * + * @returns array + * 'success' => true or false + * 'activity' => the resulting activity if successful + */ + +function post_activity_item($arr) { + + $ret = array('success' => false); + + $is_comment = false; + if((($arr['parent']) && $arr['parent'] != $arr['id']) || (($arr['parent_mid']) && $arr['parent_mid'] != $arr['mid'])) + $is_comment = true; + + if(! x($arr,'item_flags')) { + if($is_comment) + $arr['item_flags'] = ITEM_ORIGIN; else - $gs = '<<>>' ; // Impossible to match - - $sql_extra = sprintf(" - AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' ) - AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' ) - AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' ) - AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s') - ", - intval($contact['id']), - intval($contact['id']), - dbesc($gs), - dbesc($gs) - ); + $arr['item_flags'] = ITEM_ORIGIN | ITEM_WALL | ITEM_THREAD_TOP; + } + + + $channel = get_app()->get_channel(); + $observer = get_app()->get_observer(); + + $arr['aid'] = ((x($arr,'aid')) ? $arr['aid'] : $channel['channel_account_id']); + $arr['uid'] = ((x($arr,'uid')) ? $arr['uid'] : $channel['channel_id']); + + if(! perm_is_allowed($arr['uid'],$observer['xchan_hash'],(($is_comment) ? 'post_comments' : 'post_wall'))) { + $ret['message'] = t('Permission denied'); + return $ret; } - if($public_feed) - $sort = 'DESC'; - else - $sort = 'ASC'; - if(! strlen($last_update)) - $last_update = 'now -30 days'; + if(! array_key_exists('mimetype',$arr)) + $arr['mimetype'] = 'text/bbcode'; + + if(array_key_exists('item_private',$arr) && $arr['item_private']) { + + $arr['body'] = z_input_filter($arr['uid'],$arr['body'],$arr['mimetype']); + + if($channel) { + if($channel['channel_hash'] === $arr['author_xchan']) { + $arr['sig'] = base64url_encode(rsa_sign($arr['body'],$channel['channel_prvkey'])); + $arr['item_flags'] = $arr['item_flags'] | ITEM_VERIFIED; + } + } - if(isset($category)) { - $sql_extra .= file_tag_file_query('item',$category,'category'); + logger('Encrypting local storage'); + $key = get_config('system','pubkey'); + $arr['item_flags'] = $arr['item_flags'] | ITEM_OBSCURED; + if($arr['title']) + $arr['title'] = json_encode(aes_encapsulate($arr['title'],$key)); + if($arr['body']) + $arr['body'] = json_encode(aes_encapsulate($arr['body'],$key)); } - if($public_feed) { - if(! $converse) - $sql_extra .= " AND `contact`.`self` = 1 "; + $arr['mid'] = ((x($arr,'mid')) ? $arr['mid'] : item_message_id()); + $arr['parent_mid'] = ((x($arr,'parent_mid')) ? $arr['parent_mid'] : $arr['mid']); + $arr['thr_parent'] = ((x($arr,'thr_parent')) ? $arr['thr_parent'] : $arr['mid']); + + $arr['owner_xchan'] = ((x($arr,'owner_xchan')) ? $arr['owner_xchan'] : $channel['channel_hash']); + $arr['author_xchan'] = ((x($arr,'author_xchan')) ? $arr['author_xchan'] : $observer['xchan_hash']); + + $arr['verb'] = ((x($arr,'verb')) ? $arr['verb'] : ACTIVITY_POST); + $arr['obj_type'] = ((x($arr,'obj_type')) ? $arr['obj_type'] : ACTIVITY_OBJ_NOTE); + + $arr['allow_cid'] = ((x($arr,'allow_cid')) ? $arr['allow_cid'] : $channel['channel_allow_cid']); + $arr['allow_gid'] = ((x($arr,'allow_gid')) ? $arr['allow_gid'] : $channel['channel_allow_gid']); + $arr['deny_cid'] = ((x($arr,'deny_cid')) ? $arr['deny_cid'] : $channel['channel_deny_cid']); + $arr['deny_gid'] = ((x($arr,'deny_gid')) ? $arr['deny_gid'] : $channel['channel_deny_gid']); + + $arr['comment_policy'] = map_scope($channel['channel_w_comment']); + + + if ((! $arr['plink']) && ($arr['item_flags'] & ITEM_THREAD_TOP)) { + $arr['plink'] = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . $arr['mid']; } - $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s'); - $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, - `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`, - `contact`.`name_date`, `contact`.`uri_date`, `contact`.`avatar_date`, - `contact`.`thumb`, `contact`.`dfrn_id`, `contact`.`self`, - `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`, - `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer` - FROM `item` LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id` - WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0 - AND `item`.`wall` = 1 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 - AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' ) - $sql_extra - ORDER BY `parent` %s, `created` ASC LIMIT 0, 300", - intval($owner_id), - dbesc($check_date), - dbesc($check_date), - dbesc($sort) - ); + // for the benefit of plugins, we will behave as if this is an API call rather than a normal online post - // Will check further below if this actually returned results. - // We will provide an empty feed if that is the case. + $_REQUEST['api_source'] = 1; - $items = $r; - $items = fetch_post_tags($items); + call_hooks('post_local',$arr); - $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl'); + if(x($arr,'cancel')) { + logger('post_activity_item: post cancelled by plugin.'); + return $ret; + } - $atom = ''; - $hubxml = feed_hublinks(); + $post = item_store($arr); + if($post['success']) + $post_id = $post['item_id']; - $salmon = feed_salmonlinks($owner_nick); + if($post_id) { + $arr['id'] = $post_id; + call_hooks('post_local_end', $arr); + proc_run('php','include/notifier.php','activity',$post_id); + $ret['success'] = true; + $r = q("select * from item where id = %d limit 1", + intval($post_id) + ); + if($r) + $ret['activity'] = $r[0]; + } - $atom .= replace_macros($feed_template, array( - '$version' => xmlify(FRIENDICA_VERSION), - '$feed_id' => xmlify($a->get_baseurl() . '/channel/' . $owner_nick), - '$feed_title' => xmlify($owner['name']), - '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) , - '$hub' => $hubxml, - '$salmon' => $salmon, - '$name' => xmlify($owner['name']), - '$profile_page' => xmlify($owner['url']), - '$photo' => xmlify($owner['photo']), - '$thumb' => xmlify($owner['thumb']), - '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar_date'] . '+00:00' , ATOM_TIME)) , - '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri_date'] . '+00:00' , ATOM_TIME)) , - '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name_date'] . '+00:00' , ATOM_TIME)) , - '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''), - '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '') - )); + return $ret; - call_hooks('atom_feed', $atom); +} - if(! count($items)) { +/** + * @function get_public_feed($channel,$params) + * generate an Atom feed + */ - call_hooks('atom_feed_end', $atom); +function get_public_feed($channel,$params) { - $atom .= '</feed>' . "\r\n"; - return $atom; + $type = 'xml'; + $begin = '0000-00-00 00:00:00'; + $end = ''; + $start = 0; + $records = 40; + $direction = 'desc'; + $pages = 0; + + if(! $params) + $params = array(); + + $params['type'] = ((x($params,'type')) ? $params['type'] : 'xml'); + $params['begin'] = ((x($params,'begin')) ? $params['begin'] : '0000-00-00 00:00:00'); + $params['end'] = ((x($params,'end')) ? $params['end'] : datetime_convert('UTC','UTC','now')); + $params['start'] = ((x($params,'start')) ? $params['start'] : 0); + $params['records'] = ((x($params,'records')) ? $params['records'] : 40); + $params['direction'] = ((x($params,'direction')) ? $params['direction'] : 'desc'); + $params['pages'] = ((x($params,'pages')) ? intval($params['pages']) : 0); + + switch($params['type']) { + case 'json': + header("Content-type: application/atom+json"); + break; + case 'xml': + default: + header("Content-type: application/atom+xml"); + break; } - foreach($items as $item) { + + return get_feed_for($channel,get_observer_hash(),$params); +} - // prevent private email from leaking. - if($item['network'] === NETWORK_MAIL) - continue; - // public feeds get html, our own nodes use bbcode - if($public_feed) { - $type = 'html'; - // catch any email that's in a public conversation and make sure it doesn't leak - if($item['private']) + +function get_feed_for($channel, $observer_hash, $params) { + + if(! channel) + http_status_exit(401); + + + if($params['pages']) { + if(! perm_is_allowed($channel['channel_id'],$observer_hash,'view_pages')) + http_status_exit(403); + } + else { + if(! perm_is_allowed($channel['channel_id'],$observer_hash,'view_stream')) + http_status_exit(403); + } + $items = items_fetch(array( + 'wall' => '1', + 'datequery' => $params['begin'], + 'datequery2' => $params['end'], + 'start' => $params['start'], // FIXME + 'records' => $params['records'], // FIXME + 'direction' => $params['direction'], // FIXME + 'pages' => $params['pages'], + 'order' => 'post' + ), $channel, $observer_hash, CLIENT_MODE_NORMAL, get_app()->module); + + + $feed_template = get_markup_template('atom_feed.tpl'); + + $atom = ''; + + $atom .= replace_macros($feed_template, array( + '$version' => xmlify(RED_VERSION), + '$red' => xmlify(RED_PLATFORM), + '$feed_id' => xmlify($channel['channel_url']), + '$feed_title' => xmlify($channel['channel_name']), + '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) , + '$hub' => '', // feed_hublinks(), + '$salmon' => '', // feed_salmonlinks($channel['channel_address']), + '$name' => xmlify($channel['channel_name']), + '$profile_page' => xmlify($channel['channel_url']), + '$mimephoto' => xmlify($channel['xchan_photo_mimetype']), + '$photo' => xmlify($channel['xchan_photo_l']), + '$thumb' => xmlify($channel['xchan_photo_m']), + '$picdate' => '', + '$uridate' => '', + '$namdate' => '', + '$birthday' => '', + '$community' => '', + )); + + call_hooks('atom_feed', $atom); + + if($items) { + $type = 'html'; + foreach($items as $item) { + if($item['item_private']) continue; - } - else { - $type = 'text'; - } - $atom .= atom_entry($item,$type,null,$owner,true); + $atom .= atom_entry($item,$type,null,$owner,true); + } } call_hooks('atom_feed_end', $atom); @@ -287,8 +509,7 @@ function construct_activity_object($item) { if($item['object']) { $o = '<as:object>' . "\r\n"; - $r = parse_xml_string($item['object'],false); - + $r = json_decode($item['object'],false); if(! $r) return ''; @@ -298,7 +519,8 @@ function construct_activity_object($item) { $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n"; if($r->title) $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n"; - if($r->link) { + if($r->links) { + // FIXME!! if(substr($r->link,0,1) === '<') { $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link); $o .= $r->link; @@ -319,7 +541,7 @@ function construct_activity_target($item) { if($item['target']) { $o = '<as:target>' . "\r\n"; - $r = parse_xml_string($item['target'],false); + $r = json_decode($item['target'],false); if(! $r) return ''; if($r->type) @@ -328,7 +550,8 @@ function construct_activity_target($item) { $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n"; if($r->title) $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n"; - if($r->link) { + if($r->links) { + // FIXME !!! if(substr($r->link,0,1) === '<') { if(strstr($r->link,'&') && (! strstr($r->link,'&'))) $r->link = str_replace('&','&', $r->link); @@ -352,7 +575,7 @@ function construct_activity_target($item) { * The purpose of this function is to apply system message length limits to * imported messages without including any embedded photos in the length */ -if(! function_exists('limit_body_size')) { + function limit_body_size($body) { $maxlen = get_max_import_size(); @@ -430,7 +653,7 @@ function limit_body_size($body) { } else return $body; -}} +} function title_is_body($title, $body) { @@ -459,34 +682,50 @@ function title_is_body($title, $body) { function get_item_elements($x) { - $arr = array(); - $arr['body'] = (($x['body']) ? htmlentities($x['body'],ENT_COMPAT,'UTF-8') : ''); + $arr = array(); + $arr['body'] = (($x['body']) ? htmlspecialchars($x['body'],ENT_COMPAT,'UTF-8',false) : ''); $arr['created'] = datetime_convert('UTC','UTC',$x['created']); $arr['edited'] = datetime_convert('UTC','UTC',$x['edited']); - $arr['expires'] = ((x($x,'expires') && $x['expires']) - ? datetime_convert('UTC','UTC',$x['expires']) - : '0000-00-00 00:00:00'); if($arr['created'] > datetime_convert()) $arr['created'] = datetime_convert(); if($arr['edited'] > datetime_convert()) $arr['edited'] = datetime_convert(); - $arr['title'] = (($x['title']) ? htmlentities($x['title'], ENT_COMPAT,'UTF-8') : ''); - $arr['app'] = (($x['app']) ? htmlentities($x['app'], ENT_COMPAT,'UTF-8') : ''); - $arr['uri'] = (($x['message_id']) ? htmlentities($x['message_id'], ENT_COMPAT,'UTF-8') : ''); - $arr['parent_uri'] = (($x['message_top']) ? htmlentities($x['message_top'], ENT_COMPAT,'UTF-8') : ''); - $arr['thr_parent'] = (($x['message_parent']) ? htmlentities($x['message_parent'], ENT_COMPAT,'UTF-8') : ''); + $arr['expires'] = ((x($x,'expires') && $x['expires']) + ? datetime_convert('UTC','UTC',$x['expires']) + : '0000-00-00 00:00:00'); + + $arr['commented'] = ((x($x,'commented') && $x['commented']) + ? datetime_convert('UTC','UTC',$x['commented']) + : $arr['created']); + + $arr['title'] = (($x['title']) ? htmlspecialchars($x['title'], ENT_COMPAT,'UTF-8',false) : ''); + + if(mb_strlen($arr['title']) > 255) + $arr['title'] = mb_substr($arr['title'],0,255); + + + $arr['app'] = (($x['app']) ? htmlspecialchars($x['app'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['route'] = (($x['route']) ? htmlspecialchars($x['route'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['mid'] = (($x['message_id']) ? htmlspecialchars($x['message_id'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['parent_mid'] = (($x['message_top']) ? htmlspecialchars($x['message_top'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['thr_parent'] = (($x['message_parent']) ? htmlspecialchars($x['message_parent'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['plink'] = (($x['permalink']) ? htmlentities($x['permalink'], ENT_COMPAT,'UTF-8') : ''); - $arr['location'] = (($x['location']) ? htmlentities($x['location'], ENT_COMPAT,'UTF-8') : ''); - $arr['coord'] = (($x['longlat']) ? htmlentities($x['longlat'], ENT_COMPAT,'UTF-8') : ''); - $arr['verb'] = (($x['verb']) ? htmlentities($x['verb'], ENT_COMPAT,'UTF-8') : ''); - $arr['obj_type'] = (($x['object_type']) ? htmlentities($x['object_type'], ENT_COMPAT,'UTF-8') : ''); - $arr['tgt_type'] = (($x['target_type']) ? htmlentities($x['target_type'], ENT_COMPAT,'UTF-8') : ''); + $arr['plink'] = (($x['permalink']) ? htmlspecialchars($x['permalink'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['location'] = (($x['location']) ? htmlspecialchars($x['location'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['coord'] = (($x['longlat']) ? htmlspecialchars($x['longlat'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['verb'] = (($x['verb']) ? htmlspecialchars($x['verb'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['mimetype'] = (($x['mimetype']) ? htmlspecialchars($x['mimetype'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['obj_type'] = (($x['object_type']) ? htmlspecialchars($x['object_type'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['tgt_type'] = (($x['target_type']) ? htmlspecialchars($x['target_type'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['comment_policy'] = (($x['comment_scope']) ? htmlspecialchars($x['comment_scope'], ENT_COMPAT,'UTF-8',false) : 'contacts'); + $arr['sig'] = (($x['signature']) ? htmlspecialchars($x['signature'], ENT_COMPAT,'UTF-8',false) : ''); + + $arr['object'] = activity_sanitise($x['object']); $arr['target'] = activity_sanitise($x['target']); @@ -495,6 +734,9 @@ function get_item_elements($x) { $arr['item_private'] = ((array_key_exists('flags',$x) && is_array($x['flags']) && in_array('private',$x['flags'])) ? 1 : 0); + $arr['item_flags'] = 0; + + if(array_key_exists('flags',$x) && in_array('deleted',$x['flags'])) $arr['item_restrict'] = ITEM_DELETED; @@ -504,16 +746,45 @@ function get_item_elements($x) { // once, and after that your hub knows them. Sure some info is in the post, but it's only a transit identifier // and not enough info to be able to look you up from your hash - which is the only thing stored with the post. - if(import_author_xchan($x['author'])) - $arr['author_xchan'] = base64url_encode(hash('whirlpool',$x['author']['guid'] . $x['author']['guid_sig'], true)); + if(($xchan_hash = import_author_xchan($x['author'])) !== false) + $arr['author_xchan'] = $xchan_hash; else return array(); - if(import_author_xchan($x['owner'])) - $arr['owner_xchan'] = base64url_encode(hash('whirlpool',$x['owner']['guid'] . $x['owner']['guid_sig'], true)); - else - return array(); + // save a potentially expensive lookup if author == owner + if($arr['author_xchan'] === base64url_encode(hash('whirlpool',$x['owner']['guid'] . $x['owner']['guid_sig'], true))) + $arr['owner_xchan'] = $arr['author_xchan']; + else { + if(($xchan_hash = import_author_xchan($x['owner'])) !== false) + $arr['owner_xchan'] = $xchan_hash; + else + return array(); + } + + + if($arr['sig']) { + $r = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", + dbesc($arr['author_xchan']) + ); + if($r && rsa_verify($x['body'],base64url_decode($arr['sig']),$r[0]['xchan_pubkey'])) + $arr['item_flags'] |= ITEM_VERIFIED; + else + logger('get_item_elements: message verification failed.'); + } + + // if it's a private post, encrypt it in the DB. + // We have to do that here because we need to cleanse the input and prevent bad stuff from getting in, + // and we need plaintext to do that. + + if(intval($arr['item_private'])) { + $arr['item_flags'] = $arr['item_flags'] | ITEM_OBSCURED; + $key = get_config('system','pubkey'); + if($arr['title']) + $arr['title'] = json_encode(crypto_encapsulate($arr['title'],$key)); + if($arr['body']) + $arr['body'] = json_encode(crypto_encapsulate($arr['body'],$key)); + } return $arr; @@ -521,35 +792,106 @@ function get_item_elements($x) { function import_author_xchan($x) { - $r = q("select hubloc_url from hubloc where hubloc_guid = '%s' and hubloc_guid_sig = '%s' and (hubloc_flags & %d) limit 1", - dbesc($x['guid']), - dbesc($x['guid_sig']), - intval(HUBLOG_FLAGS_PRIMARY) + + $arr = array('xchan' => $x, 'xchan_hash' => ''); + call_hooks('import_author_xchan',$arr); + if($arr['xchan_hash']) + return $arr['xchan_hash']; + + if((! array_key_exists('network', $x)) || ($x['network'] === 'zot')) { + $y = import_author_zot($x); + } + + if($x['network'] === 'rss') { + $y = import_author_rss($x); + } + + return(($y) ? $y : false); +} + +function import_author_rss($x) { + + if(! $x['url']) + return false; + + $r = q("select xchan_hash from xchan where xchan_network = 'rss' and xchan_url = '%s' limit 1", + dbesc($x['url']) ); - if($r) - return true; - $them = array('hubloc_url' => $x['url'],'xchan_guid' => $x['guid'], 'xchan_guid_sig' => $x['guid_sig']); - return zot_refresh($them); + if($r) { + logger('import_author_rss: in cache' , LOGGER_DEBUG); + return $r[0]['xchan_hash']; + } + $name = trim($x['name']); + + $r = q("insert into xchan ( xchan_hash, xchan_url, xchan_name, xchan_network ) + values ( '%s', '%s', '%s', '%s' )", + dbesc($x['url']), + dbesc($x['url']), + dbesc(($name) ? $name : t('(Unknown)')), + dbesc('rss') + ); + if($r) { + + $photos = import_profile_photo($x['photo'],$x['url']); + + if($photos) { + $r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' where xchan_url = '%s' and xchan_network = 'rss' limit 1", + dbesc(datetime_convert('UTC','UTC',$arr['photo_updated'])), + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc($photos[3]), + dbesc($x['url']) + ); + if($r) + return $x['url']; + } + } + + return false; + } + function encode_item($item) { $x = array(); $x['type'] = 'activity'; - logger('encode_item: ' . print_r($item,true)); +// logger('encode_item: ' . print_r($item,true)); + + $r = q("select channel_r_stream, channel_w_comment from channel where channel_id = %d limit 1", + intval($item['uid']) + ); + + if($r) { + $public_scope = $r[0]['channel_r_stream']; + $comment_scope = $r[0]['channel_w_comment']; + } + else { + $public_scope = 0; + $comment_scope = 0; + } + + $scope = map_scope($public_scope); + $c_scope = map_scope($comment_scope); - if($item['item_restrict'] & ITEM_DELETED) { - $x['message_id'] = $item['uri']; - $x['flags'] = array('deleted'); - return $x; + if(array_key_exists('item_flags',$item) && ($item['item_flags'] & ITEM_OBSCURED)) { + $key = get_config('system','prvkey'); + if($item['title']) + $item['title'] = crypto_unencapsulate(json_decode_plus($item['title']),$key); + if($item['body']) + $item['body'] = crypto_unencapsulate(json_decode_plus($item['body']),$key); } - $x['message_id'] = $item['uri']; - $x['message_top'] = $item['parent_uri']; + + $x['message_id'] = $item['mid']; + $x['message_top'] = $item['parent_mid']; $x['message_parent'] = $item['thr_parent']; $x['created'] = $item['created']; $x['edited'] = $item['edited']; $x['expires'] = $item['expires']; + $x['commented'] = $item['commented']; + $x['mimetype'] = $item['mimetype']; $x['title'] = $item['title']; $x['body'] = $item['body']; $x['app'] = $item['app']; @@ -559,30 +901,65 @@ function encode_item($item) { $x['permalink'] = $item['plink']; $x['location'] = $item['location']; $x['longlat'] = $item['coord']; + $x['signature'] = $item['sig']; + $x['route'] = $item['route']; $x['owner'] = encode_item_xchan($item['owner']); $x['author'] = encode_item_xchan($item['author']); if($item['object']) - $x['object'] = json_decode($item['object'],true); + $x['object'] = json_decode_plus($item['object']); if($item['target']) - $x['target'] = json_decode($item['target'],true); + $x['target'] = json_decode_plus($item['target']); if($item['attach']) - $x['attach'] = json_decode($item['attach'],true); + $x['attach'] = json_decode_plus($item['attach']); if($y = encode_item_flags($item)) $x['flags'] = $y; + + if(! in_array('private',$y)) + $x['public_scope'] = $scope; + + if($item['item_flags'] & ITEM_NOCOMMENT) + $x['comment_scope'] = 'none'; + else + $x['comment_scope'] = $c_scope; + if($item['term']) $x['tags'] = encode_item_terms($item['term']); + logger('encode_item: ' . print_r($x,true), LOGGER_DATA); + return $x; } + +function map_scope($scope) { + switch($scope) { + case 0: + return 'self'; + case PERMS_PUBLIC: + return 'public'; + case PERMS_NETWORK: + return 'network: red'; + case PERMS_SITE: + return 'site: ' . get_app()->get_hostname(); + case PERMS_PENDING: + return 'any connections'; + case PERMS_CONTACTS: + default: + return 'contacts'; + } +} + + + function encode_item_xchan($xchan) { $ret = array(); $ret['name'] = $xchan['xchan_name']; $ret['address'] = $xchan['xchan_addr']; $ret['url'] = $xchan['hubloc_url']; + $ret['network'] = $xchan['xchan_network']; $ret['photo'] = array('mimetype' => $xchan['xchan_photo_mimetype'], 'src' => $xchan['xchan_photo_m']); $ret['guid'] = $xchan['xchan_guid']; $ret['guid_sig'] = $xchan['xchan_guid_sig']; @@ -592,7 +969,7 @@ function encode_item_xchan($xchan) { function encode_item_terms($terms) { $ret = array(); - $allowed_export_terms = array( TERM_UNKNOWN, TERM_HASHTAG, TERM_MENTION, TERM_CATEGORY ); + $allowed_export_terms = array( TERM_UNKNOWN, TERM_HASHTAG, TERM_MENTION, TERM_CATEGORY, TERM_BOOKMARK ); if($terms) { foreach($terms as $term) { @@ -604,7 +981,7 @@ function encode_item_terms($terms) { } function termtype($t) { - $types = array('unknown','hashtag','mention','category','private_category','file','search'); + $types = array('unknown','hashtag','mention','category','private_category','file','search','thing','bookmark'); return(($types[$t]) ? $types[$t] : 'unknown'); } @@ -614,8 +991,8 @@ function decode_tags($t) { $ret = array(); foreach($t as $x) { $tag = array(); - $tag['term'] = htmlentities($x['term'], ENT_COMPAT,'UTF-8'); - $tag['url'] = htmlentities($x['url'], ENT_COMPAT,'UTF-8'); + $tag['term'] = htmlspecialchars($x['tag'], ENT_COMPAT,'UTF-8',false); + $tag['url'] = htmlspecialchars($x['url'], ENT_COMPAT,'UTF-8',false); switch($x['type']) { case 'hashtag': $tag['type'] = TERM_HASHTAG; @@ -635,6 +1012,12 @@ function decode_tags($t) { case 'search': $tag['type'] = TERM_SEARCH; break; + case 'thing': + $tag['type'] = TERM_THING; + break; + case 'bookmark': + $tag['type'] = TERM_BOOKMARK; + break; default: case 'unknown': $tag['type'] = TERM_UNKNOWN; @@ -652,14 +1035,19 @@ function decode_tags($t) { function activity_sanitise($arr) { if($arr) { - $ret = array(); - foreach($arr as $k => $x) { - if(is_array($x)) - $ret[$k] = activity_sanitise($x); - else - $ret[$k] = htmlentities($x, ENT_COMPAT,'UTF-8'); + if(is_array($arr)) { + $ret = array(); + foreach($arr as $k => $x) { + if(is_array($x)) + $ret[$k] = activity_sanitise($x); + else + $ret[$k] = htmlspecialchars($x, ENT_COMPAT,'UTF-8',false); + } + return $ret; + } + else { + return htmlspecialchars($arr, ENT_COMPAT,'UTF-8', false); } - return $ret; } return ''; } @@ -670,7 +1058,7 @@ function array_sanitise($arr) { if($arr) { $ret = array(); foreach($arr as $x) { - $ret[] = htmlentities($x, ENT_COMPAT,'UTF-8'); + $ret[] = htmlspecialchars($x, ENT_COMPAT,'UTF-8',false); } return $ret; } @@ -681,9 +1069,11 @@ function encode_item_flags($item) { // most of item_flags and item_restrict are local settings which don't apply when transmitted. // We may need those for the case of syncing other hub locations which you are attached to. -// ITEM_DELETED is handled in encode_item directly so we don't need to handle it here. $ret = array(); + + if($item['item_restrict'] & ITEM_DELETED) + $ret[] = 'deleted'; if($item['item_flags'] & ITEM_THREAD_TOP) $ret[] = 'thread_parent'; if($item['item_flags'] & ITEM_NSFW) @@ -698,16 +1088,34 @@ function encode_mail($item) { $x = array(); $x['type'] = 'mail'; - logger('encode_mail: ' . print_r($item,true)); + if(array_key_exists('mail_flags',$item) && ($item['mail_flags'] & MAIL_OBSCURED)) { + $key = get_config('system','prvkey'); + if($item['title']) + $item['title'] = crypto_unencapsulate(json_decode_plus($item['title']),$key); + if($item['body']) + $item['body'] = crypto_unencapsulate(json_decode_plus($item['body']),$key); + } - $x['message_id'] = $item['uri']; - $x['message_parent'] = $item['parent_uri']; + $x['message_id'] = $item['mid']; + $x['message_parent'] = $item['parent_mid']; $x['created'] = $item['created']; + $x['expires'] = $item['expires']; $x['title'] = $item['title']; $x['body'] = $item['body']; $x['from'] = encode_item_xchan($item['from']); $x['to'] = encode_item_xchan($item['to']); + if($item['attach']) + $x['attach'] = json_decode_plus($item['attach']); + + $x['flags'] = array(); + + if($item['mail_flags'] & MAIL_RECALLED) { + $x['flags'][] = 'recalled'; + $x['title'] = ''; + $x['body'] = ''; + } + return $x; } @@ -717,29 +1125,51 @@ function get_mail_elements($x) { $arr = array(); - $arr['body'] = (($x['body']) ? htmlentities($x['body'],ENT_COMPAT,'UTF-8') : ''); + $arr['body'] = (($x['body']) ? htmlspecialchars($x['body'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['title'] = (($x['title'])? htmlspecialchars($x['title'],ENT_COMPAT,'UTF-8',false) : ''); $arr['created'] = datetime_convert('UTC','UTC',$x['created']); + if((! array_key_exists('expires',$x)) || ($x['expires'] === '0000-00-00 00:00:00')) + $arr['expires'] = '0000-00-00 00:00:00'; + else + $arr['expires'] = datetime_convert('UTC','UTC',$x['expires']); + + $arr['mail_flags'] = 0; + + if($x['flags'] && is_array($x['flags'])) { + if(in_array('recalled',$x['flags'])) { + $arr['mail_flags'] |= MAIL_RECALLED; + } + } + + $key = get_config('system','pubkey'); + $arr['mail_flags'] |= MAIL_OBSCURED; + $arr['body'] = htmlspecialchars($arr['body'],ENT_COMPAT,'UTF-8',false); + if($arr['body']) + $arr['body'] = json_encode(crypto_encapsulate($arr['body'],$key)); + $arr['title'] = htmlspecialchars($arr['title'],ENT_COMPAT,'UTF-8',false); + if($arr['title']) + $arr['title'] = json_encode(crypto_encapsulate($arr['title'],$key)); if($arr['created'] > datetime_convert()) $arr['created'] = datetime_convert(); - $arr['title'] = (($x['title']) ? htmlentities($x['title'], ENT_COMPAT,'UTF-8') : ''); - $arr['uri'] = (($x['message_id']) ? htmlentities($x['message_id'], ENT_COMPAT,'UTF-8') : ''); - $arr['parent_uri'] = (($x['message_parent']) ? htmlentities($x['message_parent'], ENT_COMPAT,'UTF-8') : ''); + $arr['mid'] = (($x['message_id']) ? htmlspecialchars($x['message_id'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['parent_mid'] = (($x['message_parent']) ? htmlspecialchars($x['message_parent'], ENT_COMPAT,'UTF-8',false) : ''); + if($x['attach']) + $arr['attach'] = activity_sanitise($x['attach']); - if(import_author_xchan($x['from'])) - $arr['from_xchan'] = base64url_encode(hash('whirlpool',$x['from']['guid'] . $x['from']['guid_sig'], true)); + if(($xchan_hash = import_author_xchan($x['from'])) !== false) + $arr['from_xchan'] = $xchan_hash; else return array(); - if(import_author_xchan($x['to'])) - $arr['to_xchan'] = base64url_encode(hash('whirlpool',$x['to']['guid'] . $x['to']['guid_sig'], true)); + if(($xchan_hash = import_author_xchan($x['to'])) !== false) + $arr['to_xchan'] = $xchan_hash; else return array(); - return $arr; } @@ -749,22 +1179,23 @@ function get_profile_elements($x) { $arr = array(); - if(import_author_xchan($x['from'])) - $arr['xprof_hash'] = base64url_encode(hash('whirlpool',$x['from']['guid'] . $x['from']['guid_sig'], true)); + if(($xchan_hash = import_author_xchan($x['from'])) !== false) + $arr['xprof_hash'] = $xchan_hash; else return array(); - $arr['desc'] = (($x['title']) ? htmlentities($x['title'],ENT_COMPAT,'UTF-8') : ''); + $arr['desc'] = (($x['title']) ? htmlspecialchars($x['title'],ENT_COMPAT,'UTF-8',false) : ''); $arr['dob'] = datetime_convert('UTC','UTC',$x['birthday'],'Y-m-d'); + $arr['age'] = (($x['age']) ? intval($x['age']) : 0); - $arr['gender'] = (($x['gender']) ? htmlentities($x['gender'], ENT_COMPAT,'UTF-8') : ''); - $arr['marital'] = (($x['marital']) ? htmlentities($x['marital'], ENT_COMPAT,'UTF-8') : ''); - $arr['sexual'] = (($x['sexual']) ? htmlentities($x['sexual'], ENT_COMPAT,'UTF-8') : ''); - $arr['locale'] = (($x['locale']) ? htmlentities($x['locale'], ENT_COMPAT,'UTF-8') : ''); - $arr['region'] = (($x['region']) ? htmlentities($x['region'], ENT_COMPAT,'UTF-8') : ''); - $arr['postcode'] = (($x['postcode']) ? htmlentities($x['postcode'], ENT_COMPAT,'UTF-8') : ''); - $arr['country'] = (($x['country']) ? htmlentities($x['country'], ENT_COMPAT,'UTF-8') : ''); + $arr['gender'] = (($x['gender']) ? htmlspecialchars($x['gender'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['marital'] = (($x['marital']) ? htmlspecialchars($x['marital'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['sexual'] = (($x['sexual']) ? htmlspecialchars($x['sexual'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['locale'] = (($x['locale']) ? htmlspecialchars($x['locale'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['region'] = (($x['region']) ? htmlspecialchars($x['region'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['postcode'] = (($x['postcode']) ? htmlspecialchars($x['postcode'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['country'] = (($x['country']) ? htmlspecialchars($x['country'], ENT_COMPAT,'UTF-8',false) : ''); $arr['keywords'] = (($x['keywords'] && is_array($x['keywords'])) ? array_sanitise($x['keywords']) : array()); @@ -776,8 +1207,6 @@ function get_profile_elements($x) { function get_atom_elements($feed,$item) { - require_once('library/HTMLPurifier.auto.php'); - require_once('include/html2bbcode.php'); $best_photo = array(); @@ -792,13 +1221,14 @@ function get_atom_elements($feed,$item) { $res['author-name'] = unxmlify($feed->get_title()); $res['author-link'] = unxmlify($feed->get_permalink()); } - $res['uri'] = unxmlify($item->get_id()); + $res['mid'] = unxmlify($item->get_id()); $res['title'] = unxmlify($item->get_title()); $res['body'] = unxmlify($item->get_content()); $res['plink'] = unxmlify($item->get_link(0)); // removing the content of the title if its identically to the body // This helps with auto generated titles e.g. from tumblr + if (title_is_body($res["title"], $res["body"])) $res['title'] = ""; @@ -913,14 +1343,7 @@ function get_atom_elements($feed,$item) { $res['body'] = oembed_html2bbcode($res['body']); - $config = HTMLPurifier_Config::createDefault(); - $config->set('Cache.DefinitionImpl', null); - - // we shouldn't need a whitelist, because the bbcode converter - // will strip out any unsupported tags. - - $purifier = new HTMLPurifier($config); - $res['body'] = $purifier->purify($res['body']); + $res['body'] = purify_html($res['body']); $res['body'] = @html2bbcode($res['body']); @@ -1090,14 +1513,9 @@ function get_atom_elements($feed,$item) { $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n"; if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) { - $body = html2bb_video($body); - - $config = HTMLPurifier_Config::createDefault(); - $config->set('Cache.DefinitionImpl', null); - - $purifier = new HTMLPurifier($config); - $body = $purifier->purify($body); + $body = purify_html($body); $body = html2bbcode($body); + } $res['object'] .= '<content>' . $body . '</content>' . "\n"; @@ -1128,13 +1546,7 @@ function get_atom_elements($feed,$item) { $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n"; if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) { - $body = html2bb_video($body); - - $config = HTMLPurifier_Config::createDefault(); - $config->set('Cache.DefinitionImpl', null); - - $purifier = new HTMLPurifier($config); - $body = $purifier->purify($body); + $body = purify_html($body); $body = html2bbcode($body); } @@ -1147,6 +1559,7 @@ function get_atom_elements($feed,$item) { // This is some experimental stuff. By now retweets are shown with "RT:" // But: There is data so that the message could be shown similar to native retweets // There is some better way to parse this array - but it didn't worked for me. + $child = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://activitystrea.ms/spec/1.0/"][object][0]["child"]; if (is_array($child)) { $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"]; @@ -1172,12 +1585,7 @@ function get_atom_elements($feed,$item) { $arr = array('feed' => $feed, 'item' => $item, 'result' => $res); call_hooks('parse_atom', $arr); - - //if (($res["title"] != "") or (strpos($res["body"], "RT @") > 0)) { - //if (strpos($res["body"], "RT @") !== false) { - // $debugfile = tempnam("/home/ike/log", "item-res2-"); - // file_put_contents($debugfile, serialize($arr)); - //} + logger('get_atom_elements: ' . print_r($res,true)); return $res; } @@ -1203,75 +1611,169 @@ function encode_rel_links($links) { return xmlify($o); } +function item_store($arr,$allow_exec = false) { + + $d = array('item' => $arr, 'allow_exec' => $allow_exec); + call_hooks('item_store', $d ); + $arr = $d['item']; + $allow_exec = $d['allow_exec']; -function item_store($arr,$force_parent = false) { + $ret = array('success' => false, 'item_id' => 0); if(! $arr['uid']) { logger('item_store: no uid'); - return 0; + $ret['message'] = 'No uid.'; + return ret; + } + + $uplinked_comment = false; + + // If a page layout is provided, ensure it exists and belongs to us. + + if(array_key_exists('layout_mid',$arr) && $arr['layout_mid']) { + $l = q("select item_restrict from item where mid = '%s' and uid = %d limit 1", + dbesc($arr['layout_mid']), + intval($arr['uid']) + ); + if((! $l) || (! ($l[0]['item_restrict'] & ITEM_PDL))) + unset($arr['layout_mid']); + } + + // Don't let anybody set these, either intentionally or accidentally + + if(array_key_exists('id',$arr)) + unset($arr['id']); + if(array_key_exists('parent',$arr)) + unset($arr['parent']); + + $arr['mimetype'] = ((x($arr,'mimetype')) ? notags(trim($arr['mimetype'])) : 'text/bbcode'); + + if(($arr['mimetype'] == 'application/x-php') && (! $allow_exec)) { + logger('item_store: php mimetype but allow_exec is denied.'); + $ret['message'] = 'exec denied.'; + return $ret; } - $arr['lang'] = detect_language($arr['body']); - $allowed_languages = get_pconfig($arr['uid'],'system','allowed_languages'); + $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : ''); + $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : ''); + + $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : ''); + $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : ''); + $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : ''); + $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : ''); + $arr['item_private'] = ((x($arr,'item_private')) ? intval($arr['item_private']) : 0 ); + $arr['item_flags'] = ((x($arr,'item_flags')) ? intval($arr['item_flags']) : 0 ); + + + + // only detect language if we have text content, and if the post is private but not yet + // obscured, make it so. + + if(! ($arr['item_flags'] & ITEM_OBSCURED)) { + + $arr['lang'] = detect_language($arr['body']); + // apply the input filter here - if it is obscured it has been filtered already + $arr['body'] = z_input_filter($arr['uid'],$arr['body'],$arr['mimetype']); + + + if(local_user() && (! $arr['sig'])) { + $channel = get_app()->get_channel(); + if($channel['channel_hash'] === $arr['author_xchan']) { + $arr['sig'] = base64url_encode(rsa_sign($arr['body'],$channel['channel_prvkey'])); + $arr['item_flags'] |= ITEM_VERIFIED; + } + } + + $allowed_languages = get_pconfig($arr['uid'],'system','allowed_languages'); - if((is_array($allowed_languages)) && ($arr['lang']) && (! array_key_exists($arr['lang'],$allowed_languages))) { - $translate = array('item' => $arr, 'from' => $arr['lang'], 'to' => $allowed_languages, 'translated' => false); - call_hooks('item_translate', $translate); - if((! $translate['translated']) && (intval(get_pconfig($arr['uid'],'system','reject_disallowed_languages')))) { - logger('item_store: language ' . $arr['lang'] . ' not accepted for uid ' . $arr['uid']); - return; + if((is_array($allowed_languages)) && ($arr['lang']) && (! array_key_exists($arr['lang'],$allowed_languages))) { + $translate = array('item' => $arr, 'from' => $arr['lang'], 'to' => $allowed_languages, 'translated' => false); + call_hooks('item_translate', $translate); + if((! $translate['translated']) && (intval(get_pconfig($arr['uid'],'system','reject_disallowed_languages')))) { + logger('item_store: language ' . $arr['lang'] . ' not accepted for uid ' . $arr['uid']); + $ret['message'] = 'language not accepted'; + return $ret; + } + $arr = $translate['item']; + } + if($arr['item_private']) { + $key = get_config('system','pubkey'); + $arr['item_flags'] = $arr['item_flags'] | ITEM_OBSCURED; + if($arr['title']) + $arr['title'] = json_encode(crypto_encapsulate($arr['title'],$key)); + if($arr['body']) + $arr['body'] = json_encode(crypto_encapsulate($arr['body'],$key)); } - $arr = $translate['item']; + } - // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin. + if((x($arr,'object')) && is_array($arr['object'])) { + activity_sanitise($arr['object']); + $arr['object'] = json_encode($arr['object']); + } - if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false)) - $arr['body'] = escape_tags($arr['body']); + if((x($arr,'target')) && is_array($arr['target'])) { + activity_sanitise($arr['target']); + $arr['target'] = json_encode($arr['target']); + } + + if((x($arr,'attach')) && is_array($arr['attach'])) { + activity_sanitise($arr['attach']); + $arr['attach'] = json_encode($arr['attach']); + } $arr['aid'] = ((x($arr,'aid')) ? intval($arr['aid']) : 0); - $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string()); + $arr['mid'] = ((x($arr,'mid')) ? notags(trim($arr['mid'])) : random_string()); $arr['author_xchan'] = ((x($arr,'author_xchan')) ? notags(trim($arr['author_xchan'])) : ''); $arr['owner_xchan'] = ((x($arr,'owner_xchan')) ? notags(trim($arr['owner_xchan'])) : ''); $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert()); $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert()); - $arr['expires'] = ((x($arr,'expires') !== false) ? datetime_convert('UTC','UTC',$arr['expires']) : '0000-00-00 00:00:00'); - $arr['commented'] = datetime_convert(); + $arr['expires'] = ((x($arr,'expires') !== false) ? datetime_convert('UTC','UTC',$arr['expires']) : '0000-00-00 00:00:00'); + $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert()); $arr['received'] = datetime_convert(); $arr['changed'] = datetime_convert(); - $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : ''); $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : ''); $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : ''); - $arr['parent_uri'] = ((x($arr,'parent_uri')) ? notags(trim($arr['parent_uri'])) : ''); + $arr['parent_mid'] = ((x($arr,'parent_mid')) ? notags(trim($arr['parent_mid'])) : ''); + $arr['thr_parent'] = ((x($arr,'thr_parent')) ? notags(trim($arr['thr_parent'])) : $arr['parent_mid']); $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : ''); $arr['obj_type'] = ((x($arr,'obj_type')) ? notags(trim($arr['obj_type'])) : ''); $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : ''); $arr['tgt_type'] = ((x($arr,'tgt_type')) ? notags(trim($arr['tgt_type'])) : ''); $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : ''); $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : ''); - $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : ''); - $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : ''); - $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : ''); - $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : ''); - $arr['item_private'] = ((x($arr,'item_private')) ? intval($arr['item_private']) : 0 ); - $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : ''); $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : ''); $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : ''); $arr['item_restrict'] = ((x($arr,'item_restrict')) ? intval($arr['item_restrict']) : 0 ); - $arr['item_flags'] = ((x($arr,'item_flags')) ? intval($arr['item_flags']) : 0 ); + + $arr['comment_policy'] = ((x($arr,'comment_policy')) ? notags(trim($arr['comment_policy'])) : 'contacts' ); + $arr['item_flags'] = $arr['item_flags'] | ITEM_UNSEEN; - - $arr['thr_parent'] = $arr['parent_uri']; - $arr['llink'] = z_root() . '/display/' . $arr['uri']; + if($arr['comment_policy'] == 'none') + $arr['item_flags'] = $arr['item_flags'] | ITEM_NOCOMMENT; + + + + // handle time travelers + // Allow a bit of fudge in case somebody just has a slightly slow/fast clock + + $d1 = new DateTime('now +10 minutes', new DateTimeZone('UTC')); + $d2 = new DateTime($arr['created'] . '+00:00'); + if($d2 > $d1) + $arr['item_restrict'] = $arr['item_restrict'] | ITEM_DELAYED_PUBLISH; + + $arr['llink'] = z_root() . '/display/' . $arr['mid']; if(! $arr['plink']) $arr['plink'] = $arr['llink']; - if($arr['parent_uri'] === $arr['uri']) { + + + if($arr['parent_mid'] === $arr['mid']) { $parent_id = 0; $parent_deleted = 0; $allow_cid = $arr['allow_cid']; @@ -1285,23 +1787,23 @@ function item_store($arr,$force_parent = false) { // find the parent and snarf the item id and ACL's // and anything else we need to inherit - $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1", - dbesc($arr['parent_uri']), + $r = q("SELECT * FROM `item` WHERE `mid` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1", + dbesc($arr['parent_mid']), intval($arr['uid']) ); - if(count($r)) { + if($r) { // is the new message multi-level threaded? // even though we don't support it now, preserve the info // and re-attach to the conversation parent. - if($r[0]['uri'] != $r[0]['parent_uri']) { - $arr['parent_uri'] = $r[0]['parent_uri']; - $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent_uri` = '%s' AND `uid` = %d + if($r[0]['mid'] != $r[0]['parent_mid']) { + $arr['parent_mid'] = $r[0]['parent_mid']; + $z = q("SELECT * FROM `item` WHERE `mid` = '%s' AND `parent_mid` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1", - dbesc($r[0]['parent_uri']), - dbesc($r[0]['parent_uri']), + dbesc($r[0]['parent_mid']), + dbesc($r[0]['parent_mid']), intval($arr['uid']) ); if($z && count($z)) @@ -1318,6 +1820,16 @@ function item_store($arr,$force_parent = false) { if($r[0]['item_flags'] & ITEM_WALL) $arr['item_flags'] = $arr['item_flags'] | ITEM_WALL; + + // An uplinked comment might arrive with a downstream owner. + // Fix it. + + if($r[0]['owner_xchan'] !== $arr['owner_xchan']) { + $arr['owner_xchan'] = $r[0]['owner_xchan']; + $uplinked_comment = true; + } + + // if the parent is private, force privacy for the entire conversation // This differs from the above settings as it subtly allows comments from // email correspondents to be private even if the overall thread is not. @@ -1333,22 +1845,9 @@ function item_store($arr,$force_parent = false) { $arr['item_private'] = 0; } else { - - // Allow one to see reply tweets from status.net even when - // we don't have or can't see the original post. - - if($force_parent) { - logger('item_store: $force_parent=true, reply converted to top-level post.'); - $parent_id = 0; - $arr['parent_uri'] = $arr['uri']; - $arr['flags'] = $arr['flags'] | ITEM_THREAD_TOP; - } - else { - logger('item_store: item parent was not found - ignoring item'); - return 0; - } - - $parent_deleted = 0; + logger('item_store: item parent was not found - ignoring item'); + $ret['message'] = 'parent not found.'; + return $ret; } } @@ -1356,20 +1855,25 @@ function item_store($arr,$force_parent = false) { $arr['item_restrict'] = $arr['item_restrict'] | ITEM_DELETED; - $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($arr['uri']), + $r = q("SELECT `id` FROM `item` WHERE `mid` = '%s' AND `uid` = %d LIMIT 1", + dbesc($arr['mid']), intval($arr['uid']) ); if($r) { - logger('item-store: duplicate item ignored. ' . print_r($arr,true)); - return 0; + logger('item_store: duplicate item ignored. ' . print_r($arr,true)); + $ret['message'] = 'duplicate post.'; + return $ret; } + call_hooks('item_store',$arr); + + // This hook remains for backward compatibility. call_hooks('post_remote',$arr); if(x($arr,'cancel')) { logger('item_store: post cancelled by plugin.'); - return 0; + $ret['message'] = 'cancelled.'; + return $ret; } // pull out all the taxonomy stuff for separate storage @@ -1380,10 +1884,10 @@ function item_store($arr,$force_parent = false) { unset($arr['term']); } - dbesc_array($arr); - logger('item_store: ' . print_r($arr,true), LOGGER_DATA); + dbesc_array($arr); + $r = dbq("INSERT INTO `item` (`" . implode("`, `", array_keys($arr)) . "`) VALUES ('" @@ -1392,40 +1896,43 @@ function item_store($arr,$force_parent = false) { // find the item we just created - $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ", - $arr['uri'], // already dbesc'd + $r = q("SELECT * FROM `item` WHERE `mid` = '%s' AND `uid` = %d ORDER BY `id` ASC ", + $arr['mid'], // already dbesc'd intval($arr['uid']) ); + if($r && count($r)) { $current_post = $r[0]['id']; + $arr = $r[0]; // This will gives us a fresh copy of what's now in the DB and undo the db escaping, which really messes up the notifications logger('item_store: created item ' . $current_post, LOGGER_DEBUG); } else { - logger('item_store: could not locate created item'); - return 0; + logger('item_store: could not locate stored item'); + $ret['message'] = 'unable to retrieve.'; + return $ret; } if(count($r) > 1) { logger('item_store: duplicated post occurred. Removing duplicates.'); - q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ", - $arr['uri'], + q("DELETE FROM `item` WHERE `mid` = '%s' AND `uid` = %d AND `id` != %d ", + $arr['mid'], intval($arr['uid']), intval($current_post) ); } - if((! $parent_id) || ($arr['parent_uri'] === $arr['uri'])) + if((! $parent_id) || ($arr['parent_mid'] === $arr['mid'])) $parent_id = $current_post; if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid)) $private = 1; else - $private = $arr['private']; + $private = $arr['item_private']; // Set parent id - and also make sure to inherit the parent's ACL's. - $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s', - `deny_cid` = '%s', `deny_gid` = '%s', `item_private` = %d WHERE `id` = %d LIMIT 1", + $r = q("UPDATE item SET parent = %d, allow_cid = '%s', allow_gid = '%s', + deny_cid = '%s', deny_gid = '%s', item_private = %d WHERE id = %d LIMIT 1", intval($parent_id), dbesc($allow_cid), dbesc($allow_gid), @@ -1435,23 +1942,24 @@ function item_store($arr,$force_parent = false) { intval($current_post) ); + // These are probably redundant now that we've queried the just stored post $arr['id'] = $current_post; $arr['parent'] = $parent_id; $arr['allow_cid'] = $allow_cid; $arr['allow_gid'] = $allow_gid; $arr['deny_cid'] = $deny_cid; $arr['deny_gid'] = $deny_gid; - $arr['private'] = $private; + $arr['item_private'] = $private; // Store taxonomy - + if(($terms) && (is_array($terms))) { foreach($terms as $t) { q("insert into term (uid,oid,otype,type,term,url) values(%d,%d,%d,%d,'%s','%s') ", intval($arr['uid']), intval($current_post), - intval($t['otype']), + intval(TERM_OBJ_POST), intval($t['type']), dbesc($t['term']), dbesc($t['url']) @@ -1465,17 +1973,325 @@ function item_store($arr,$force_parent = false) { // update the commented timestamp on the parent - q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d LIMIT 1", - dbesc(datetime_convert()), + $z = q("select max(created) as commented from item where parent_mid = '%s' and uid = %d ", + dbesc($arr['parent_mid']), + intval($arr['uid']) + ); + + q("UPDATE item set commented = '%s', changed = '%s' WHERE id = %d LIMIT 1", + dbesc(($z) ? $z[0]['commented'] : (datetime_convert())), dbesc(datetime_convert()), intval($parent_id) ); + + send_status_notifications($current_post,$arr); + tag_deliver($arr['uid'],$current_post); + $ret['success'] = true; + $ret['item_id'] = $current_post; - return $current_post; + return $ret; +} + + + +function item_store_update($arr,$allow_exec = false) { + + $d = array('item' => $arr, 'allow_exec' => $allow_exec); + call_hooks('item_store_update', $d ); + $arr = $d['item']; + $allow_exec = $d['allow_exec']; + + $ret = array('success' => false, 'item_id' => 0); + if(! intval($arr['uid'])) { + logger('item_store_update: no uid'); + $ret['message'] = 'no uid.'; + return $ret; + } + if(! intval($arr['id'])) { + logger('item_store_update: no id'); + $ret['message'] = 'no id.'; + return $ret; + } + + $orig_post_id = $arr['id']; + $uid = $arr['uid']; + + $orig = q("select * from item where id = %d and uid = %d limit 1", + intval($orig_post_id), + intval($uid) + ); + if(! $orig) { + logger('item_store_update: original post not found: ' . $orig_post_id); + $ret['message'] = 'no original'; + return $ret; + } + + // override the unseen flag with the original + + if($arr['item_flags'] & ITEM_UNSEEN) + $arr['item_flags'] = $arr['item_flags'] ^ ITEM_UNSEEN; + + if($orig[0]['item_flags'] & ITEM_VERIFIED) + $orig[0]['item_flags'] = $orig[0]['item_flags'] ^ ITEM_VERIFIED; + + if($orig[0]['item_flags'] & ITEM_OBSCURED) + $orig[0]['item_flags'] = $orig[0]['item_flags'] ^ ITEM_OBSCURED; + + + $arr['item_flags'] = intval($arr['item_flags']) | $orig[0]['item_flags']; + $arr['item_restrict'] = intval($arr['item_restrict']) | $orig[0]['item_restrict']; + + + if(array_key_exists('edit',$arr)) + unset($arr['edit']); + $arr['mimetype'] = ((x($arr,'mimetype')) ? notags(trim($arr['mimetype'])) : 'text/bbcode'); + + if(($arr['mimetype'] == 'application/x-php') && (! $allow_exec)) { + logger('item_store: php mimetype but allow_exec is denied.'); + $ret['message'] = 'exec denied.'; + return $ret; + } + + if(! ($arr['item_flags'] & ITEM_OBSCURED)) { + + $arr['lang'] = detect_language($arr['body']); + // apply the input filter here - if it is obscured it has been filtered already + $arr['body'] = z_input_filter($arr['uid'],$arr['body'],$arr['mimetype']); + + if(local_user() && (! $arr['sig'])) { + $channel = get_app()->get_channel(); + if($channel['channel_hash'] === $arr['author_xchan']) { + $arr['sig'] = base64url_encode(rsa_sign($arr['body'],$channel['channel_prvkey'])); + $arr['item_flags'] |= ITEM_VERIFIED; + } + } + + $allowed_languages = get_pconfig($arr['uid'],'system','allowed_languages'); + + if((is_array($allowed_languages)) && ($arr['lang']) && (! array_key_exists($arr['lang'],$allowed_languages))) { + $translate = array('item' => $arr, 'from' => $arr['lang'], 'to' => $allowed_languages, 'translated' => false); + call_hooks('item_translate', $translate); + if((! $translate['translated']) && (intval(get_pconfig($arr['uid'],'system','reject_disallowed_languages')))) { + logger('item_store: language ' . $arr['lang'] . ' not accepted for uid ' . $arr['uid']); + $ret['message'] = 'language not accepted'; + return $ret; + } + $arr = $translate['item']; + } + if($arr['item_private']) { + $key = get_config('system','pubkey'); + $arr['item_flags'] = $arr['item_flags'] | ITEM_OBSCURED; + if($arr['title']) + $arr['title'] = json_encode(crypto_encapsulate($arr['title'],$key)); + if($arr['body']) + $arr['body'] = json_encode(crypto_encapsulate($arr['body'],$key)); + } + + } + + + if((x($arr,'object')) && is_array($arr['object'])) { + activity_sanitise($arr['object']); + $arr['object'] = json_encode($arr['object']); + } + + if((x($arr,'target')) && is_array($arr['target'])) { + activity_sanitise($arr['target']); + $arr['target'] = json_encode($arr['target']); + } + + if((x($arr,'attach')) && is_array($arr['attach'])) { + activity_sanitise($arr['attach']); + $arr['attach'] = json_encode($arr['attach']); + } + + + unset($arr['id']); + unset($arr['uid']); + unset($arr['aid']); + unset($arr['mid']); + unset($arr['parent']); + unset($arr['parent_mid']); + unset($arr['created']); + unset($arr['author_xchan']); + unset($arr['owner_xchan']); + unset($arr['thr_parent']); + unset($arr['llink']); + + $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert()); + $arr['expires'] = ((x($arr,'expires') !== false) ? datetime_convert('UTC','UTC',$arr['expires']) : $orig[0]['expires']); + $arr['commented'] = $orig[0]['commented']; + $arr['received'] = datetime_convert(); + $arr['changed'] = datetime_convert(); + $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : ''); + $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : $orig[0]['location']); + $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : $orig[0]['coord']); + $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : $orig[0]['verb']); + $arr['obj_type'] = ((x($arr,'obj_type')) ? notags(trim($arr['obj_type'])) : $orig[0]['obj_type']); + $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : $orig[0]['object']); + $arr['tgt_type'] = ((x($arr,'tgt_type')) ? notags(trim($arr['tgt_type'])) : $orig[0]['tgt_type']); + $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : $orig[0]['target']); + $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : $orig[0]['plink']); + + $arr['allow_cid'] = ((array_key_exists('allow_cid',$arr)) ? trim($arr['allow_cid']) : $orig[0]['allow_cid']); + $arr['allow_gid'] = ((array_key_exists('allow_gid',$arr)) ? trim($arr['allow_gid']) : $orig[0]['allow_gid']); + $arr['deny_cid'] = ((array_key_exists('deny_cid',$arr)) ? trim($arr['deny_cid']) : $orig[0]['deny_cid']); + $arr['deny_gid'] = ((array_key_exists('deny_gid',$arr)) ? trim($arr['deny_gid']) : $orig[0]['deny_gid']); + $arr['item_private'] = ((array_key_exists('item_private',$arr)) ? intval($arr['item_private']) : $orig[0]['item_private']); + + $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : ''); + $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : $orig[0]['attach']); + $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : $orig[0]['app']); +// $arr['item_restrict'] = ((x($arr,'item_restrict')) ? intval($arr['item_restrict']) : $orig[0]['item_restrict'] ); +// $arr['item_flags'] = ((x($arr,'item_flags')) ? intval($arr['item_flags']) : $orig[0]['item_flags'] ); + + $arr['sig'] = ((x($arr,'sig')) ? $arr['sig'] : ''); + $arr['layout_mid'] = ((array_key_exists('layout_mid',$arr)) ? dbesc($arr['layout_mid']) : $orig[0]['layout_mid'] ); + + call_hooks('post_remote_update',$arr); + + if(x($arr,'cancel')) { + logger('item_store_update: post cancelled by plugin.'); + $ret['message'] = 'cancelled.'; + return $ret; + } + + // pull out all the taxonomy stuff for separate storage + + $terms = null; + if(array_key_exists('term',$arr)) { + $terms = $arr['term']; + unset($arr['term']); + } + + dbesc_array($arr); + + logger('item_store_update: ' . print_r($arr,true), LOGGER_DATA); + + $str = ''; + foreach($arr as $k => $v) { + if($str) + $str .= ","; + $str .= " `" . $k . "` = '" . $v . "' "; + } + + $r = dbq("update `item` set " . $str . " where id = " . $orig_post_id . " limit 1"); + + if($r) + logger('item_store_update: updated item ' . $orig_post_id, LOGGER_DEBUG); + else { + logger('item_store_update: could not update item'); + $ret['message'] = 'DB update failed.'; + return $ret; + } + + $r = q("delete from term where oid = %d and otype = %d", + intval($orig_post_id), + intval(TERM_OBJ_POST) + ); + + if(($terms) && (is_array($terms))) { + foreach($terms as $t) { + q("insert into term (uid,oid,otype,type,term,url) + values(%d,%d,%d,%d,'%s','%s') ", + intval($uid), + intval($orig_post_id), + intval(TERM_OBJ_POST), + intval($t['type']), + dbesc($t['term']), + dbesc($t['url']) + ); + } + + $arr['term'] = $terms; + } + + call_hooks('post_remote_update_end',$arr); + + send_status_notifications($orig_post_id,$arr); + + tag_deliver($uid,$orig_post_id); + $ret['success'] = true; + $ret['item_id'] = $orig_post_id; + + return $ret; } + + + +function send_status_notifications($post_id,$item) { + + $notify = false; + $parent = 0; + + $r = q("select channel_hash from channel where channel_id = %d limit 1", + intval($item['uid']) + ); + if(! $r) + return; + + // my own post - no notification needed + if($item['author_xchan'] === $r[0]['channel_hash']) + return; + + // I'm the owner - notify me + + if($item['owner_hash'] === $r[0]['channel_hash']) + $notify = true; + + // Was I involved in this conversation? + + $x = q("select * from item where parent_mid = '%s' and uid = %d", + dbesc($item['parent_mid']), + intval($item['uid']) + ); + if($x) { + foreach($x as $xx) { + if($xx['author_xchan'] === $r[0]['channel_hash']) { + $notify = true; + } + if($xx['id'] == $xx['parent']) { + $parent = $xx['parent']; + } + } + } + + $link = get_app()->get_baseurl() . '/display/' . $item['mid']; + + + $y = q("select id from notify where link = '%s' and uid = %d limit 1", + dbesc($link), + intval($item['uid']) + ); + + if($y) + $notify = false; + + if(! $notify) + return; + require_once('include/enotify.php'); + notification(array( + 'type' => NOTIFY_COMMENT, + 'from_xchan' => $item['author_xchan'], + 'to_xchan' => $r[0]['channel_hash'], + 'item' => $item, + 'link' => $link, + 'verb' => ACTIVITY_POST, + 'otype' => 'item', + 'parent' => $parent, + 'parent_mid' => $item['parent_mid'] + )); + return; +} + + + + + + function get_item_contact($item,$contacts) { if(! count($contacts) || (! is_array($item))) return false; @@ -1491,7 +2307,9 @@ function get_item_contact($item,$contacts) { function tag_deliver($uid,$item_id) { - // look for mention tags and setup a second delivery chain for forum/community posts if appropriate + // Called when we deliver things that might be tagged in ways that require delivery processing. + // Handles community tagging of posts and also look for mention tags + // and sets up a second delivery chain if appropriate $a = get_app(); @@ -1500,112 +2318,286 @@ function tag_deliver($uid,$item_id) { $u = q("select * from channel where channel_id = %d limit 1", intval($uid) ); - if(! count($u)) + if(! $u) return; - - - // fixme - look for permissions allowing tag delivery - $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false); - $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false); - - $i = q("select * from item where id = %d and uid = %d limit 1", intval($item_id), intval($uid) ); - if(! count($i)) + if(! $i) return; + $i = fetch_post_tags($i); + $item = $i[0]; - $link = normalise_link($a->get_baseurl() . '/channel/' . $u[0]['nickname']); + if(($item['source_xchan']) && ($item['item_flags'] & ITEM_UPLINK) && ($item['item_flags'] & ITEM_THREAD_TOP) && ($item['edited'] != $item['created'])) { + // this is an update to a post which was already processed by us and has a second delivery chain + // Just start the second delivery chain to deliver the updated post + proc_run('php','include/notifier.php','tgroup',$item['id']); + return; + } - $body = preg_replace("/\[share\](.*?)\[\/share\]/ism", '', $item['body']); - - $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$body,$matches,PREG_SET_ORDER); - if($cnt) { - foreach($matches as $mtch) { - if(link_compare($link,$mtch[1])) { - $mention = true; - logger('tag_deliver: mention found: ' . $mtch[2]); + + if (stristr($item['verb'],ACTIVITY_POKE)) { + $poke_notify = true; + + if(($item['obj_type'] == "") || ($item['obj_type'] !== ACTIVITY_OBJ_PERSON) || (! $item['object'])) + $poke_notify = false; + + $obj = json_decode_plus($item['object']); + if($obj) { + if($obj['id'] !== $u[0]['channel_hash']) + $poke_notify = false; + } + + $verb = urldecode(substr($item['verb'],strpos($item['verb'],'#')+1)); + if($poke_notify) { + require_once('include/enotify.php'); + notification(array( + 'to_xchan' => $u[0]['channel_hash'], + 'from_xchan' => $item['author_xchan'], + 'type' => NOTIFY_POKE, + 'item' => $item, + 'link' => $i[0]['llink'], + 'verb' => ACTIVITY_POKE, + 'activity' => $verb, + 'otype' => 'item' + )); + } + } + + if($item['obj_type'] === ACTIVITY_OBJ_TAGTERM) { + + // We received a community tag activity for a post. + // See if we are the owner of the parent item and have given permission to tag our posts. + // If so tag the parent post. + + logger('tag_deliver: community tag activity received'); + + if(($item['owner_xchan'] === $u[0]['channel_hash']) && (! get_pconfig($u[0]['channel_id'],'system','blocktags'))) { + logger('tag_deliver: community tag recipient: ' . $u[0]['channel_name']); + $j_tgt = json_decode_plus($item['target']); + if($j_tgt && $j_tgt['id']) { + $p = q("select * from item where mid = '%s' and uid = %d limit 1", + dbesc($j_tgt['id']), + intval($u[0]['channel_id']) + ); + if($p) { + $j_obj = json_decode_plus($item['object']); + logger('tag_deliver: tag object: ' . print_r($j_obj,true), LOGGER_DATA); + if($j_obj && $j_obj['id'] && $j_obj['title']) { + if(is_array($j_obj['link'])) + $taglink = get_rel_link($j_obj['link'],'alternate'); + + store_item_tag($u[0]['channel_id'],$p[0]['id'],TERM_OBJ_POST,TERM_HASHTAG,$j_obj['title'],$j_obj['id']); + $x = q("update item set edited = '%s', received = '%s', changed = '%s' where mid = '%s' and uid = %d limit 1", + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc($j_tgt['id']), + intval($u[0]['channel_id']) + ); + proc_run('php','include/notifier.php','edit_post',$p[0]['id']); + } + } } } + else + logger('tag_deliver: tag permission denied for ' . $u[0]['channel_address']); } - if(! $mention) - return; - // send a notification + $union = check_item_source($uid,$item); + if($union) + logger('check_item_source returns true'); - // use a local photo if we have one - $r = q("select thumb from contact where uid = %d and nurl = '%s' limit 1", - intval($u[0]['uid']), - dbesc(normalise_link($item['author-link'])) - ); - $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']); + // This might be a followup (e.g. comment) by the original post author to a tagged forum + // If so setup a second delivery chain -// fixme for channels + $r = null; - require_once('include/enotify.php'); - notification(array( - 'type' => NOTIFY_TAGSELF, - 'notify_flags' => $u[0]['notify-flags'], - 'language' => $u[0]['language'], - 'to_name' => $u[0]['username'], - 'to_email' => $u[0]['email'], - 'uid' => $u[0]['uid'], - 'item' => $item, - 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'], - 'source_name' => $item['author-name'], - 'source_link' => $item['author-link'], - 'source_photo' => $photo, - 'verb' => ACTIVITY_TAG, - 'otype' => 'item' - )); + if( ! ($item['item_flags'] & ITEM_THREAD_TOP)) { + $x = q("select * from item where id = parent and parent = %d and uid = %d limit 1", + intval($item['parent']), + intval($uid) + ); + + + if(($x) && ($x[0]['item_flags'] & ITEM_UPLINK)) { + + logger('tag_deliver: creating second delivery chain for comment to tagged post.'); + + // now change this copy of the post to a forum head message and deliver to all the tgroup members + // also reset all the privacy bits to the forum default permissions + + $private = (($u[0]['channel_allow_cid'] || $u[0]['channel_allow_gid'] || $u[0]['channel_deny_cid'] || $u[0]['channel_deny_gid']) ? 1 : 0); + + $flag_bits = ITEM_WALL|ITEM_ORIGIN; + + // maintain the original source, which will be the original item owner and was stored in source_xchan + // when we created the delivery fork + + $r = q("update item set source_xchan = '%s' where id = %d limit 1", + dbesc($x[0]['source_xchan']), + intval($item_id) + ); + + $r = q("update item set item_flags = ( item_flags | %d ), owner_xchan = '%s', allow_cid = '%s', allow_gid = '%s', + deny_cid = '%s', deny_gid = '%s', item_private = %d where id = %d limit 1", + intval($flag_bits), + dbesc($u[0]['channel_hash']), + dbesc($u[0]['channel_allow_cid']), + dbesc($u[0]['channel_allow_gid']), + dbesc($u[0]['channel_deny_cid']), + dbesc($u[0]['channel_deny_gid']), + intval($private), + intval($item_id) + ); + if($r) + proc_run('php','include/notifier.php','tgroup',$item_id); + else + logger('tag_deliver: failed to update item'); + } + } + + $terms = get_terms_oftype($item['term'],TERM_MENTION); + + if($terms) + logger('tag_deliver: post mentions: ' . print_r($terms,true), LOGGER_DATA); + + $link = normalise_link($a->get_baseurl() . '/channel/' . $u[0]['channel_address']); + + if($terms) { + foreach($terms as $term) { + if(link_compare($term['url'],$link)) { + $mention = true; + break; + } + } + } + + if($mention) { + logger('tag_deliver: mention found for ' . $u[0]['channel_name']); + + $r = q("update item set item_flags = ( item_flags | %d ) where id = %d limit 1", + intval(ITEM_MENTIONSME), + intval($item_id) + ); + + + + // At this point we've determined that the person receiving this post was mentioned in it or it is a union. + // Now let's check if this mention was inside a reshare so we don't spam a forum + // If it's private we may have to unobscure it momentarily so that we can parse it. + + $body = ''; + + if($item['item_flags'] & ITEM_OBSCURED) { + $key = get_config('system','prvkey'); + if($item['body']) + $body = crypto_unencapsulate(json_decode_plus($item['body']),$key); + } + else + $body = $item['body']; + + $body = preg_replace('/\[share(.*?)\[\/share\]/','',$body); + + $tagged = false; + $plustagged = false; - if((! $community_page) && (! $prvgroup)) + $pattern = '/@\!?\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($term['term'],'/') . '\[\/zrl\]/'; + if(preg_match($pattern,$body,$matches)) + $tagged = true; + + $pattern = '/@\!?\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($term['term'] . '+','/') . '\[\/zrl\]/'; + if(preg_match($pattern,$body,$matches)) + $plustagged = true; + + if(! ($tagged || $plustagged)) { + logger('tag_deliver: mention was in a reshare - ignoring'); + return; + } + + $arr = array('channel_id' => $uid, 'item' => $item, 'body' => $body); + call_hooks('tagged',$arr); + + // Valid tag. Send a notification + + require_once('include/enotify.php'); + notification(array( + 'to_xchan' => $u[0]['channel_hash'], + 'from_xchan' => $item['author_xchan'], + 'type' => NOTIFY_TAGSELF, + 'item' => $item, + 'link' => $i[0]['llink'], + 'verb' => ACTIVITY_TAG, + 'otype' => 'item' + )); + + // Just a normal tag? + + if(! $plustagged) { + logger('tag_deliver: not a plus tag', LOGGER_DEBUG); + return; + } + + // plustagged - keep going, next check permissions + + if(! perm_is_allowed($uid,$item['author_xchan'],'tag_deliver')) { + logger('tag_delivery denied for uid ' . $uid . ' and xchan ' . $item['author_xchan']); + return; + } + + } + + if((! $mention) && (! $union)) { + logger('tag_deliver: no mention and no union.'); return; + } // tgroup delivery - setup a second delivery chain // prevent delivery looping - only proceed // if the message originated elsewhere and is a top-level post - if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent'])) + if(($item['item_flags'] & ITEM_WALL) || ($item['item_flags'] & ITEM_ORIGIN) || (!($item['item_flags'] & ITEM_THREAD_TOP)) || ($item['id'] != $item['parent'])) { + logger('tag_deliver: item was local or a comment. rejected.'); return; + } - // now change this copy of the post to a forum head message and deliver to all the tgroup members + logger('tag_deliver: creating second delivery chain.'); + // now change this copy of the post to a forum head message and deliver to all the tgroup members + // also reset all the privacy bits to the forum default permissions - $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1", - intval($u[0]['uid']) - ); - if(! count($c)) - return; + $private = (($u[0]['channel_allow_cid'] || $u[0]['channel_allow_gid'] || $u[0]['channel_deny_cid'] || $u[0]['channel_deny_gid']) ? 1 : 0); - // also reset all the privacy bits to the forum default permissions + $flag_bits = ITEM_WALL|ITEM_ORIGIN|ITEM_UPLINK; - $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0; + // preserve the source - $forum_mode = (($prvgroup) ? 2 : 1); + $r = q("update item set source_xchan = owner_xchan where id = %d limit 1", + intval($item_id) + ); - q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s', - `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d limit 1", - intval($forum_mode), - dbesc($c[0]['name']), - dbesc($c[0]['url']), - dbesc($c[0]['thumb']), + $r = q("update item set item_flags = ( item_flags | %d ), owner_xchan = '%s', allow_cid = '%s', allow_gid = '%s', + deny_cid = '%s', deny_gid = '%s', item_private = %d where id = %d limit 1", + intval($flag_bits), + dbesc($u[0]['channel_hash']), + dbesc($u[0]['channel_allow_cid']), + dbesc($u[0]['channel_allow_gid']), + dbesc($u[0]['channel_deny_cid']), + dbesc($u[0]['channel_deny_gid']), intval($private), - dbesc($u[0]['allow_cid']), - dbesc($u[0]['allow_gid']), - dbesc($u[0]['deny_cid']), - dbesc($u[0]['deny_gid']), intval($item_id) ); - - proc_run('php','include/notifier.php','tgroup',$item_id); + if($r) + proc_run('php','include/notifier.php','tgroup',$item_id); + else + logger('tag_deliver: failed to update item'); } @@ -1618,53 +2610,135 @@ function tgroup_check($uid,$item) { $mention = false; // check that the message originated elsewhere and is a top-level post + // or is a followup and we have already accepted the top level post as an uplink - if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri'])) + if($item['mid'] != $item['parent_mid']) { + $r = q("select id from item where mid = '%s' and uid = %d and ( item_flags & %d ) limit 1", + dbesc($item['parent_mid']), + intval($uid), + intval(ITEM_UPLINK) + ); + if($r) + return true; + return false; + } + if(! perm_is_allowed($uid,$item['author_xchan'],'tag_deliver')) return false; - - $u = q("select * from user where uid = %d limit 1", + $u = q("select * from channel where channel_id = %d limit 1", intval($uid) ); - if(! count($u)) - return false; - - $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false); - $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false); + if(! $u) + return false; - $link = normalise_link($a->get_baseurl() . '/channel/' . $u[0]['nickname']); + $terms = get_terms_oftype($item['term'],TERM_MENTION); - // Diaspora uses their own hardwired link URL in @-tags - // instead of the one we supply with webfinger + if($terms) + logger('tgroup_check: post mentions: ' . print_r($terms,true), LOGGER_DATA); - $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']); + $link = normalise_link($a->get_baseurl() . '/channel/' . $u[0]['channel_address']); - $body = preg_replace("/\[share\](.*?)\[\/share\]/ism", '', $item['body']); - - $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$body,$matches,PREG_SET_ORDER); - if($cnt) { - foreach($matches as $mtch) { - if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) { + if($terms) { + foreach($terms as $term) { + if(link_compare($term['url'],$link)) { $mention = true; - logger('tgroup_check: mention found: ' . $mtch[2]); + break; } } + } + + if($mention) { + logger('tgroup_check: mention found for ' . $u[0]['channel_name']); + } + else + return false; + + // At this point we've determined that the person receiving this post was mentioned in it. + // Now let's check if this mention was inside a reshare so we don't spam a forum + + $body = preg_replace('/\[share(.*?)\[\/share\]/','',$item['body']); + + $pattern = '/@\!?\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($term['term'] . '+','/') . '\[\/zrl\]/'; + + if(! preg_match($pattern,$body,$matches)) { + logger('tgroup_check: mention was in a reshare - ignoring'); + return false; } - if(! $mention) + + return true; + +} + + +/** + * @function check_item_source($uid,$item) + * @param $uid + * @param $item + * + * @description + * Checks to see if this item owner is referenced as a source for this channel and if the post + * matches the rules for inclusion in this channel. Returns true if we should create a second delivery + * chain and false if none of the rules apply, or if the item is private. + */ + + +function check_item_source($uid,$item) { + + if($item['item_private']) + return false; + + + $r = q("select * from source where src_channel_id = %d and ( src_xchan = '%s' || src_xchan = '*' ) limit 1", + intval($uid), + dbesc(($item['source_xchan']) ? $item['source_xchan'] : $item['owner_xchan']) + ); + + if(! $r) return false; - if((! $community_page) && (! $prvgroup)) + $x = q("select abook_their_perms from abook where abook_channel = %d and abook_xchan = '%s' limit 1", + intval($uid), + dbesc($item['owner_xchan']) + ); + + if(! $x) return false; + if(! ($x[0]['abook_their_perms'] & PERMS_A_REPUBLISH)) + return false; + if($r[0]['src_channel_xchan'] === $item['owner_xchan']) + return false; - return true; + if(! $r[0]['src_patt']) + return true; + require_once('include/html2plain.php'); + $text = prepare_text($item['body'],$item['mimetype']); + $text = html2plain($text); + + $tags = ((count($items['term'])) ? $items['term'] : false); + + $words = explode("\n",$r[0]['src_patt']); + if($words) { + foreach($words as $word) { + if(substr($word,0,1) === '#' && $tags) { + foreach($tags as $t) + if(($t['type'] == TERM_HASHTAG) && ((substr($t,1) === substr($word,1)) || (substr($word,1) === '*'))) + return true; + } + if(stristr($text,$word) !== false) + return true; + } + } + return false; } + + function mail_store($arr) { if(! $arr['channel_id']) { @@ -1675,24 +2749,29 @@ function mail_store($arr) { if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false)) $arr['body'] = escape_tags($arr['body']); + if(array_key_exists('attach',$arr) && is_array($arr['attach'])) + $arr['attach'] = json_encode($arr['attach']); + $arr['account_id'] = ((x($arr,'account_id')) ? intval($arr['account_id']) : 0); - $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string()); + $arr['mid'] = ((x($arr,'mid')) ? notags(trim($arr['mid'])) : random_string()); $arr['from_xchan'] = ((x($arr,'from_xchan')) ? notags(trim($arr['from_xchan'])) : ''); $arr['to_xchan'] = ((x($arr,'to_xchan')) ? notags(trim($arr['to_xchan'])) : ''); $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert()); + $arr['expires'] = ((x($arr,'expires') !== false) ? datetime_convert('UTC','UTC',$arr['expires']) : '0000-00-00 00:00:00'); $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : ''); - $arr['parent_uri'] = ((x($arr,'parent_uri')) ? notags(trim($arr['parent_uri'])) : ''); + $arr['parent_mid'] = ((x($arr,'parent_mid')) ? notags(trim($arr['parent_mid'])) : ''); $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : ''); + $arr['mail_flags'] = ((x($arr,'mail_flags')) ? intval($arr['mail_flags']) : 0 ); - if(! $arr['parent_uri']) { + if(! $arr['parent_mid']) { logger('mail_store: missing parent'); - $arr['parent_uri'] = $arr['uri']; + $arr['parent_mid'] = $arr['mid']; } - $r = q("SELECT `id` FROM mail WHERE `uri` = '%s' AND channel_id = %d LIMIT 1", - dbesc($arr['uri']), + $r = q("SELECT `id` FROM mail WHERE `mid` = '%s' AND channel_id = %d LIMIT 1", + dbesc($arr['mid']), intval($arr['channel_id']) ); if($r) { @@ -1719,14 +2798,15 @@ function mail_store($arr) { // find the item we just created - $r = q("SELECT `id` FROM mail WHERE `uri` = '%s' AND `channel_id` = %d ORDER BY `id` ASC ", - $arr['uri'], // already dbesc'd + $r = q("SELECT `id` FROM mail WHERE `mid` = '%s' AND `channel_id` = %d ORDER BY `id` ASC ", + $arr['mid'], // already dbesc'd intval($arr['channel_id']) ); if($r) { $current_post = $r[0]['id']; logger('mail_store: created item ' . $current_post, LOGGER_DEBUG); + $arr['id'] = $current_post; // for notification } else { logger('mail_store: could not locate created item'); @@ -1734,211 +2814,33 @@ function mail_store($arr) { } if(count($r) > 1) { logger('mail_store: duplicated post occurred. Removing duplicates.'); - q("DELETE FROM mail WHERE `uri` = '%s' AND `channel_id` = %d AND `id` != %d ", - $arr['uri'], + q("DELETE FROM mail WHERE `mid` = '%s' AND `channel_id` = %d AND `id` != %d ", + $arr['mid'], intval($arr['channel_id']), intval($current_post) ); } - - call_hooks('post_mail_end',$arr); - return $current_post; -} - - - - - - -function dfrn_deliver($owner,$contact,$atom, $dissolve = false) { - - $a = get_app(); - - $idtosend = $orig_id = (($contact['dfrn_id']) ? $contact['dfrn_id'] : $contact['issued_id']); - - if($contact['duplex'] && $contact['dfrn_id']) - $idtosend = '0:' . $orig_id; - if($contact['duplex'] && $contact['issued_id']) - $idtosend = '1:' . $orig_id; - - $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0); - - $rino_enable = get_config('system','rino_encrypt'); - - if(! $rino_enable) - $rino = 0; - - $ssl_val = intval(get_config('system','ssl_policy')); - $ssl_policy = ''; - - switch($ssl_val){ - case SSL_POLICY_FULL: - $ssl_policy = 'full'; - break; - case SSL_POLICY_SELFSIGN: - $ssl_policy = 'self'; - break; - case SSL_POLICY_NONE: - default: - $ssl_policy = 'none'; - break; - } - - $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : ''); - - logger('dfrn_deliver: ' . $url); - - $xml = fetch_url($url); - - $curl_stat = $a->get_curl_code(); - if(! $curl_stat) - return(-1); // timed out - - logger('dfrn_deliver: ' . $xml, LOGGER_DATA); - - if(! $xml) - return 3; - - if(strpos($xml,'<?xml') === false) { - logger('dfrn_deliver: no valid XML returned'); - logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA); - return 3; - } - - $res = parse_xml_string($xml); - - if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id))) - return (($res->status) ? $res->status : 3); - - $postvars = array(); - $sent_dfrn_id = hex2bin((string) $res->dfrn_id); - $challenge = hex2bin((string) $res->challenge); - $perm = (($res->perm) ? $res->perm : null); - $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0); - $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0); - $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0); - - if($owner['page-flags'] == PAGE_PRVGROUP) - $page = 2; - - $final_dfrn_id = ''; - - if($perm) { - if((($perm == 'rw') && (! intval($contact['writable']))) - || (($perm == 'r') && (intval($contact['writable'])))) { - q("update contact set writable = %d where id = %d limit 1", - intval(($perm == 'rw') ? 1 : 0), - intval($contact['id']) - ); - $contact['writable'] = (string) 1 - intval($contact['writable']); - } - } - - if(($contact['duplex'] && strlen($contact['pubkey'])) - || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey'])) - || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) { - openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']); - openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']); - } - else { - openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']); - openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']); - } - - $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.')); - - if(strpos($final_dfrn_id,':') == 1) - $final_dfrn_id = substr($final_dfrn_id,2); - - if($final_dfrn_id != $orig_id) { - logger('dfrn_deliver: wrong dfrn_id.'); - // did not decode properly - cannot trust this site - return 3; - } - - $postvars['dfrn_id'] = $idtosend; - $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION; - if($dissolve) - $postvars['dissolve'] = '1'; - - - if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) { - $postvars['data'] = $atom; - $postvars['perm'] = 'rw'; - } else { - $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom); - $postvars['perm'] = 'r'; - } - - $postvars['ssl_policy'] = $ssl_policy; - - if($page) - $postvars['page'] = $page; - - if($rino && $rino_allowed && (! $dissolve)) { - $key = substr(random_string(),0,16); - $data = bin2hex(aes_encrypt($postvars['data'],$key)); - $postvars['data'] = $data; - logger('rino: sent key = ' . $key, LOGGER_DEBUG); - - - if($dfrn_version >= 2.1) { - if(($contact['duplex'] && strlen($contact['pubkey'])) - || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey'])) - || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) { - - openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']); - } - else { - openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']); - } - } - else { - if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) { - openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']); - } - else { - openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']); - } - } - - logger('md5 rawkey ' . md5($postvars['key'])); - - $postvars['key'] = bin2hex($postvars['key']); - } - - logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA); - - $xml = post_url($contact['notify'],$postvars); - - logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA); - - $curl_stat = $a->get_curl_code(); - if((! $curl_stat) || (! strlen($xml))) - return(-1); // timed out - - if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after'))) - return(-1); - - if(strpos($xml,'<?xml') === false) { - logger('dfrn_deliver: phase 2: no valid XML returned'); - logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA); - return 3; - } + require_once('include/enotify.php'); - if($contact['term_date'] != '0000-00-00 00:00:00') { - logger("dfrn_deliver: $url back from the dead - removing mark for death"); - require_once('include/Contact.php'); - unmark_for_death($contact); + $notif_params = array( + 'from_xchan' => $arr['from_xchan'], + 'to_xchan' => $arr['to_xchan'], + 'type' => NOTIFY_MAIL, + 'item' => $arr, + 'verb' => ACTIVITY_POST, + 'otype' => 'mail' + ); + + notification($notif_params); } - $res = parse_xml_string($xml); - - return $res->status; + call_hooks('post_mail_end',$arr); + return $current_post; } + /** * * consume_feed - process atom feed and update anything/everything we might need to update @@ -1974,6 +2876,9 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) logger('consume_feed: empty input'); return; } + + // Want to see this work as a content source for the matrix? + // Read this: https://github.com/friendica/red/wiki/Service_Federation $feed = new SimplePie(); $feed->set_raw_data($xml); @@ -1990,182 +2895,6 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) // Check at the feed level for updated contact name and/or photo - $name_updated = ''; - $new_name = ''; - $photo_timestamp = ''; - $photo_url = ''; - $birthday = ''; - - $hubs = $feed->get_links('hub'); - logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA); - - if(count($hubs)) - $hub = implode(',', $hubs); - - $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner'); - if(! $rawtags) - $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); - if($rawtags) { - $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; - if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) { - $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']; - $new_name = $elems['name'][0]['data']; - } - if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) { - $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']); - $photo_url = $elems['link'][0]['attribs']['']['href']; - } - - if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) { - $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']); - } - } - - if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar_date'])) { - logger('consume_feed: Updating photo for ' . $contact['name']); - require_once("Photo.php"); - $photo_failure = false; - $have_photo = false; - - $r = q("SELECT `resource_id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1", - intval($contact['id']), - intval($contact['uid']) - ); - if(count($r)) { - $resource_id = $r[0]['resource_id']; - $have_photo = true; - } - else { - $resource_id = photo_new_resource(); - } - - $img_str = fetch_url($photo_url,true); - // guess mimetype from headers or filename - $type = guess_image_type($photo_url,true); - - - $img = new Photo($img_str, $type); - if($img->is_valid()) { - if($have_photo) { - q("DELETE FROM `photo` WHERE `resource_id` = '%s' AND `contact-id` = %d AND `uid` = %d", - dbesc($resource_id), - intval($contact['id']), - intval($contact['uid']) - ); - } - - $img->scaleImageSquare(175); - - $hash = $resource_id; - $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4); - - $img->scaleImage(80); - $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5); - - $img->scaleImage(48); - $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6); - - $a = get_app(); - - q("UPDATE `contact` SET `avatar_date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s' - WHERE `uid` = %d AND `id` = %d LIMIT 1", - dbesc(datetime_convert()), - dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()), - dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()), - dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()), - intval($contact['uid']), - intval($contact['id']) - ); - } - } - - if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name_date'])) { - $r = q("select * from contact where uid = %d and id = %d limit 1", - intval($contact['uid']), - intval($contact['id']) - ); - - $x = q("UPDATE `contact` SET `name` = '%s', `name_date` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1", - dbesc(notags(trim($new_name))), - dbesc(datetime_convert()), - intval($contact['uid']), - intval($contact['id']) - ); - - // do our best to update the name on content items - - if(count($r)) { - q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d", - dbesc(notags(trim($new_name))), - dbesc($r[0]['name']), - dbesc($r[0]['url']), - intval($contact['uid']) - ); - } - } - - if(strlen($birthday)) { - if(substr($birthday,0,4) != $contact['bdyear']) { - logger('consume_feed: updating birthday: ' . $birthday); - - /** - * - * Add new birthday event for this person - * - * $bdtext is just a readable placeholder in case the event is shared - * with others. We will replace it during presentation to our $importer - * to contain a sparkle link and perhaps a photo. - * - */ - - $bdtext = sprintf( t('%s\'s birthday'), $contact['name']); - $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ; - - - $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`) - VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", - intval($contact['uid']), - intval($contact['id']), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc(datetime_convert('UTC','UTC', $birthday)), - dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')), - dbesc($bdtext), - dbesc($bdtext2), - dbesc('birthday') - ); - - - // update bdyear - - q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1", - dbesc(substr($birthday,0,4)), - intval($contact['uid']), - intval($contact['id']) - ); - - // This function is called twice without reloading the contact - // Make sure we only create one event. This is why &$contact - // is a reference var in this function - - $contact['bdyear'] = substr($birthday,0,4); - } - - } - - $community_page = 0; - $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community'); - if($rawtags) { - $community_page = intval($rawtags[0]['data']); - } - if(is_array($contact) && intval($contact['forum']) != $community_page) { - q("update contact set forum = %d where id = %d limit 1", - intval($community_page), - intval($contact['id']) - ); - $contact['forum'] = (string) $community_page; - } - // process any deleted entries @@ -2174,7 +2903,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) foreach($del_entries as $dentry) { $deleted = false; if(isset($dentry['attribs']['']['ref'])) { - $uri = $dentry['attribs']['']['ref']; + $mid = $dentry['attribs']['']['ref']; $deleted = true; if(isset($dentry['attribs']['']['when'])) { $when = $dentry['attribs']['']['when']; @@ -2184,71 +2913,39 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s'); } if($deleted && is_array($contact)) { - $r = q("SELECT `item`.*, `contact`.`self` FROM `item` left join `contact` on `item`.`contact-id` = `contact`.`id` - WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1", - dbesc($uri), - intval($importer['uid']), +/* $r = q("SELECT `item`.*, `contact`.`self` FROM `item` left join `contact` on `item`.`contact-id` = `contact`.`id` + WHERE `mid` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1", + dbesc($mid), + intval($importer['channel_id']), intval($contact['id']) ); +*/ if(count($r)) { $item = $r[0]; if(! $item['deleted']) - logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG); - - if(($item['verb'] === ACTIVITY_TAG) && ($item['obj_type'] === ACTIVITY_OBJ_TAGTERM)) { - $xo = parse_xml_string($item['object'],false); - $xt = parse_xml_string($item['target'],false); - if($xt->type === ACTIVITY_OBJ_NOTE) { - $i = q("select * from `item` where uri = '%s' and uid = %d limit 1", - dbesc($xt->id), - intval($importer['importer_uid']) - ); - if(count($i)) { - - // For tags, the owner cannot remove the tag on the author's copy of the post. - - $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false); - $author_remove = (($item['origin'] && $item['self']) ? true : false); - $author_copy = (($item['origin']) ? true : false); - - if($owner_remove && $author_copy) - continue; - if($author_remove || $owner_remove) { - $tags = explode(',',$i[0]['tag']); - $newtags = array(); - if(count($tags)) { - foreach($tags as $tag) - if(trim($tag) !== trim($xo->body)) - $newtags[] = trim($tag); - } - q("update item set tag = '%s' where id = %d limit 1", - dbesc(implode(',',$newtags)), - intval($i[0]['id']) - ); - } - } - } - } + logger('consume_feed: deleting item ' . $item['id'] . ' mid=' . $item['mid'], LOGGER_DEBUG); - if($item['uri'] == $item['parent_uri']) { - $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', + if($item['mid'] == $item['parent_mid']) { + $r = q("UPDATE `item` SET item_restrict = (item_restrict | %d), `edited` = '%s', `changed` = '%s', `body` = '', `title` = '' - WHERE `parent_uri` = '%s' AND `uid` = %d", + WHERE `parent_mid` = '%s' AND `uid` = %d", + intval(ITEM_DELETED), dbesc($when), dbesc(datetime_convert()), - dbesc($item['uri']), - intval($importer['uid']) + dbesc($item['mid']), + intval($importer['channel_id']) ); } else { - $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', + $r = q("UPDATE `item` SET item_restrict = ( item_restrict | %d ), `edited` = '%s', `changed` = '%s', `body` = '', `title` = '' - WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + WHERE `mid` = '%s' AND `uid` = %d LIMIT 1", + intval(ITEM_DELETED), dbesc($when), dbesc(datetime_convert()), - dbesc($uri), - intval($importer['uid']) + dbesc($mid), + intval($importer['channel_id']) ); } } @@ -2273,22 +2970,20 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) $is_reply = false; $item_id = $item->get_id(); + +logger('consume_feed: processing ' . $item_id); + $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to'); if(isset($rawthread[0]['attribs']['']['ref'])) { $is_reply = true; - $parent_uri = $rawthread[0]['attribs']['']['ref']; + $parent_mid = $rawthread[0]['attribs']['']['ref']; } - if(($is_reply) && is_array($contact)) { + if($is_reply) { if($pass == 1) continue; - // not allowed to post - - if($contact['rel'] == CONTACT_IS_FOLLOWER) - continue; - // Have we seen it? If not, import it. @@ -2308,26 +3003,26 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) } - $r = q("SELECT `uid`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + $r = q("SELECT `uid`, `edited`, `body` FROM `item` WHERE `mid` = '%s' AND `uid` = %d LIMIT 1", dbesc($item_id), - intval($importer['uid']) + intval($importer['channel_id']) ); // Update content if 'updated' changes - if(count($r)) { + if($r) { if((x($datarray,'edited') !== false) && (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) { // do not accept (ignore) an earlier edit than one we currently have. if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) continue; - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `edited` = '%s' WHERE `mid` = '%s' AND `uid` = %d LIMIT 1", dbesc($datarray['title']), dbesc($datarray['body']), dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), dbesc($item_id), - intval($importer['uid']) + intval($importer['channel_id']) ); } @@ -2335,19 +3030,19 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) } - $datarray['parent_uri'] = $parent_uri; - $datarray['uid'] = $importer['uid']; + $datarray['parent_mid'] = $parent_mid; + $datarray['uid'] = $importer['channel_id']; $datarray['contact-id'] = $contact['id']; if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) { $datarray['type'] = 'activity'; $datarray['gravity'] = GRAVITY_LIKE; // only one like or dislike per person - $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent_uri` = '%s' OR `thr_parent` = '%s') limit 1", + $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent_mid` = '%s' OR `thr_parent` = '%s') limit 1", intval($datarray['uid']), intval($datarray['contact-id']), dbesc($datarray['verb']), - dbesc($parent_uri), - dbesc($parent_uri) + dbesc($parent_mid), + dbesc($parent_mid) ); if($r && count($r)) continue; @@ -2358,16 +3053,16 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) $xt = parse_xml_string($datarray['target'],false); if($xt->type == ACTIVITY_OBJ_NOTE) { - $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1", + $r = q("select * from item where `mid` = '%s' AND `uid` = %d limit 1", dbesc($xt->id), - intval($importer['importer_uid']) + intval($importer['channel_id']) ); if(! count($r)) continue; // extract tag, if not duplicate, add to parent item if($xo->id && $xo->content) { - $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]'; + $newtag = '#[zrl=' . $xo->id . ']'. $xo->content . '[/zrl]'; if(! (stristr($r[0]['tag'],$newtag))) { q("UPDATE item SET tag = '%s' WHERE id = %d LIMIT 1", dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag), @@ -2378,7 +3073,10 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) } } - $r = item_store($datarray,$force_parent); +logger('consume_feed: ' . print_r($datarray,true)); + +// $xx = item_store($datarray); + $r = $xx['item_id']; continue; } @@ -2409,44 +3107,44 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) if((x($datarray,'obj_type')) && ($datarray['obj_type'] === ACTIVITY_OBJ_EVENT)) { $ev = bbtoevent($datarray['body']); if(x($ev,'desc') && x($ev,'start')) { - $ev['uid'] = $importer['uid']; - $ev['uri'] = $item_id; + $ev['uid'] = $importer['channel_id']; + $ev['mid'] = $item_id; $ev['edited'] = $datarray['edited']; $ev['private'] = $datarray['private']; if(is_array($contact)) $ev['cid'] = $contact['id']; - $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + $r = q("SELECT * FROM `event` WHERE `mid` = '%s' AND `uid` = %d LIMIT 1", dbesc($item_id), - intval($importer['uid']) + intval($importer['channel_id']) ); if(count($r)) $ev['id'] = $r[0]['id']; - $xyz = event_store($ev); +// $xyz = event_store($ev); continue; } } - $r = q("SELECT `uid`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + $r = q("SELECT `uid`, `edited`, `body` FROM `item` WHERE `mid` = '%s' AND `uid` = %d LIMIT 1", dbesc($item_id), - intval($importer['uid']) + intval($importer['channel_id']) ); // Update content if 'updated' changes - if(count($r)) { + if($r) { if((x($datarray,'edited') !== false) && (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) { // do not accept (ignore) an earlier edit than one we currently have. if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) continue; - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `edited` = '%s' WHERE `mid` = '%s' AND `uid` = %d LIMIT 1", dbesc($datarray['title']), dbesc($datarray['body']), dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), dbesc($item_id), - intval($importer['uid']) + intval($importer['channel_id']) ); } @@ -2474,8 +3172,8 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) } - if(! is_array($contact)) - return; +// if(! is_array($contact)) +// return; // This is my contact on another system, but it's really me. @@ -2485,8 +3183,8 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) $datarray['wall'] = 1; } - $datarray['parent_uri'] = $item_id; - $datarray['uid'] = $importer['uid']; + $datarray['parent_mid'] = $item_id; + $datarray['uid'] = $importer['channel_id']; $datarray['contact-id'] = $contact['id']; if(! link_compare($datarray['owner-link'],$contact['url'])) { @@ -2504,1127 +3202,25 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) // posting an @-tag delivery, which followers are allowed to do for certain // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it. - if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray))) + if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['channel_id'],$datarray))) continue; +logger('consume_feed: ' . print_r($datarray,true)); - $r = item_store($datarray); +// $xx = item_store($datarray); + $r = $xx['item_id']; continue; } } } -} - -function local_delivery($importer,$data) { - - $a = get_app(); - - if($importer['readonly']) { - // We aren't receiving stuff from this person. But we will quietly ignore them - // rather than a blatant "go away" message. - logger('local_delivery: ignoring'); - return 0; - //NOTREACHED - } - - // Consume notification feed. This may differ from consuming a public feed in several ways - // - might contain email or friend suggestions - // - might contain remote followup to our message - // - in which case we need to accept it and then notify other conversants - // - we may need to send various email notifications - - $feed = new SimplePie(); - $feed->set_raw_data($data); - $feed->enable_order_by_date(false); - $feed->init(); - - - if($feed->error()) - logger('local_delivery: Error parsing XML: ' . $feed->error()); - - - // Check at the feed level for updated contact name and/or photo - $name_updated = ''; - $new_name = ''; - $photo_timestamp = ''; - $photo_url = ''; - - $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner'); - if(! $rawtags) - $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); - if($rawtags) { - $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; - if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) { - $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']; - $new_name = $elems['name'][0]['data']; - } - if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) { - $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']); - $photo_url = $elems['link'][0]['attribs']['']['href']; - } - } - - if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar_date'])) { - logger('local_delivery: Updating photo for ' . $importer['name']); - require_once("Photo.php"); - $photo_failure = false; - $have_photo = false; - - $r = q("SELECT `resource_id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1", - intval($importer['id']), - intval($importer['importer_uid']) - ); - if(count($r)) { - $resource_id = $r[0]['resource_id']; - $have_photo = true; - } - else { - $resource_id = photo_new_resource(); - } - - $img_str = fetch_url($photo_url,true); - // guess mimetype from headers or filename - $type = guess_image_type($photo_url,true); - - - $img = new Photo($img_str, $type); - if($img->is_valid()) { - if($have_photo) { - q("DELETE FROM `photo` WHERE `resource_id` = '%s' AND `contact-id` = %d AND `uid` = %d", - dbesc($resource_id), - intval($importer['id']), - intval($importer['importer_uid']) - ); - } - - $img->scaleImageSquare(175); - - $hash = $resource_id; - $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4); - - $img->scaleImage(80); - $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5); - - $img->scaleImage(48); - $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6); - - $a = get_app(); - - q("UPDATE `contact` SET `avatar_date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s' - WHERE `uid` = %d AND `id` = %d LIMIT 1", - dbesc(datetime_convert()), - dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()), - dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()), - dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()), - intval($importer['importer_uid']), - intval($importer['id']) - ); - } - } - - if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name_date'])) { - $r = q("select * from contact where uid = %d and id = %d limit 1", - intval($importer['importer_uid']), - intval($importer['id']) - ); - - $x = q("UPDATE `contact` SET `name` = '%s', `name_date` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1", - dbesc(notags(trim($new_name))), - dbesc(datetime_convert()), - intval($importer['importer_uid']), - intval($importer['id']) - ); - - // do our best to update the name on content items - - if(count($r)) { - q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d", - dbesc(notags(trim($new_name))), - dbesc($r[0]['name']), - dbesc($r[0]['url']), - intval($importer['importer_uid']) - ); - } - } - - -/* - // Currently unsupported - needs a lot of work - $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' ); - if(isset($reloc[0]['child'][NAMESPACE_DFRN])) { - $base = $reloc[0]['child'][NAMESPACE_DFRN]; - $newloc = array(); - $newloc['uid'] = $importer['importer_uid']; - $newloc['cid'] = $importer['id']; - $newloc['name'] = notags(unxmlify($base['name'][0]['data'])); - $newloc['photo'] = notags(unxmlify($base['photo'][0]['data'])); - $newloc['url'] = notags(unxmlify($base['url'][0]['data'])); - $newloc['request'] = notags(unxmlify($base['request'][0]['data'])); - $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data'])); - $newloc['notify'] = notags(unxmlify($base['notify'][0]['data'])); - $newloc['poll'] = notags(unxmlify($base['poll'][0]['data'])); - $newloc['site_pubkey'] = notags(unxmlify($base['site_pubkey'][0]['data'])); - $newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data'])); - $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data'])); - - // TODO - // merge with current record, current contents have priority - // update record, set url-updated - // update profile photos - // schedule a scan? - - } -*/ - - // handle friend suggestion notification - - $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' ); - if(isset($sugg[0]['child'][NAMESPACE_DFRN])) { - $base = $sugg[0]['child'][NAMESPACE_DFRN]; - $fsugg = array(); - $fsugg['uid'] = $importer['importer_uid']; - $fsugg['cid'] = $importer['id']; - $fsugg['name'] = notags(unxmlify($base['name'][0]['data'])); - $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data'])); - $fsugg['url'] = notags(unxmlify($base['url'][0]['data'])); - $fsugg['request'] = notags(unxmlify($base['request'][0]['data'])); - $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data'])); - - // Does our member already have a friend matching this description? - - $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1", - dbesc($fsugg['name']), - dbesc(normalise_link($fsugg['url'])), - intval($fsugg['uid']) - ); - if(count($r)) - return 0; - - // Do we already have an fcontact record for this person? - - $fid = 0; - $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", - dbesc($fsugg['url']), - dbesc($fsugg['name']), - dbesc($fsugg['request']) - ); - if(count($r)) { - $fid = $r[0]['id']; - - // OK, we do. Do we already have an introduction for this person ? - $r = q("select id from intro where uid = %d and fid = %d limit 1", - intval($fsugg['uid']), - intval($fid) - ); - if(count($r)) - return 0; - } - if(! $fid) - $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ", - dbesc($fsugg['name']), - dbesc($fsugg['url']), - dbesc($fsugg['photo']), - dbesc($fsugg['request']) - ); - $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", - dbesc($fsugg['url']), - dbesc($fsugg['name']), - dbesc($fsugg['request']) - ); - if(count($r)) { - $fid = $r[0]['id']; - } - // database record did not get created. Quietly give up. - else - return 0; - - - $hash = random_string(); - - $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` ) - VALUES( %d, %d, %d, '%s', '%s', '%s', %d )", - intval($fsugg['uid']), - intval($fid), - intval($fsugg['cid']), - dbesc($fsugg['body']), - dbesc($hash), - dbesc(datetime_convert()), - intval(0) - ); - - notification(array( - 'type' => NOTIFY_SUGGEST, - 'notify_flags' => $importer['notify-flags'], - 'language' => $importer['language'], - 'to_name' => $importer['username'], - 'to_email' => $importer['email'], - 'uid' => $importer['importer_uid'], - 'item' => $fsugg, - 'link' => $a->get_baseurl() . '/notifications/intros', - 'source_name' => $importer['name'], - 'source_link' => $importer['url'], - 'source_photo' => $importer['photo'], - 'verb' => ACTIVITY_REQ_FRIEND, - 'otype' => 'intro' - )); - - return 0; - } - - $ismail = false; - - $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' ); - if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) { - - logger('local_delivery: private message received'); - - $ismail = true; - $base = $rawmail[0]['child'][NAMESPACE_DFRN]; - - $msg = array(); - $msg['uid'] = $importer['importer_uid']; - $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data'])); - $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data'])); - $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])); - $msg['contact-id'] = $importer['id']; - $msg['title'] = notags(unxmlify($base['subject'][0]['data'])); - $msg['body'] = escape_tags(unxmlify($base['content'][0]['data'])); - $msg['seen'] = 0; - $msg['replied'] = 0; - $msg['uri'] = notags(unxmlify($base['id'][0]['data'])); - $msg['parent_uri'] = notags(unxmlify($base['in-reply-to'][0]['data'])); - $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data']))); - - dbesc_array($msg); - - $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg)) - . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" ); - - // send notifications. - - require_once('include/enotify.php'); - - $notif_params = array( - 'type' => NOTIFY_MAIL, - 'notify_flags' => $importer['notify-flags'], - 'language' => $importer['language'], - 'to_name' => $importer['username'], - 'to_email' => $importer['email'], - 'uid' => $importer['importer_uid'], - 'item' => $msg, - 'source_name' => $msg['from-name'], - 'source_link' => $importer['url'], - 'source_photo' => $importer['thumb'], - 'verb' => ACTIVITY_POST, - 'otype' => 'mail' - ); - - notification($notif_params); - return 0; - - // NOTREACHED - } - - $community_page = 0; - $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community'); - if($rawtags) { - $community_page = intval($rawtags[0]['data']); - } - if(intval($importer['forum']) != $community_page) { - q("update contact set forum = %d where id = %d limit 1", - intval($community_page), - intval($importer['id']) - ); - $importer['forum'] = (string) $community_page; - } - - logger('local_delivery: feed item count = ' . $feed->get_item_quantity()); - - // process any deleted entries - - $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry'); - if(is_array($del_entries) && count($del_entries)) { - foreach($del_entries as $dentry) { - $deleted = false; - if(isset($dentry['attribs']['']['ref'])) { - $uri = $dentry['attribs']['']['ref']; - $deleted = true; - if(isset($dentry['attribs']['']['when'])) { - $when = $dentry['attribs']['']['when']; - $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s'); - } - else - $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s'); - } - if($deleted) { - - // check for relayed deletes to our conversation - - $is_reply = false; - $r = q("select * from item where uri = '%s' and uid = %d limit 1", - dbesc($uri), - intval($importer['importer_uid']) - ); - if(count($r)) { - $parent_uri = $r[0]['parent_uri']; - if($r[0]['id'] != $r[0]['parent']) - $is_reply = true; - } - - if($is_reply) { - $community = false; - - if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) { - $sql_extra = ''; - $community = true; - logger('local_delivery: possible community delete'); - } - else - $sql_extra = " and contact.self = 1 and item.wall = 1 "; - - // was the top-level post for this reply written by somebody on this site? - // Specifically, the recipient? - - $is_a_remote_delete = false; - - $r = q("select `item`.`id`, `item`.`uri`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`, - `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item` - LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - WHERE `item`.`uri` = '%s' AND (`item`.`parent_uri` = '%s' or `item`.`thr_parent` = '%s') - AND `item`.`uid` = %d - $sql_extra - LIMIT 1", - dbesc($parent_uri), - dbesc($parent_uri), - dbesc($parent_uri), - intval($importer['importer_uid']) - ); - if($r && count($r)) - $is_a_remote_delete = true; - - // Does this have the characteristics of a community or private group comment? - // If it's a reply to a wall post on a community/prvgroup page it's a - // valid community comment. Also forum_mode makes it valid for sure. - // If neither, it's not. - - if($is_a_remote_delete && $community) { - if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) { - $is_a_remote_delete = false; - logger('local_delivery: not a community delete'); - } - } - - if($is_a_remote_delete) { - logger('local_delivery: received remote delete'); - } - } - - $r = q("SELECT `item`.*, `contact`.`self` FROM `item` left join contact on `item`.`contact-id` = `contact`.`id` - WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1", - dbesc($uri), - intval($importer['importer_uid']), - intval($importer['id']) - ); - - if(count($r)) { - $item = $r[0]; - - if($item['deleted']) - continue; - - logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG); - - if(($item['verb'] === ACTIVITY_TAG) && ($item['obj_type'] === ACTIVITY_OBJ_TAGTERM)) { - $xo = parse_xml_string($item['object'],false); - $xt = parse_xml_string($item['target'],false); - - if($xt->type === ACTIVITY_OBJ_NOTE) { - $i = q("select * from `item` where uri = '%s' and uid = %d limit 1", - dbesc($xt->id), - intval($importer['importer_uid']) - ); - if(count($i)) { - - // For tags, the owner cannot remove the tag on the author's copy of the post. - - $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false); - $author_remove = (($item['origin'] && $item['self']) ? true : false); - $author_copy = (($item['origin']) ? true : false); - - if($owner_remove && $author_copy) - continue; - if($author_remove || $owner_remove) { -//FIXME - $tags = explode(',',$i[0]['tag']); - $newtags = array(); - if(count($tags)) { - foreach($tags as $tag) - if(trim($tag) !== trim($xo->body)) - $newtags[] = trim($tag); - } - q("update item set tag = '%s' where id = %d limit 1", - dbesc(implode(',',$newtags)), - intval($i[0]['id']) - ); - } - } - } - } - - if($item['uri'] == $item['parent_uri']) { - $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', - `body` = '', `title` = '' - WHERE `parent_uri` = '%s' AND `uid` = %d", - dbesc($when), - dbesc(datetime_convert()), - dbesc($item['uri']), - intval($importer['importer_uid']) - ); - } - else { - $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', - `body` = '', `title` = '' - WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($when), - dbesc(datetime_convert()), - dbesc($uri), - intval($importer['importer_uid']) - ); - - // if this is a relayed delete, propagate it to other recipients - - if($is_a_remote_delete) - proc_run('php',"include/notifier.php","drop",$item['id']); - } - } - } - } - } - - - foreach($feed->get_items() as $item) { - - $is_reply = false; - $item_id = $item->get_id(); - $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to'); - if(isset($rawthread[0]['attribs']['']['ref'])) { - $is_reply = true; - $parent_uri = $rawthread[0]['attribs']['']['ref']; - } - - if($is_reply) { - $community = false; - - if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) { - $sql_extra = ''; - $community = true; - logger('local_delivery: possible community reply'); - } - else - $sql_extra = " and contact.self = 1 and item.wall = 1 "; - - // was the top-level post for this reply written by somebody on this site? - // Specifically, the recipient? - - $is_a_remote_comment = false; - - // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used? - $r = q("select `item`.`id`, `item`.`uri`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`, - `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item` - LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - WHERE `item`.`uri` = '%s' AND (`item`.`parent_uri` = '%s' or `item`.`thr_parent` = '%s') - AND `item`.`uid` = %d - $sql_extra - LIMIT 1", - dbesc($parent_uri), - dbesc($parent_uri), - dbesc($parent_uri), - intval($importer['importer_uid']) - ); - if($r && count($r)) - $is_a_remote_comment = true; - - // Does this have the characteristics of a community or private group comment? - // If it's a reply to a wall post on a community/prvgroup page it's a - // valid community comment. Also forum_mode makes it valid for sure. - // If neither, it's not. - - if($is_a_remote_comment && $community) { - if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) { - $is_a_remote_comment = false; - logger('local_delivery: not a community reply'); - } - } - - if($is_a_remote_comment) { - logger('local_delivery: received remote comment'); - $is_like = false; - // remote reply to our post. Import and then notify everybody else. - - $datarray = get_atom_elements($feed,$item); - - $r = q("SELECT `id`, `uid`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['importer_uid']) - ); - - // Update content if 'updated' changes - - if(count($r)) { - $iid = $r[0]['id']; - if((x($datarray,'edited') !== false) && (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) { - - // do not accept (ignore) an earlier edit than one we currently have. - if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) - continue; - - logger('received updated comment' , LOGGER_DEBUG); - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($datarray['title']), - dbesc($datarray['body']), - dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), - dbesc($item_id), - intval($importer['importer_uid']) - ); - - proc_run('php',"include/notifier.php","comment-import",$iid); - - } - - continue; - } - - - - $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1", - intval($importer['importer_uid']) - ); - - - $datarray['type'] = 'remote-comment'; - $datarray['wall'] = 1; - $datarray['parent_uri'] = $parent_uri; - $datarray['uid'] = $importer['importer_uid']; - $datarray['owner-name'] = $own[0]['name']; - $datarray['owner-link'] = $own[0]['url']; - $datarray['owner-avatar'] = $own[0]['thumb']; - $datarray['contact-id'] = $importer['id']; - - if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) { - $is_like = true; - $datarray['type'] = 'activity'; - $datarray['gravity'] = GRAVITY_LIKE; - - // only one like or dislike per person - $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`thr_parent` = '%s' or `parent_uri` = '%s') and deleted = 0 limit 1", - intval($datarray['uid']), - intval($datarray['contact-id']), - dbesc($datarray['verb']), - dbesc($datarray['parent_uri']), - dbesc($datarray['parent_uri']) - - ); - if($r && count($r)) - continue; - } - - if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['obj_type'] === ACTIVITY_OBJ_TAGTERM)) { - - $xo = parse_xml_string($datarray['object'],false); - $xt = parse_xml_string($datarray['target'],false); - - if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) { - - // fetch the parent item - - $tagp = q("select * from item where uri = '%s' and uid = %d limit 1", - dbesc($xt->id), - intval($importer['importer_uid']) - ); - if(! count($tagp)) - continue; - - // extract tag, if not duplicate, and this user allows tags, add to parent item -//FIXME - if($xo->id && $xo->content) { - $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]'; - if(! (stristr($tagp[0]['tag'],$newtag))) { - $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1", - intval($importer['importer_uid']) - ); - if(count($i) && ! intval($i[0]['blocktags'])) { - q("UPDATE item SET tag = '%s', `edited` = '%s' WHERE id = %d LIMIT 1", - dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag), - intval($tagp[0]['id']), - dbesc(datetime_convert()) - ); - } - } - } - } - } - - - $posted_id = item_store($datarray); - $parent = 0; - - if($posted_id) { - $r = q("SELECT `parent`, `parent_uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", - intval($posted_id), - intval($importer['importer_uid']) - ); - if(count($r)) { - $parent = $r[0]['parent']; - $parent_uri = $r[0]['parent_uri']; - } - - if(! $is_like) { - $r1 = q("UPDATE `item` SET `changed` = '%s' WHERE `uid` = %d AND `parent` = %d", - dbesc(datetime_convert()), - intval($importer['importer_uid']), - intval($r[0]['parent']) - ); - - $r2 = q("UPDATE `item` SET `changed` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1", - dbesc(datetime_convert()), - intval($importer['importer_uid']), - intval($posted_id) - ); - } - - if($posted_id && $parent) { - - proc_run('php',"include/notifier.php","comment-import","$posted_id"); - - if((! $is_like) && (! $importer['self'])) { - - require_once('include/enotify.php'); - - notification(array( - 'type' => NOTIFY_COMMENT, - 'notify_flags' => $importer['notify-flags'], - 'language' => $importer['language'], - 'to_name' => $importer['username'], - 'to_email' => $importer['email'], - 'uid' => $importer['importer_uid'], - 'item' => $datarray, - 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id, - 'source_name' => stripslashes($datarray['author-name']), - 'source_link' => $datarray['author-link'], - 'source_photo' => ((link_compare($datarray['author-link'],$importer['url'])) - ? $importer['thumb'] : $datarray['author-avatar']), - 'verb' => ACTIVITY_POST, - 'otype' => 'item', - 'parent' => $parent, - 'parent_uri' => $parent_uri, - )); - - } - } - - return 0; - // NOTREACHED - } - } - else { - - // regular comment that is part of this total conversation. Have we seen it? If not, import it. - - $item_id = $item->get_id(); - $datarray = get_atom_elements($feed,$item); - - if($importer['rel'] == CONTACT_IS_FOLLOWER) - continue; - - - $r = q("SELECT `uid`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['importer_uid']) - ); - - // Update content if 'updated' changes - - if(count($r)) { - if((x($datarray,'edited') !== false) && (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) { - - // do not accept (ignore) an earlier edit than one we currently have. - if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) - continue; - - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($datarray['title']), - dbesc($datarray['body']), - dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), - dbesc($item_id), - intval($importer['importer_uid']) - ); - } - - continue; - } - - $datarray['parent_uri'] = $parent_uri; - $datarray['uid'] = $importer['importer_uid']; - $datarray['contact-id'] = $importer['id']; - if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) { - $datarray['type'] = 'activity'; - $datarray['gravity'] = GRAVITY_LIKE; - // only one like or dislike per person - $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent_uri` = '%s' OR `thr_parent` = '%s') limit 1", - intval($datarray['uid']), - intval($datarray['contact-id']), - dbesc($datarray['verb']), - dbesc($parent_uri), - dbesc($parent_uri) - ); - if($r && count($r)) - continue; - - } - - if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['obj_type'] === ACTIVITY_OBJ_TAGTERM)) { - - $xo = parse_xml_string($datarray['object'],false); - $xt = parse_xml_string($datarray['target'],false); - - if($xt->type == ACTIVITY_OBJ_NOTE) { - $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1", - dbesc($xt->id), - intval($importer['importer_uid']) - ); - if(! count($r)) - continue; - - // extract tag, if not duplicate, add to parent item - if($xo->content) { - if(! (stristr($r[0]['tag'],trim($xo->content)))) { - q("UPDATE item SET tag = '%s' WHERE id = %d LIMIT 1", - dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'), - intval($r[0]['id']) - ); - } - } - } - } - - $posted_id = item_store($datarray); - - // find out if our user is involved in this conversation and wants to be notified. - - if(!x($datarray['type']) || $datarray['type'] != 'activity') { - - $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent_uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0", - dbesc($parent_uri), - intval($importer['importer_uid']) - ); - - if(count($myconv)) { - $importer_url = $a->get_baseurl() . '/channel/' . $importer['nickname']; - - // first make sure this isn't our own post coming back to us from a wall-to-wall event - if(! link_compare($datarray['author-link'],$importer_url)) { - - - foreach($myconv as $conv) { - - // now if we find a match, it means we're in this conversation - - if(! link_compare($conv['author-link'],$importer_url)) - continue; - - require_once('include/enotify.php'); - - $conv_parent = $conv['parent']; - - notification(array( - 'type' => NOTIFY_COMMENT, - 'notify_flags' => $importer['notify-flags'], - 'language' => $importer['language'], - 'to_name' => $importer['username'], - 'to_email' => $importer['email'], - 'uid' => $importer['importer_uid'], - 'item' => $datarray, - 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id, - 'source_name' => stripslashes($datarray['author-name']), - 'source_link' => $datarray['author-link'], - 'source_photo' => ((link_compare($datarray['author-link'],$importer['url'])) - ? $importer['thumb'] : $datarray['author-avatar']), - 'verb' => ACTIVITY_POST, - 'otype' => 'item', - 'parent' => $conv_parent, - 'parent_uri' => $parent_uri - - )); - - // only send one notification - break; - } - } - } - } - continue; - } - } - - else { - - // Head post of a conversation. Have we seen it? If not, import it. - - - $item_id = $item->get_id(); - $datarray = get_atom_elements($feed,$item); - - if((x($datarray,'obj_type')) && ($datarray['obj_type'] === ACTIVITY_OBJ_EVENT)) { - $ev = bbtoevent($datarray['body']); - if(x($ev,'desc') && x($ev,'start')) { - $ev['cid'] = $importer['id']; - $ev['uid'] = $importer['uid']; - $ev['uri'] = $item_id; - $ev['edited'] = $datarray['edited']; - $ev['private'] = $datarray['private']; - - $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['uid']) - ); - if(count($r)) - $ev['id'] = $r[0]['id']; - $xyz = event_store($ev); - continue; - } - } - - $r = q("SELECT `uid`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['importer_uid']) - ); - - // Update content if 'updated' changes - - if(count($r)) { - if((x($datarray,'edited') !== false) && (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) { - - // do not accept (ignore) an earlier edit than one we currently have. - if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) - continue; - - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($datarray['title']), - dbesc($datarray['body']), - dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), - dbesc($item_id), - intval($importer['importer_uid']) - ); - } - - continue; - } - - // This is my contact on another system, but it's really me. - // Turn this into a wall post. - - if($importer['remote_self']) - $datarray['wall'] = 1; - - $datarray['parent_uri'] = $item_id; - $datarray['uid'] = $importer['importer_uid']; - $datarray['contact-id'] = $importer['id']; - - - if(! link_compare($datarray['owner-link'],$importer['url'])) { - // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, - // but otherwise there's a possible data mixup on the sender's system. - // the tgroup delivery code called from item_store will correct it if it's a forum, - // but we're going to unconditionally correct it here so that the post will always be owned by our contact. - logger('local_delivery: Correcting item owner.', LOGGER_DEBUG); - $datarray['owner-name'] = $importer['senderName']; - $datarray['owner-link'] = $importer['url']; - $datarray['owner-avatar'] = $importer['thumb']; - } - - if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray))) - continue; - - $posted_id = item_store($datarray); - - if(stristr($datarray['verb'],ACTIVITY_POKE)) { - $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1)); - if(! $verb) - continue; - $xo = parse_xml_string($datarray['object'],false); - - if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) { - - // somebody was poked/prodded. Was it me? - - $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false); - - foreach($links->link as $l) { - $atts = $l->attributes(); - switch($atts['rel']) { - case "alternate": - $Blink = $atts['href']; - break; - default: - break; - } - } - if($Blink && link_compare($Blink,$a->get_baseurl() . '/channel/' . $importer['nickname'])) { - - // send a notification - require_once('include/enotify.php'); - - notification(array( - 'type' => NOTIFY_POKE, - 'notify_flags' => $importer['notify-flags'], - 'language' => $importer['language'], - 'to_name' => $importer['username'], - 'to_email' => $importer['email'], - 'uid' => $importer['importer_uid'], - 'item' => $datarray, - 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id, - 'source_name' => stripslashes($datarray['author-name']), - 'source_link' => $datarray['author-link'], - 'source_photo' => ((link_compare($datarray['author-link'],$importer['url'])) - ? $importer['thumb'] : $datarray['author-avatar']), - 'verb' => $datarray['verb'], - 'otype' => 'person', - 'activity' => $verb, - - )); - } - } - } - - continue; - } - } - - return 0; - // NOTREACHED - -} - - -function new_follower($importer,$contact,$datarray,$item,$sharing = false) { - $url = notags(trim($datarray['author-link'])); - $name = notags(trim($datarray['author-name'])); - $photo = notags(trim($datarray['author-avatar'])); - - $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor'); - if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data']) - $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data']; - - if(is_array($contact)) { - if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING) - || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) { - $r = q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d AND `uid` = %d LIMIT 1", - intval(CONTACT_IS_FRIEND), - intval($contact['id']), - intval($importer['uid']) - ); - } - // send email notification to owner? - } - else { - - // create contact record - - $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`, - `blocked`, `readonly`, `pending`, `writable` ) - VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ", - intval($importer['uid']), - dbesc(datetime_convert()), - dbesc($url), - dbesc(normalise_link($url)), - dbesc($name), - dbesc($nick), - dbesc($photo), - dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS), - intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER) - ); - $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1", - intval($importer['uid']), - dbesc($url) - ); - if(count($r)) - $contact_record = $r[0]; - - // create notification - $hash = random_string(); - - if(is_array($contact_record)) { - $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`) - VALUES ( %d, %d, 0, 0, '%s', '%s' )", - intval($importer['uid']), - intval($contact_record['id']), - dbesc($hash), - dbesc(datetime_convert()) - ); - } - $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", - intval($importer['uid']) - ); - $a = get_app(); - if(count($r)) { - - if(intval($r[0]['def_gid'])) { - require_once('include/group.php'); - group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']); - } - - if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) { - $email_tpl = get_intltext_template('follow_notify_eml.tpl'); - $email = replace_macros($email_tpl, array( - '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')), - '$url' => $url, - '$myname' => $r[0]['username'], - '$siteurl' => $a->get_baseurl(), - '$sitename' => $a->config['sitename'] - )); - $res = mail($r[0]['email'], - (($sharing) ? t('A new person is sharing with you at ') : t("You have a new follower at ")) . $a->config['sitename'], - $email, - 'From: ' . t('Administrator') . '@' . $_SERVER['SERVER_NAME'] . "\n" - . 'Content-type: text/plain; charset=UTF-8' . "\n" - . 'Content-transfer-encoding: 8bit' ); - - } - } - } -} - -function lose_follower($importer,$contact,$datarray,$item) { - - if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) { - q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d LIMIT 1", - intval(CONTACT_IS_SHARING), - intval($contact['id']) - ); - } - else { - contact_remove($contact['id']); - } } -function lose_sharer($importer,$contact,$datarray,$item) { - - if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) { - q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d LIMIT 1", - intval(CONTACT_IS_FOLLOWER), - intval($contact['id']) - ); - } - else { - contact_remove($contact['id']); - } -} -function atom_author($tag,$name,$uri,$h,$w,$photo) { +function atom_author($tag,$name,$uri,$h,$w,$type,$photo) { $o = ''; if(! $tag) return $o; @@ -3638,8 +3234,8 @@ function atom_author($tag,$name,$uri,$h,$w,$photo) { $o .= "<$tag>\r\n"; $o .= "<name>$name</name>\r\n"; $o .= "<uri>$uri</uri>\r\n"; - $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n"; - $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n"; + $o .= '<link rel="photo" type="' . $type . '" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n"; + $o .= '<link rel="avatar" type="' . $type . '" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n"; call_hooks('atom_author', $o); @@ -3655,7 +3251,7 @@ function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) { return; if($item['deleted']) - return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n"; + return '<at:deleted-entry ref="' . xmlify($item['mid']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n"; if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid']) @@ -3667,35 +3263,35 @@ function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) { $o = "\r\n\r\n<entry>\r\n"; if(is_array($author)) - $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']); + $o .= atom_author('author',$author['xchan_name'],$author['xchan_url'],80,80,$author['xchan_photo_mimetype'],$author['xchan_photo_m']); else - $o .= atom_author('author',(($item['author-name']) ? $item['author-name'] : $item['name']),(($item['author-link']) ? $item['author-link'] : $item['url']),80,80,(($item['author-avatar']) ? $item['author-avatar'] : $item['thumb'])); - if(strlen($item['owner-name'])) - $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']); + $o .= atom_author('author',$item['author']['xchan_name'],$item['author']['xchan_url'],80,80,$item['author']['xchan_photo_mimetype'], $item['author']['xchan_photo_m']); - if(($item['parent'] != $item['id']) || ($item['parent_uri'] !== $item['uri']) || (($item['thr_parent'] !== '') && ($item['thr_parent'] !== $item['uri']))) { - $parent_item = (($item['thr_parent']) ? $item['thr_parent'] : $item['parent_uri']); - $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n"; + $o .= atom_author('zot:owner',$item['owner']['xchan_name'],$item['owner']['xchan_url'],80,80,$item['owner']['xchan_photo_mimetype'],$item['owner']['xchan_photo_m']); + + if(($item['parent'] != $item['id']) || ($item['parent_mid'] !== $item['mid']) || (($item['thr_parent'] !== '') && ($item['thr_parent'] !== $item['mid']))) { + $parent_item = (($item['thr_parent']) ? $item['thr_parent'] : $item['parent_mid']); + $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($item['plink']) . '" />' . "\r\n"; } - $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n"; + $o .= '<id>' . xmlify($item['mid']) . '</id>' . "\r\n"; $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n"; $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n"; $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n"; - $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n"; - $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\r\n"; - $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n"; + + $o .= '<content type="' . $type . '" >' . xmlify(prepare_text($body,$item['mimetype'])) . '</content>' . "\r\n"; + $o .= '<link rel="alternate" type="text/html" href="' . xmlify($item['plink']) . '" />' . "\r\n"; if($item['location']) { - $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n"; + $o .= '<zot:location>' . xmlify($item['location']) . '</zot:location>' . "\r\n"; $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n"; } if($item['coord']) $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n"; - if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])) - $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n"; + if(($item['item_private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])) + $o .= '<zot:private>' . (($item['item_private']) ? $item['item_private'] : 1) . '</zot:private>' . "\r\n"; if($item['app']) @@ -3711,18 +3307,20 @@ function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) { if(strlen($actarg)) $o .= $actarg; - $tags = item_getfeedtags($item); - if(count($tags)) { - foreach($tags as $t) { - $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n"; - } - } + // FIXME +// $tags = item_getfeedtags($item); +// if(count($tags)) { +// foreach($tags as $t) { +// $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n"; +// } +// } - $o .= item_getfeedattach($item); +// FIXME +// $o .= item_getfeedattach($item); - $mentioned = get_mentions($item,$tags); - if($mentioned) - $o .= $mentioned; +// $mentioned = get_mentions($item,$tags); +// if($mentioned) +// $o .= $mentioned; call_hooks('atom_entry', $o); @@ -3740,9 +3338,9 @@ function fix_private_photos($s, $uid, $item = null, $cid = 0) { $orig_body = $s; $new_body = ''; - $img_start = strpos($orig_body, '[img'); + $img_start = strpos($orig_body, '[zmg'); $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false); - $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false); + $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/zmg]') : false); while( ($img_st_close !== false) && ($img_len !== false) ) { $img_st_close++; // make it point to AFTER the closing bracket @@ -3792,13 +3390,13 @@ function fix_private_photos($s, $uid, $item = null, $cid = 0) { $type = $r[0]['type']; // If a custom width and height were specified, apply before embedding - if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) { + if(preg_match("/\[zmg\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) { logger('fix_private_photos: scaling photo', LOGGER_DEBUG); $width = intval($match[1]); $height = intval($match[2]); - $ph = new Photo($data, $type); + $ph = photo_factory($data, $type); if($ph->is_valid()) { $ph->scaleImage(max($width, $height)); $data = $ph->imageString(); @@ -3814,14 +3412,14 @@ function fix_private_photos($s, $uid, $item = null, $cid = 0) { } } - $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]'; - $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]')); + $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/zmg]'; + $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/zmg]')); if($orig_body === false) $orig_body = ''; - $img_start = strpos($orig_body, '[img'); + $img_start = strpos($orig_body, '[zmg'); $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false); - $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false); + $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/zmg]') : false); } $new_body = $new_body . $orig_body; @@ -3914,62 +3512,64 @@ function item_expire($uid,$days) { // $expire_network_only = save your own wall posts // and just expire conversations started by others + // do not enable this until we can pass bulk delete messages through zot + // $expire_network_only = get_pconfig($uid,'expire','network_only'); - $expire_network_only = get_pconfig($uid,'expire','network_only'); - $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : ""); + $expire_network_only = 1; + + $sql_extra = ((intval($expire_network_only)) ? " AND not (item_flags & " . intval(ITEM_WALL) . ") " : ""); $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY AND `id` = `parent` $sql_extra - AND `deleted` = 0", + AND NOT ( item_flags & %d ) + AND (item_restrict = 0 ) ", intval($uid), - intval($days) + intval($days), + intval(ITEM_RETAINED) ); - if(! count($r)) + if(! $r) return; - $expire_items = get_pconfig($uid, 'expire','items'); - $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1 - - $expire_notes = get_pconfig($uid, 'expire','notes'); - $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1 - - $expire_starred = get_pconfig($uid, 'expire','starred'); - $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1 - - $expire_photos = get_pconfig($uid, 'expire','photos'); - $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0 - - logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos"); + $r = fetch_post_tags($r,true); foreach($r as $item) { // don't expire filed items - if(strpos($item['file'],'[') !== false) + $terms = get_terms_oftype($item['term'],TERM_FILE); + if($terms) { + retain_item($item['id']); continue; + } // Only expire posts, not photos and photo comments - if($expire_photos==0 && strlen($item['resource_id'])) + if($item['resource_type'] === 'photo') { + retain_item($item['id']); continue; - if($expire_starred==0 && intval($item['starred'])) - continue; - if($expire_notes==0 && $item['type']=='note') - continue; - if($expire_items==0 && $item['type']!='note') + } + if($item['item_flags'] & ITEM_STARRED) { + retain_item($item['id']); continue; + } drop_item($item['id'],false); } - proc_run('php',"include/notifier.php","expire","$uid"); +// proc_run('php',"include/notifier.php","expire","$uid"); } +function retain_item($id) { + $r = q("update item set item_flags = (item_flags | %d ) where id = %d limit 1", + intval(ITEM_RETAINED), + intval($id) + ); +} function drop_items($items) { $uid = 0; @@ -3992,17 +3592,26 @@ function drop_items($items) { } -function drop_item($id,$interactive = true) { +// Delete item with given item $id. $interactive means we're running interactively, and must check +// permissions to carry out this act. If it is non-interactive, we are deleting something at the +// system's request and do not check permission. This is very important to know. + +// Some deletion requests (those coming from remote sites) must be staged. +// $stage = 0 => unstaged +// $stage = 1 => set deleted flag on the item and perform intial notifications +// $stage = 2 => perform low level delete at a later stage + +function drop_item($id,$interactive = true,$stage = DROPITEM_NORMAL) { $a = get_app(); // locate item to be deleted - $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1", + $r = q("SELECT * FROM item WHERE id = %d LIMIT 1", intval($id) ); - if(! count($r)) { + if((! $r) || (($r[0]['item_restrict'] & ITEM_DELETED) && ($stage === DROPITEM_NORMAL))) { if(! $interactive) return 0; notice( t('Item not found.') . EOL); @@ -4011,118 +3620,161 @@ function drop_item($id,$interactive = true) { $item = $r[0]; - $owner = $item['uid']; + $ok_to_delete = false; - $cid = 0; + // system deletion + if(! $interactive) + $ok_to_delete = true; - // check if logged in user is either the author or owner of this item - - if(is_array($_SESSION['remote'])) { - foreach($_SESSION['remote'] as $visitor) { - if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) { - $cid = $visitor['cid']; - break; - } - } - } + // owner deletion + if(local_user() && local_user() == $item['uid']) + $ok_to_delete = true; + // author deletion + $observer = $a->get_observer(); + if($observer && $observer['xchan_hash'] && ($observer['xchan_hash'] === $item['author_xchan'])) + $ok_to_delete = true; - if((local_user() == $item['uid']) || ($cid) || (! $interactive)) { + if($ok_to_delete) { - // delete the item + // set the deleted flag immediately on this item just in case the + // hook calls a remote process which loops. We'll delete it properly in a second. - $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d LIMIT 1", - dbesc(datetime_convert()), - dbesc(datetime_convert()), + $r = q("UPDATE item SET item_restrict = ( item_restrict | %d ) WHERE id = %d LIMIT 1", + intval(ITEM_DELETED), intval($item['id']) ); - // clean up categories and tags so they don't end up as orphans + $arr = array('item' => $item, 'interactive' => $interactive, 'stage' => $stage); + call_hooks('drop_item', $arr ); - $matches = false; - $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER); - if($cnt) { - foreach($matches as $mtch) { - file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true); - } + $notify_id = intval($item['id']); + + $items = q("select * from item where parent = %d and uid = %d", + intval($item['id']), + intval($item['uid']) + ); + if($items) { + foreach($items as $i) + delete_item_lowlevel($i,$stage); } + else + delete_item_lowlevel($item,$stage); - $matches = false; + if(! $interactive) + return 1; - $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER); - if($cnt) { - foreach($matches as $mtch) { - file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false); - } - } + // send the notification upstream/downstream as the case may be + // only send notifications to others if this is the owner's wall item. - // If item is a link to a photo resource, nuke all the associated photos - // (visitors will not have photo resources) - // This only applies to photos uploaded from the photos page. Photos inserted into a post do not - // generate a resource_id and therefore aren't intimately linked to the item. + if(($item['item_flags'] & ITEM_WALL) && ($stage != DROPITEM_PHASE2)) + proc_run('php','include/notifier.php','drop',$notify_id); - if(strlen($item['resource_id'])) { - q("DELETE FROM `photo` WHERE `resource_id` = '%s' AND `uid` = %d ", - dbesc($item['resource_id']), - intval($item['uid']) - ); - // ignore the result - } + goaway($a->get_baseurl() . '/' . $_SESSION['return_url']); - // If item is a link to an event, nuke the event record. + } + else { + if(! $interactive) + return 0; + notice( t('Permission denied.') . EOL); + goaway($a->get_baseurl() . '/' . $_SESSION['return_url']); + } + +} - if(intval($item['event-id'])) { - q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d LIMIT 1", - intval($item['event-id']), - intval($item['uid']) - ); - // ignore the result - } +// This function does not check for permission and does not send notifications and does not check recursion. +// It merely destroys all resources associated with an item. +// Please do not use without a suitable wrapper. - // clean up item_id and sign meta-data tables +function delete_item_lowlevel($item,$stage = DROPITEM_NORMAL) { - $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)", - intval($item['id']), - intval($item['uid']) - ); - $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)", - intval($item['id']), - intval($item['uid']) - ); + switch($stage) { + case DROPITEM_PHASE2: + $r = q("UPDATE item SET item_restrict = ( item_restrict | %d ), body = '', title = '', + changed = '%s', edited = '%s' WHERE id = %d LIMIT 1", + intval(ITEM_PENDING_REMOVE), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($item['id']) + ); + break; - // If it's the parent of a comment thread, kill all the kids + case DROPITEM_PHASE1: + $r = q("UPDATE item SET item_restrict = ( item_restrict | %d ), + changed = '%s', edited = '%s' WHERE id = %d LIMIT 1", + intval(ITEM_DELETED), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($item['id']) + ); + break; - if($item['uri'] == $item['parent_uri']) { - $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' - WHERE `parent_uri` = '%s' AND `uid` = %d ", + case DROPITEM_NORMAL: + default: + $r = q("UPDATE item SET item_restrict = ( item_restrict | %d ), body = '', title = '', + changed = '%s', edited = '%s' WHERE id = %d LIMIT 1", + intval(ITEM_DELETED), dbesc(datetime_convert()), dbesc(datetime_convert()), - dbesc($item['parent_uri']), + intval($item['id']) + ); + break; + } + + + // immediately remove any undesired profile likes. + + q("delete from likes where iid = %d and channel_id = %d limit 1", + intval($item['id']), + intval($item['uid']) + ); + + + // network deletion request. Keep the message structure so that we can deliver delete notifications. + // Come back after several days (or perhaps a month) to do the lowlevel delete (DROPITEM_PHASE2). + + if($stage == DROPITEM_PHASE1) + return true; + + $r = q("delete from term where otype = %d and oid = %d limit 1", + intval(TERM_OBJ_POST), + intval($item['id']) + ); + + // If item is a link to a photo resource, nuke all the associated photos + // This only applies to photos uploaded from the photos page. Photos inserted into a post do not + // generate a resource_id and therefore aren't intimately linked to the item. + + if(strlen($item['resource_id'])) { + if($item['resource_type'] === 'event') { + q("delete from event where event_hash = '%s' and uid = %d limit 1", + dbesc($item['resource_id']), + intval($item['uid']) + ); + } + elseif($item['resource_type'] === 'photo') { + q("DELETE FROM `photo` WHERE `resource_id` = '%s' AND `uid` = %d ", + dbesc($item['resource_id']), intval($item['uid']) ); - // ignore the result } + } - $drop_id = intval($item['id']); + q("delete from item_id where iid = %d and uid = %d limit 1", + intval($item['id']), + intval($item['uid']) + ); - // send the notification upstream/downstream as the case may be + q("delete from term where oid = %d and otype = %d", + intval($item['id']), + intval(TERM_OBJ_POST) + ); - if(! $interactive) - return $owner; +// FIXME remove notifications for this item - proc_run('php',"include/notifier.php","drop","$drop_id"); - goaway($a->get_baseurl() . '/' . $_SESSION['return_url']); - //NOTREACHED - } - else { - if(! $interactive) - return 0; - notice( t('Permission denied.') . EOL); - goaway($a->get_baseurl() . '/' . $_SESSION['return_url']); - //NOTREACHED - } - + + return true; } @@ -4137,14 +3789,20 @@ function first_post_date($uid,$wall = false) { intval($uid) ); - if(count($r)) { + if($r) { // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA); return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10); } return false; } -function posted_dates($uid,$wall) { +/** + * modified posted_dates() {below} to arrange the list in years, which we'll eventually + * use to make a menu of years with collapsible sub-menus for the months instead of the + * current flat list of all representative dates. + */ + +function list_post_dates($uid,$wall) { $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d'); $dthen = first_post_date($uid,$wall); @@ -4163,39 +3821,53 @@ function posted_dates($uid,$wall) { // Starting with the current month, get the first and last days of every // month down to and including the month of the first post while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) { + $dyear = intval(substr($dnow,0,4)); $dstart = substr($dnow,0,8) . '01'; $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5))); $start_month = datetime_convert('','',$dstart,'Y-m-d'); $end_month = datetime_convert('','',$dend,'Y-m-d'); - $str = day_translate(datetime_convert('','',$dnow,'F Y')); - $ret[] = array($str,$end_month,$start_month); + $str = day_translate(datetime_convert('','',$dnow,'F')); + if(! $ret[$dyear]) + $ret[$dyear] = array(); + $ret[$dyear][] = array($str,$end_month,$start_month); $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d'); } return $ret; } -function posted_date_widget($url,$uid,$wall) { - $o = ''; +function posted_dates($uid,$wall) { + $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d'); - if(! feature_enabled($uid,'archives')) - return $o; + $dthen = first_post_date($uid,$wall); + if(! $dthen) + return array(); - $ret = posted_dates($uid,$wall); - if(! count($ret)) - return $o; + // If it's near the end of a long month, backup to the 28th so that in + // consecutive loops we'll always get a whole month difference. - $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array( - '$title' => t('Archives'), - '$size' => ((count($ret) > 6) ? 6 : count($ret)), - '$url' => $url, - '$dates' => $ret - )); - return $o; + if(intval(substr($dnow,8)) > 28) + $dnow = substr($dnow,0,8) . '28'; + if(intval(substr($dthen,8)) > 28) + $dnow = substr($dthen,0,8) . '28'; + + $ret = array(); + // Starting with the current month, get the first and last days of every + // month down to and including the month of the first post + while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) { + $dstart = substr($dnow,0,8) . '01'; + $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5))); + $start_month = datetime_convert('','',$dstart,'Y-m-d'); + $end_month = datetime_convert('','',$dend,'Y-m-d'); + $str = day_translate(datetime_convert('','',$dnow,'F Y')); + $ret[] = array($str,$end_month,$start_month); + $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d'); + } + return $ret; } -function fetch_post_tags($items) { +function fetch_post_tags($items,$link = false) { $tag_finder = array(); if($items) { @@ -4226,6 +3898,8 @@ function fetch_post_tags($items) { for($x = 0; $x < count($items); $x ++) { if($tags) { foreach($tags as $t) { + if(($link) && ($t['type'] == TERM_MENTION)) + $t['url'] = chanlink_url($t['url']); if(array_key_exists('item_id',$items[$x])) { if($t['oid'] == $items[$x]['item_id']) { if(! is_array($items[$x]['term'])) @@ -4249,50 +3923,428 @@ function fetch_post_tags($items) { -function zot_feed($uid,$observer,$mindate) { +function zot_feed($uid,$observer_xchan,$mindate) { + $result = array(); $mindate = datetime_convert('UTC','UTC',$mindate); if(! $mindate) $mindate = '0000-00-00 00:00:00'; - if(! perm_is_allowed($uid,$observer,'view_stream')) { + $mindate = dbesc($mindate); + + logger('zot_feed: ' . $uid); + + if(! perm_is_allowed($uid,$observer_xchan,'view_stream')) { + logger('zot_feed: permission denied.'); return $result; } -// FIXME - $sql_extra = item_permissions_sql($uid,$remote_contact,$groups); + if(! is_sys_channel($uid)) { + require_once('include/security.php'); + $sql_extra = item_permissions_sql($uid); + } - if($mindate != '0000-00-00 00:00:00') + if($mindate != '0000-00-00 00:00:00') { $sql_extra .= " and created > '$mindate' "; + $limit = ""; + } + else + $limit = " limit 0, 50 "; + $items = array(); + + if(is_sys_channel($uid)) { + require_once('include/security.php'); + $r = q("SELECT distinct parent from item + WHERE uid != %d + and uid in (" . stream_perms_api_uids(PERMS_PUBLIC) . ") AND item_restrict = 0 + AND (item_flags & %d) + and item_private = 0 $sql_extra ORDER BY created ASC $limit", + intval($uid), + intval(ITEM_WALL) + ); + } + else { + $r = q("SELECT distinct parent from item + WHERE uid = %d AND item_restrict = 0 + AND (item_flags & %d) + $sql_extra ORDER BY created ASC $limit", + intval($uid), + intval(ITEM_WALL) + ); + } + + if($r) { + $parents_str = ids_to_querystr($r,'parent'); + $sys_query = ((is_sys_channel($uid)) ? $sql_extra : ''); + + $items = q("SELECT `item`.*, `item`.`id` AS `item_id` FROM `item` + WHERE `item`.`item_restrict` = 0 + AND `item`.`parent` IN ( %s ) $sys_query ", + dbesc($parents_str) + ); + } -// FIXME - // We probably should use two queries and pick up total conversations. - // For now get a chunk of raw posts in ascending created order so that - // hopefully the parent is imported before we see the kids. - // This will fail if there are more than $limit kids and you didn't - // receive the parent via direct delivery - - $limit = 200; - - $items = q("SELECT item.* from item - WHERE uid = %d AND item_restrict = 0 - AND (item_flags & %d) - $sql_extra ORDER BY created ASC limit 0, $limit", - intval($uid), - intval(ITEM_WALL) - ); if($items) { xchan_query($items); $items = fetch_post_tags($items); - } else { - $items = array(); + require_once('include/conversation.php'); + $items = conv_sort($items,'ascending'); + } + else + $items = array(); + + + logger('zot_feed: number items: ' . count($items),LOGGER_DEBUG); foreach($items as $item) $result[] = encode_item($item); return $result; +} + + + +function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = CLIENT_MODE_NORMAL,$module = 'network') { + + $result = array('success' => false); + + $a = get_app(); + + $sql_extra = ''; + $sql_nets = ''; + $sql_options = ''; + $sql_extra2 = ''; + $sql_extra3 = ''; + $def_acl = ''; + + $item_uids = ' true '; + + if ($arr['uid']) $uid= $arr['uid']; + + if($channel) { + $uid = $channel['channel_id']; + $uidhash = $channel['channel_hash']; + $item_uids = " item.uid = " . intval($uid) . " "; + } + + if($arr['star']) + $sql_options .= " and (item_flags & " . intval(ITEM_STARRED) . ") "; + + if($arr['wall']) + $sql_options .= " and (item_flags & " . intval(ITEM_WALL) . ") "; + + $sql_extra = " AND item.parent IN ( SELECT parent FROM item WHERE (item_flags & " . intval(ITEM_THREAD_TOP) . ") $sql_options ) "; + + if($arr['since_id']) + $sql_extra .= " and item.id > " . $since_id . " "; + + if($arr['gid'] && $uid) { + $r = q("SELECT * FROM `groups` WHERE id = %d AND uid = %d LIMIT 1", + intval($arr['group']), + intval($uid) + ); + if(! $r) { + $result['message'] = t('Collection not found.'); + return $result; + } + + $contact_str = ''; + $contacts = group_get_members($group); + if($contacts) { + foreach($contacts as $c) { + if($contact_str) + $contact_str .= ','; + $contact_str .= "'" . $c['xchan'] . "'"; + } + } + else { + $contact_str = ' 0 '; + $result['message'] = t('Collection is empty.'); + return $result; + } + + $sql_extra = " AND item.parent IN ( SELECT DISTINCT parent FROM item WHERE true $sql_options AND (( author_xchan IN ( $contact_str ) OR owner_xchan in ( $contact_str)) or allow_gid like '" . protect_sprintf('%<' . dbesc($r[0]['hash']) . '>%') . "' ) and id = parent and item_restrict = 0 ) "; + + $x = group_rec_byhash($uid,$r[0]['hash']); + $result['headline'] = sprintf( t('Collection: %s'),$x['name']); + + } + elseif($arr['cid'] && $uid) { + + $r = q("SELECT abook.*, xchan.* from abook left join xchan on abook_xchan = xchan_hash where abook_id = %d and abook_channel = %d and not ( abook_flags & " . intval(ABOOK_FLAG_BLOCKED) . ") limit 1", + intval($arr['cid']), + intval(local_user()) + ); + if($r) { + $sql_extra = " AND item.parent IN ( SELECT DISTINCT parent FROM item WHERE true $sql_options AND uid = " . intval($arr['uid']) . " AND ( author_xchan = '" . dbesc($r[0]['abook_xchan']) . "' or owner_xchan = '" . dbesc($r[0]['abook_xchan']) . "' ) and item_restrict = 0 ) "; + $result['headline'] = sprintf( t('Connection: %s'),$r[0]['xchan_name']); + } + else { + $result['message'] = t('Connection not found.'); + return $result; + } + } + + if($arr['datequery']) { + $sql_extra3 .= protect_sprintf(sprintf(" AND item.created <= '%s' ", dbesc(datetime_convert(date_default_timezone_get(),'',$arr['datequery'])))); + } + if($arr['datequery2']) { + $sql_extra3 .= protect_sprintf(sprintf(" AND item.created >= '%s' ", dbesc(datetime_convert(date_default_timezone_get(),'',$arr['datequery2'])))); + } + + if(! array_key_exists('nouveau',$arr)) { + $sql_extra2 = " AND item.parent = item.id "; + $sql_extra3 = ''; + } + + if($arr['search']) { + if(strpos($arr['search'],'#') === 0) + $sql_extra .= term_query('item',substr($arr['search'],1),TERM_HASHTAG); + else + $sql_extra .= sprintf(" AND item.body like '%s' ", + dbesc(protect_sprintf('%' . $arr['search'] . '%')) + ); + } + + if(strlen($arr['file'])) { + $sql_extra .= term_query('item',$arr['files'],TERM_FILE); + } + + if($arr['conv'] && $channel) { + $sql_extra .= sprintf(" AND parent IN (SELECT distinct parent from item where ( author_xchan like '%s' or ( item_flags & %d ))) ", + dbesc(protect_sprintf($uidhash)), + intval(ITEM_MENTIONSME) + ); + } + + + if(($client_mode & CLIENT_MODE_UPDATE) && (! ($client_mode & CLIENT_MODE_LOAD))) { + + // only setup pagination on initial page view + $pager_sql = ''; + + } + else { + $itemspage = (($channel) ? get_pconfig($uid,'system','itemspage') : 20); + $a->set_pager_itemspage(((intval($itemspage)) ? $itemspage : 20)); + $pager_sql = sprintf(" LIMIT %d, %d ",intval(get_app()->pager['start']), intval(get_app()->pager['itemspage'])); + } + + if(isset($arr['start']) && isset($arr['records'])) + $pager_sql = sprintf(" LIMIT %d, %d ",intval($arr['start']), intval($arr['records'])); + + if(array_key_exists('cmin',$arr) || array_key_exists('cmax',$arr)) { + if(($arr['cmin'] != 0) || ($arr['cmax'] != 99)) { + + // Not everybody who shows up in the network stream will be in your address book. + // By default those that aren't are assumed to have closeness = 99; but this isn't + // recorded anywhere. So if cmax is 99, we'll open the search up to anybody in + // the stream with a NULL address book entry. + + $sql_nets .= " AND "; + + if($arr['cmax'] == 99) + $sql_nets .= " ( "; + + $sql_nets .= "( abook.abook_closeness >= " . intval($arr['cmin']) . " "; + $sql_nets .= " AND abook.abook_closeness <= " . intval($arr['cmax']) . " ) "; + if($cmax == 99) + $sql_nets .= " OR abook.abook_closeness IS NULL ) "; + } + } + + $simple_update = (($client_mode & CLIENT_MODE_UPDATE) ? " and ( item.item_flags & " . intval(ITEM_UNSEEN) . " ) " : ''); + if($client_mode & CLIENT_MODE_LOAD) + $simple_update = ''; + + $start = dba_timer(); + + require_once('include/security.php'); + $sql_extra .= item_permissions_sql($channel['channel_id']); + + if($arr['pages']) + $item_restrict = " AND (item_restrict & " . ITEM_WEBPAGE . ") "; + else + $item_restrict = " AND item_restrict = 0 "; + + + if($arr['nouveau'] && ($client_mode & CLIENT_MODE_LOAD) && $channel) { + // "New Item View" - show all items unthreaded in reverse created date order + + $items = q("SELECT item.*, item.id AS item_id FROM item + WHERE $item_uids $item_restrict + $simple_update + $sql_extra $sql_nets + ORDER BY item.received DESC $pager_sql " + ); + + require_once('include/items.php'); + + xchan_query($items); + + $items = fetch_post_tags($items,true); + } + else { + + // Normal conversation view + + if($arr['order'] === 'post') + $ordering = "created"; + else + $ordering = "commented"; + + if(($client_mode & CLIENT_MODE_LOAD) || ($client_mode == CLIENT_MODE_NORMAL)) { + + // Fetch a page full of parent items for this page + + $r = q("SELECT distinct item.id AS item_id FROM item + left join abook on item.author_xchan = abook.abook_xchan + WHERE $item_uids $item_restrict + AND item.parent = item.id + and ((abook.abook_flags & %d) = 0 or abook.abook_flags is null) + $sql_extra3 $sql_extra $sql_nets + ORDER BY item.$ordering DESC $pager_sql ", + intval(ABOOK_FLAG_BLOCKED) + ); + + } + else { + // update + $r = q("SELECT item.parent AS item_id FROM item + left join abook on item.author_xchan = abook.abook_xchan + WHERE $item_uids $item_restrict $simple_update + and ((abook.abook_flags & %d) = 0 or abook.abook_flags is null) + $sql_extra3 $sql_extra $sql_nets ", + intval(ABOOK_FLAG_BLOCKED) + ); + } + + $first = dba_timer(); + + // Then fetch all the children of the parents that are on this page + + if($r) { + + $parents_str = ids_to_querystr($r,'item_id'); + + $items = q("SELECT item.*, item.id AS item_id FROM item + WHERE $item_uids $item_restrict + AND item.parent IN ( %s ) + $sql_extra ", + dbesc($parents_str) + ); + + $second = dba_timer(); + xchan_query($items); + + $third = dba_timer(); + + $items = fetch_post_tags($items,true); + + $fourth = dba_timer(); + + require_once('include/conversation.php'); + $items = conv_sort($items,$ordering); + + //logger('items: ' . print_r($items,true)); + + } + else { + $items = array(); + } + + if($parents_str && $arr['mark_seen']) + $update_unseen = ' AND parent IN ( ' . dbesc($parents_str) . ' )'; + // FIXME finish mark unseen sql + } + + return $items; +} + + +function update_remote_id($channel,$post_id,$webpage,$pagetitle,$namespace,$remote_id,$mid) { + + $page_type = ''; + + if($webpage & ITEM_WEBPAGE) + $page_type = 'WEBPAGE'; + elseif($webpage & ITEM_BUILDBLOCK) + $page_type = 'BUILDBLOCK'; + elseif($webpage & ITEM_PDL) + $page_type = 'PDL'; + elseif($namespace && $remote_id) { + $page_type = $namespace; + $pagetitle = $remote_id; + } + + if($page_type) { + + // store page info as an alternate message_id so we can access it via + // https://sitename/page/$channelname/$pagetitle + // if no pagetitle was given or it couldn't be transliterated into a url, use the first + // sixteen bytes of the mid - which makes the link portable and not quite as daunting + // as the entire mid. If it were the post_id the link would be less portable. + + $r = q("select * from item_id where iid = %d and uid = %d and service = '%s' limit 1", + intval($post_id), + intval($channel['channel_id']), + dbesc($page_type) + ); + if($r) { + q("update item_id set sid = '%s' where id = %d limit 1", + dbesc(($pagetitle) ? $pagetitle : substr($mid,0,16)), + intval($r[0]['id']) + ); + } + else { + q("insert into item_id ( iid, uid, sid, service ) values ( %d, %d, '%s','%s' )", + intval($post_id), + intval($channel['channel_id']), + dbesc(($pagetitle) ? $pagetitle : substr($mid,0,16)), + dbesc($page_type) + ); + } + } + +} + + + +/** + * change access control for item with message_id $mid and channel_id $uid + */ + + +function item_add_cid($xchan_hash,$mid,$uid) { + $r = q("select id from item where mid = '%s' and uid = %d and allow_cid like '%s'", + dbesc($mid), + intval($uid), + dbesc('<' . $xchan_hash . '>') + ); + if(! $r) { + $r = q("update item set allow_cid = concat(allow_cid,'%s') where mid = '%s' and uid = %d limit 1", + dbesc('<' . $xchan_hash . '>'), + dbesc($mid), + intval($uid) + ); + } +} + +function item_remove_cid($xchan_hash,$mid,$uid) { + $r = q("select allow_cid from item where mid = '%s' and uid = %d and allow_cid like '%s'", + dbesc($mid), + intval($uid), + dbesc('<' . $xchan_hash . '>') + ); + if($r) { + $x = q("update item set allow_cid = '%s' where mid = '%s' and uid = %d limit 1", + dbesc(str_replace('<' . $xchan_hash . '>','',$r[0]['allow_cid'])), + dbesc($mid), + intval($uid) + ); + } } diff --git a/include/js_strings.php b/include/js_strings.php index 60a3e1a86..cda66a09c 100644 --- a/include/js_strings.php +++ b/include/js_strings.php @@ -1,17 +1,23 @@ -<?php +<?php /** @file */ function js_strings() { return replace_macros(get_markup_template('js_strings.tpl'), array( - '$delitem' => t('Delete this item?'), - '$comment' => t('Comment'), - '$showmore' => t('show more'), - '$showfewer' => t('show fewer'), - '$pwshort' => t("Password too short"), - '$pwnomatch' => t("Passwords do not match"), - '$everybody' => t('everybody'), + '$delitem' => t('Delete this item?'), + '$comment' => t('Comment'), + '$showmore' => t('[+] show all'), + '$showfewer' => t('[-] show less'), + '$divgrowmore' => t('[+] expand'), + '$divgrowless' => t('[-] collapse'), + '$pwshort' => t("Password too short"), + '$pwnomatch' => t("Passwords do not match"), + '$everybody' => t('everybody'), + '$passphrase' => t('Secret Passphrase'), + '$passhint' => t('Passphrase hint'), + '$permschange' => t('Notice: Permissions have changed but have not yet been submitted.'), + '$closeAll' => t('close all'), - '$t01' => ((t('timeago.prefixAgo') != 'timeago.prefixAgo') ? t('timeago.prefixAgo') : 'null'), - '$t02' => ((t('timeago.suffixAgo') != 'timeago.suffixAgo') ? t('timeago.suffixAgo') : 'null'), + '$t01' => ((t('timeago.prefixAgo') != 'timeago.prefixAgo') ? t('timeago.prefixAgo') : ''), + '$t02' => ((t('timeago.prefixFromNow') != 'timeago.prefixFromNow') ? t('timeago.prefixFromNow') : ''), '$t03' => t('ago'), '$t04' => t('from now'), '$t05' => t('less than a minute'), @@ -28,6 +34,5 @@ function js_strings() { '$t16' => t(' '), // wordSeparator '$t17' => ((t('timeago.numbers') != 'timeago.numbers') ? t('timeago.numbers') : '[]') - )); -}
\ No newline at end of file +} diff --git a/include/language.php b/include/language.php index 56d5f1cf4..855d94505 100644 --- a/include/language.php +++ b/include/language.php @@ -1,57 +1,69 @@ <?php - - /** - * translation support + * @file + * + * @brief translation support + * + * This file contains functions to work with translations and other + * language related tasks. */ /** + * @brief Get the browser's submitted preferred languages. + * + * This functions parses the HTTP_ACCEPT_LANGUAGE header sent by the browser and + * extracts the preferred languages and their priority. * * Get the language setting directly from system variables, bypassing get_config() * as database may not yet be configured. * * If possible, we use the value from the browser. * + * @return array with ordered list of preferred languages from browser */ - - -if(! function_exists('get_browser_language')) { function get_browser_language() { - $langs = array(); if (x($_SERVER,'HTTP_ACCEPT_LANGUAGE')) { - // break up string into pieces (languages and q factors) - preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', + // break up string into pieces (languages and q factors) + preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse); - if (count($lang_parse[1])) { - // create a list like "en" => 0.8 - $langs = array_combine($lang_parse[1], $lang_parse[4]); - - // set default to 1 for any without q factor - foreach ($langs as $lang => $val) { - if ($val === '') $langs[$lang] = 1; - } - - // sort list based on value - arsort($langs, SORT_NUMERIC); - } + if (count($lang_parse[1])) { + // create a list like "en" => 0.8 + $langs = array_combine($lang_parse[1], $lang_parse[4]); + + // set default to 1 for any without q factor + foreach ($langs as $lang => $val) { + if ($val === '') $langs[$lang] = 1; + } + + // sort list based on value + arsort($langs, SORT_NUMERIC); + } } - else - $langs['en'] = 1; return $langs; -}} - +} +/** + * @brief Returns the best language for which also a translation exists. + * + * This function takes the results from get_browser_language() and compares it + * with the available translations and returns the best fitting language for + * which there exists a translation. + * + * If there is no match fall back to config['system']['language'] + * + * @return Language code in 2-letter ISO 639-1 (en). + */ function get_best_language() { - $langs = get_browser_language(); if(isset($langs) && count($langs)) { foreach ($langs as $lang => $v) { + $lang = strtolower($lang); if(file_exists("view/$lang") && is_dir("view/$lang")) { $preferred = $lang; break; @@ -62,7 +74,7 @@ function get_best_language() { if(isset($preferred)) return $preferred; - $a = get_app(); + $a = get_app(); return ((isset($a->config['system']['language'])) ? $a->config['system']['language'] : 'en'); } @@ -81,7 +93,6 @@ function push_lang($language) { $a->strings = array(); load_translation_table($language); $a->language = $language; - } function pop_lang() { @@ -101,15 +112,25 @@ function pop_lang() { // load string translation table for alternate language -if(! function_exists('load_translation_table')) { -function load_translation_table($lang) { +function load_translation_table($lang, $install = false) { global $a; + $a->strings = array(); if(file_exists("view/$lang/strings.php")) { include("view/$lang/strings.php"); } - else - $a->strings = array(); + + if(! $install) { + $plugins = q("SELECT name FROM addon WHERE installed=1;"); + if ($plugins !== false) { + foreach($plugins as $p) { + $name = $p['name']; + if(file_exists("addon/$name/lang/$lang/strings.php")) { + include("addon/$name/lang/$lang/strings.php"); + } + } + } + } // Allow individual strings to be over-ridden on this site // Either for the default language or for all languages @@ -118,128 +139,98 @@ function load_translation_table($lang) { include("view/local-$lang/strings.php"); } -}} - -// translate string if translation exists - -if(! function_exists('t')) { -function t($s) { +} +/** + * @brief translate string if translation exists. + * + * @param $s string that should get translated + * @param $ctx optional context to appear in po file + * @return translated string if exsists, otherwise s + * + */ +function t($s,$ctx = '') { global $a; - if(x($a->strings,$s)) { - $t = $a->strings[$s]; - return is_array($t)?$t[0]:$t; + $cs = $ctx?"__ctx:".$ctx."__ ".$s:$s; + if(x($a->strings,$cs)) { + $t = $a->strings[$cs]; + return is_array($t) ? $t[0] : $t; } return $s; -}} +} -if(! function_exists('tt')){ -function tt($singular, $plural, $count){ + +function tt($singular, $plural, $count, $ctx = ''){ $a = get_app(); - if(x($a->strings,$singular)) { - $t = $a->strings[$singular]; - $f = 'string_plural_select_' . str_replace('-','_',$a->language); + $cs = $ctx?"__ctx:".$ctx."__ ".$singular:$singular; + if(x($a->strings,$cs)) { + $t = $a->strings[$cs]; + $f = 'string_plural_select_' . str_replace('-', '_', $a->language); if(! function_exists($f)) $f = 'string_plural_select_default'; $k = $f($count); - return is_array($t)?$t[$k]:$t; + return is_array($t) ? $t[$k] : $t; } - if ($count!=1){ + if ($count != 1){ return $plural; } else { return $singular; } -}} +} // provide a fallback which will not collide with // a function defined in any language file -if(! function_exists('string_plural_select_default')) { function string_plural_select_default($n) { return ($n != 1); -}} - - +} +/** + * @brief Takes a string and tries to identify the language. + * + * It uses the pear library Text_LanguageDetect and it can identify 52 human languages. + * It returns the identified languges and a confidence score for each. + * + * Strings need to have a min length config['system']['language_detect_min_length'] + * and you can influence the confidence that must be met before a result will get + * returned through config['system']['language_detect_min_confidence']. + * + * @see http://pear.php.net/package/Text_LanguageDetect + * @param s A string to examine + * @return Language code in 2-letter ISO 639-1 (en, de, fr) format + */ function detect_language($s) { - - $detected_languages = array( - 'Albanian' => 'sq', - 'Arabic' => 'ar', - 'Azeri' => 'az', - 'Bengali' => 'bn', - 'Bulgarian' => 'bg', - 'Cebuano' => '', - 'Croatian' => 'hr', - 'Czech' => 'cz', - 'Danish' => 'da', - 'Dutch' => 'nl', - 'English' => 'en', - 'Estonian' => 'et', - 'Farsi' => 'fa', - 'Finnish' => 'fi', - 'French' => 'fr', - 'German' => 'de', - 'Hausa' => 'ha', - 'Hawaiian' => '', - 'Hindi' => 'hi', - 'Hungarian' => 'hu', - 'Icelandic' => 'is', - 'Indonesian' => 'id', - 'Italian' => 'it', - 'Kazakh' => 'kk', - 'Kyrgyz' => 'ky', - 'Latin' => 'la', - 'Latvian' => 'lv', - 'Lithuanian' => 'lt', - 'Macedonian' => 'mk', - 'Mongolian' => 'mn', - 'Nepali' => 'ne', - 'Norwegian' => 'no', - 'Pashto' => 'ps', - 'Pidgin' => '', - 'Polish' => 'pl', - 'Portuguese' => 'pt', - 'Romanian' => 'ro', - 'Russian' => 'ru', - 'Serbian' => 'sr', - 'Slovak' => 'sk', - 'Slovene' => 'sl', - 'Somali' => 'so', - 'Spanish' => 'es', - 'Swahili' => 'sw', - 'Swedish' => 'sv', - 'Tagalog' => 'tl', - 'Turkish' => 'tr', - 'Ukrainian' => 'uk', - 'Urdu' => 'ur', - 'Uzbek' => 'uz', - 'Vietnamese' => 'vi', - 'Welsh' => 'cy' - ); - require_once('Text/LanguageDetect.php'); - $min_length = get_config('system','language_detect_min_length'); + $min_length = get_config('system', 'language_detect_min_length'); if($min_length === false) $min_length = LANGUAGE_DETECT_MIN_LENGTH; - $min_confidence = get_config('system','language_detect_min_confidence'); + $min_confidence = get_config('system', 'language_detect_min_confidence'); if($min_confidence === false) $min_confidence = LANGUAGE_DETECT_MIN_CONFIDENCE; - - $naked_body = preg_replace('/\[(.+?)\]/','',$s); - if(mb_strlen($naked_body) < intval($min_length)) + // embedded apps have long base64 strings which will trip up the detector. + $naked_body = preg_replace('/\[app\](.*?)\[\/app\]/','',$s); + // strip off bbcode + $naked_body = preg_replace('/\[(.+?)\]/', '', $naked_body); + if(mb_strlen($naked_body) < intval($min_length)) { + logger('detect language: string length less than ' . intval($min_length), LOGGER_DATA); return ''; + } $l = new Text_LanguageDetect; - $lng = $l->detectConfidence($naked_body); - - logger('detect language: ' . print_r($lng,true) . $naked_body, LOGGER_DATA); + try { + // return 2-letter ISO 639-1 (en) language code + $l->setNameMode(2); + $lng = $l->detectConfidence($naked_body); + logger('detect language: ' . print_r($lng, true) . $naked_body, LOGGER_DATA); + } catch (Text_LanguageDetect_Exception $e) { + logger('detect language exception: ' . $e->getMessage(), LOGGER_DATA); + } if((! $lng) || (! (x($lng,'language')))) { return ''; @@ -250,6 +241,29 @@ function detect_language($s) { return ''; } - return(($lng && (x($lng,'language'))) ? $detected_languages[ucfirst($lng['language'])] : ''); + return($lng['language']); +} +/** + * @brief Returns the display name of a given language code. + * + * By default we use the localized language name. You can switch the result + * to any language with the optional 2nd parameter $l. + * + * $s and $l can be in any format that PHP's Locale understands. We will mostly + * use the 2-letter ISO 639-1 (en, de, fr) format. + * + * If nothing could be looked up it returns $s. + * + * @param $s Language code to look up + * @param $l (optional) In which language to return the name + * @return string with the language name, or $s if unrecognized + */ +function get_language_name($s, $l = null) { + if($l === null) + $l = $s; + + logger('get_language_name: for ' . $s . ' in ' . $l . ' returns: ' . Locale::getDisplayLanguage($s, $l), LOGGER_DEBUG); + return Locale::getDisplayLanguage($s, $l); } + diff --git a/include/menu.php b/include/menu.php new file mode 100644 index 000000000..4b0a11f10 --- /dev/null +++ b/include/menu.php @@ -0,0 +1,323 @@ +<?php /** @file */ + +require_once('include/security.php'); +require_once('include/bbcode.php'); + +function menu_fetch($name,$uid,$observer_xchan) { + + $sql_options = permissions_sql($uid); + + $r = q("select * from menu where menu_channel_id = %d and menu_name = '%s' limit 1", + intval($uid), + dbesc($name) + ); + if($r) { + $x = q("select * from menu_item where mitem_menu_id = %d and mitem_channel_id = %d + $sql_options + order by mitem_order asc, mitem_desc asc", + intval($r[0]['menu_id']), + intval($uid) + ); + return array('menu' => $r[0], 'items' => $x ); + } + + return null; +} + +function menu_render($menu, $class='', $edit = false) { + if(! $menu) + return ''; + + for($x = 0; $x < count($menu['items']); $x ++) { + if($menu['items'][$x]['mitem_flags'] & MENU_ITEM_ZID) + $menu['items'][$x]['mitem_link'] = zid($menu['items'][$x]['mitem_link']); + if($menu['items'][$x]['mitem_flags'] & MENU_ITEM_NEWWIN) + $menu['items'][$x]['newwin'] = '1'; + $menu['items'][$x]['mitem_desc'] = bbcode($menu['items'][$x]['mitem_desc']); + } + + return replace_macros(get_markup_template('usermenu.tpl'),array( + '$menu' => $menu['menu'], + '$class' => $class, + '$edit' => (($edit) ? t("Edit") : ''), + '$items' => $menu['items'] + )); +} + + + +function menu_fetch_id($menu_id,$channel_id) { + + $r = q("select * from menu where menu_id = %d and menu_channel_id = %d limit 1", + intval($menu_id), + intval($channel_id) + ); + + return (($r) ? $r[0] : false); +} + + + +function menu_create($arr) { + + + $menu_name = trim(escape_tags($arr['menu_name'])); + $menu_desc = trim(escape_tags($arr['menu_desc'])); + $menu_flags = intval($arr['menu_flags']); + + + if(! $menu_desc) + $menu_desc = $menu_name; + + if(! $menu_name) + return false; + + if(! $menu_flags) + $menu_flags = 0; + + + $menu_channel_id = intval($arr['menu_channel_id']); + + $r = q("select * from menu where menu_name = '%s' and menu_channel_id = %d limit 1", + dbesc($menu_name), + intval($menu_channel_id) + ); + + if($r) + return false; + + $r = q("insert into menu ( menu_name, menu_desc, menu_flags, menu_channel_id ) + values( '%s', '%s', %d, %d )", + dbesc($menu_name), + dbesc($menu_desc), + intval($menu_flags), + intval($menu_channel_id) + ); + if(! $r) + return false; + + $r = q("select menu_id from menu where menu_name = '%s' and menu_channel_id = %d limit 1", + dbesc($menu_name), + intval($menu_channel_id) + ); + if($r) + return $r[0]['menu_id']; + return false; + +} + +/** + * If $flags is present, check that all the bits in $flags are set + * so that MENU_SYSTEM|MENU_BOOKMARK will return entries with both + * bits set. We will use this to find system generated bookmarks. + */ + +function menu_list($channel_id, $name = '', $flags = 0) { + + $sel_options = ''; + $sel_options .= (($name) ? " and menu_name = '" . protect_sprintf(dbesc($name)) . "' " : ''); + $sel_options .= (($flags) ? " and menu_flags = " . intval($flags) . " " : ''); + + $r = q("select * from menu where menu_channel_id = %d $sel_options order by menu_desc", + intval($channel_id) + ); + return $r; +} + + + +function menu_edit($arr) { + + $menu_id = intval($arr['menu_id']); + + $menu_name = trim(escape_tags($arr['menu_name'])); + $menu_desc = trim(escape_tags($arr['menu_desc'])); + $menu_flags = intval($arr['menu_flags']); + + if(! $menu_desc) + $menu_desc = $menu_name; + + if(! $menu_name) + return false; + + if(! $menu_flags) + $menu_flags = 0; + + + $menu_channel_id = intval($arr['menu_channel_id']); + + $r = q("select menu_id from menu where menu_name = '%s' and menu_channel_id = %d limit 1", + dbesc($menu_name), + intval($menu_channel_id) + ); + if(($r) && ($r[0]['menu_id'] != $menu_id)) { + logger('menu_edit: duplicate menu name for channel ' . $menu_channel_id); + return false; + } + + + $r = q("select * from menu where menu_id = %d and menu_channel_id = %d limit 1", + intval($menu_id), + intval($menu_channel_id) + ); + if(! $r) { + logger('menu_edit: not found: ' . print_r($arr,true)); + return false; + } + + return q("update menu set menu_name = '%s', menu_desc = '%s', menu_flags = %d + where menu_id = %d and menu_channel_id = %d limit 1", + dbesc($menu_name), + dbesc($menu_desc), + intval($menu_flags), + intval($menu_id), + intval($menu_channel_id) + ); +} + +function menu_delete($menu_name, $uid) { + $r = q("select menu_id from menu where menu_name = '%s' and menu_channel_id = %d limit 1", + dbesc($menu_name), + intval($uid) + ); + + if($r) + return menu_delete_id($r[0]['menu_id'],$uid); + return false; +} + +function menu_delete_id($menu_id, $uid) { + $r = q("select menu_id from menu where menu_id = %d and menu_channel_id = %d limit 1", + intval($menu_id), + intval($uid) + ); + if($r) { + $x = q("delete from menu_item where mitem_menu_id = %d and mitem_channel_id = %d", + intval($menu_id), + intval($uid) + ); + return q("delete from menu where menu_id = %d and menu_channel_id = %d limit 1", + intval($menu_id), + intval($uid) + ); + } + return false; +} + + +function menu_add_item($menu_id, $uid, $arr) { + + + $mitem_link = escape_tags($arr['mitem_link']); + $mitem_desc = escape_tags($arr['mitem_desc']); + $mitem_order = intval($arr['mitem_order']); + $mitem_flags = intval($arr['mitem_flags']); + + if(local_user() == $uid) { + $channel = get_app()->get_channel(); + } + + if (($channel) + && (! $arr['contact_allow']) + && (! $arr['group_allow']) + && (! $arr['contact_deny']) + && (! $arr['group_deny'])) { + $str_group_allow = $channel['channel_allow_gid']; + $str_contact_allow = $channel['channel_allow_cid']; + $str_group_deny = $channel['channel_deny_gid']; + $str_contact_deny = $channel['channel_deny_cid']; + } + else { + + // use the posted permissions + + $str_group_allow = perms2str($arr['group_allow']); + $str_contact_allow = perms2str($arr['contact_allow']); + $str_group_deny = perms2str($arr['group_deny']); + $str_contact_deny = perms2str($arr['contact_deny']); + } + +// unused +// $allow_cid = perms2str($arr['allow_cid']); +// $allow_gid = perms2str($arr['allow_gid']); +// $deny_cid = perms2str($arr['deny_cid']); +// $deny_gid = perms2str($arr['deny_gid']); + + $r = q("insert into menu_item ( mitem_link, mitem_desc, mitem_flags, allow_cid, allow_gid, deny_cid, deny_gid, mitem_channel_id, mitem_menu_id, mitem_order ) values ( '%s', '%s', %d, '%s', '%s', '%s', '%s', %d, %d, %d ) ", + dbesc($mitem_link), + dbesc($mitem_desc), + intval($mitem_flags), + dbesc($str_contact_allow), + dbesc($str_group_allow), + dbesc($str_contact_deny), + dbesc($str_group_deny), + intval($uid), + intval($menu_id), + intval($mitem_order) + ); + return $r; + +} + +function menu_edit_item($menu_id, $uid, $arr) { + + + $mitem_id = intval($arr['mitem_id']); + $mitem_link = escape_tags($arr['mitem_link']); + $mitem_desc = escape_tags($arr['mitem_desc']); + $mitem_order = intval($arr['mitem_order']); + $mitem_flags = intval($arr['mitem_flags']); + + + if(local_user() == $uid) { + $channel = get_app()->get_channel(); + } + + if ((! $arr['contact_allow']) + && (! $arr['group_allow']) + && (! $arr['contact_deny']) + && (! $arr['group_deny'])) { + $str_group_allow = $channel['channel_allow_gid']; + $str_contact_allow = $channel['channel_allow_cid']; + $str_group_deny = $channel['channel_deny_gid']; + $str_contact_deny = $channel['channel_deny_cid']; + } + else { + + // use the posted permissions + + $str_group_allow = perms2str($arr['group_allow']); + $str_contact_allow = perms2str($arr['contact_allow']); + $str_group_deny = perms2str($arr['group_deny']); + $str_contact_deny = perms2str($arr['contact_deny']); + } + + + $r = q("update menu_item set mitem_link = '%s', mitem_desc = '%s', mitem_flags = %d, allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s', mitem_order = %d where mitem_channel_id = %d and mitem_menu_id = %d and mitem_id = %d limit 1", + dbesc($mitem_link), + dbesc($mitem_desc), + intval($mitem_flags), + dbesc($str_contact_allow), + dbesc($str_group_allow), + dbesc($str_contact_deny), + dbesc($str_group_deny), + intval($mitem_order), + intval($uid), + intval($menu_id), + intval($mitem_id) + ); + return $r; +} + + + + +function menu_del_item($menu_id,$uid,$item_id) { + $r = q("delete from menu_item where mitem_menu_id = %d and mitem_channel_id = %d and mitem_id = %d limit 1", + intval($menu_id), + intval($uid), + intval($item_id) + ); + return $r; +} + diff --git a/include/message.php b/include/message.php index fd9698381..607166ec9 100644 --- a/include/message.php +++ b/include/message.php @@ -1,18 +1,31 @@ -<?php +<?php /** @file */ +/* Private Message backend API */ + +require_once('include/crypto.php'); +require_once('include/attach.php'); // send a private message -function send_message($uid = 0, $recipient='', $body='', $subject='', $replyto=''){ +function send_message($uid = 0, $recipient='', $body='', $subject='', $replyto='',$expires = ''){ + + $ret = array('success' => false); $a = get_app(); - if(! $recipient) return -1; + if(! $recipient) { + $ret['message'] = t('No recipient provided.'); + return $ret; + } if(! strlen($subject)) $subject = t('[no subject]'); +// if(! $expires) +// $expires = '0000-00-00 00:00:00'; +// else +// $expires = datetime_convert(date_default_timezone_get(),'UTC',$expires); if($uid) { $r = q("select * from channel where channel_id = %d limit 1", @@ -25,88 +38,342 @@ function send_message($uid = 0, $recipient='', $body='', $subject='', $replyto=' $channel = get_app()->get_channel(); } + if(! $channel) { + $ret['message'] = t('Unable to determine sender.'); + return $ret; + } + + // generate a unique message_id do { $dups = false; $hash = random_string(); - $uri = $hash . '@' . get_app()->get_hostname(); + $mid = $hash . '@' . get_app()->get_hostname(); - $r = q("SELECT `id` FROM mail WHERE `uri` = '%s' LIMIT 1", - dbesc($uri)); + $r = q("SELECT id FROM mail WHERE mid = '%s' LIMIT 1", + dbesc($mid)); if(count($r)) $dups = true; } while($dups == true); if(! strlen($replyto)) { - $replyto = $uri; + $replyto = $mid; } + /** + * + * When a photo was uploaded into the message using the (profile wall) ajax + * uploader, The permissions are initially set to disallow anybody but the + * owner from seeing it. This is because the permissions may not yet have been + * set for the post. If it's private, the photo permissions should be set + * appropriately. But we didn't know the final permissions on the post until + * now. So now we'll look for links of uploaded messages that are in the + * post and set them to the same permissions as the post itself. + * + */ + + $match = null; + $images = null; + if(preg_match_all("/\[zmg\](.*?)\[\/zmg\]/",((strpos($body,'[/crypt]')) ? $_POST['media_str'] : $body),$match)) + $images = $match[1]; + + $match = false; + + if(preg_match_all("/\[attachment\](.*?)\[\/attachment\]/",((strpos($body,'[/crypt]')) ? $_POST['media_str'] : $body),$match)) + $attaches = $match[1]; + + $attachments = ''; + + if(preg_match_all('/(\[attachment\](.*?)\[\/attachment\])/',$body,$match)) { + $attachments = array(); + foreach($match[2] as $mtch) { + $hash = substr($mtch,0,strpos($mtch,',')); + $rev = intval(substr($mtch,strpos($mtch,','))); + $r = attach_by_hash_nodata($hash,$rev); + if($r['success']) { + $attachments[] = array( + 'href' => $a->get_baseurl() . '/attach/' . $r['data']['hash'], + 'length' => $r['data']['filesize'], + 'type' => $r['data']['filetype'], + 'title' => urlencode($r['data']['filename']), + 'revision' => $r['data']['revision'] + ); + } + $body = str_replace($match[1],'',$body); + } + } + + $jattach = (($attachments) ? json_encode($attachments) : ''); + + $key = get_config('system','pubkey'); + if($subject) + $subject = json_encode(crypto_encapsulate($subject,$key)); + if($body) + $body = json_encode(crypto_encapsulate($body,$key)); - $r = q("INSERT INTO `mail` ( account_id, channel_id, from_xchan, to_xchan, title, body, uri, parent_uri, created ) - VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' )", + + $r = q("INSERT INTO mail ( account_id, mail_flags, channel_id, from_xchan, to_xchan, title, body, attach, mid, parent_mid, created, expires ) + VALUES ( %d, %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' )", intval($channel['channel_account_id']), - intval(local_user()), + intval(MAIL_OBSCURED), + intval($channel['channel_id']), dbesc($channel['channel_hash']), dbesc($recipient), dbesc($subject), dbesc($body), - dbesc($uri), + dbesc($jattach), + dbesc($mid), dbesc($replyto), - dbesc(datetime_convert()) + dbesc(datetime_convert()), + dbesc($expires) ); + // verify the save - $r = q("SELECT * FROM `mail` WHERE uri = '%s' and channel_id = %d LIMIT 1", - dbesc($uri), - intval(local_user()) + $r = q("SELECT * FROM mail WHERE mid = '%s' and channel_id = %d LIMIT 1", + dbesc($mid), + intval($channel['channel_id']) ); - if(count($r)) + if($r) $post_id = $r[0]['id']; + else { + $ret['message'] = t('Stored post could not be verified.'); + return $ret; + } + if(count($images)) { + foreach($images as $image) { + if(! stristr($image,$a->get_baseurl() . '/photo/')) + continue; + $image_uri = substr($image,strrpos($image,'/') + 1); + $image_uri = substr($image_uri,0, strpos($image_uri,'-')); + $r = q("UPDATE photo SET allow_cid = '%s' WHERE resource_id = '%s' AND uid = %d and allow_cid = '%s'", + dbesc('<' . $recipient . '>'), + dbesc($image_uri), + intval($channel['channel_id']), + dbesc('<' . $channel['channel_hash'] . '>') + ); + } + } + + if($attaches) { + foreach($attaches as $attach) { + $hash = substr($attach,0,strpos($attach,',')); + $rev = intval(substr($attach,strpos($attach,','))); + attach_store($channel,$observer_hash,$options = 'update', array( + 'hash' => $hash, + 'revision' => $rev, + 'allow_cid' => '<' . $recipient . '>', - /** - * - * When a photo was uploaded into the message using the (profile wall) ajax - * uploader, The permissions are initially set to disallow anybody but the - * owner from seeing it. This is because the permissions may not yet have been - * set for the post. If it's private, the photo permissions should be set - * appropriately. But we didn't know the final permissions on the post until - * now. So now we'll look for links of uploaded messages that are in the - * post and set them to the same permissions as the post itself. - * - */ + )); + } + } - $match = null; + proc_run('php','include/notifier.php','mail',$post_id); - if(preg_match_all("/\[img\](.*?)\[\/img\]/",$body,$match)) { - $images = $match[1]; - if(count($images)) { - foreach($images as $image) { - if(! stristr($image,$a->get_baseurl() . '/photo/')) - continue; - $image_uri = substr($image,strrpos($image,'/') + 1); - $image_uri = substr($image_uri,0, strpos($image_uri,'-')); - $r = q("UPDATE `photo` SET `allow_cid` = '%s' - WHERE `resource_id` = '%s' AND `album` = '%s' AND `uid` = %d ", - dbesc('<' . $recipient . '>'), - dbesc($image_uri), - dbesc( t('Wall Photos')), - intval(local_user()) - ); - } + $ret['success'] = true; + $ret['message_item'] = intval($post_id); + return $ret; + +} + +function private_messages_list($uid, $mailbox = '', $start = 0, $numitems = 0) { + + $where = ''; + $limit = ''; + + if($numitems) + $limit = " LIMIT " . intval($start) . ", " . intval($numitems); + + if($mailbox !== '') { + $x = q("select channel_hash from channel where channel_id = %d limit 1", + intval($uid) + ); + if(! $x) + return array(); + if($mailbox === 'inbox') + $where = " and sender_xchan != '" . dbesc($x[0]['channel_hash']) . "' "; + elseif($mailbox === 'outbox') + $where = " and sender_xchan = '" . dbesc($x[0]['channel_hash']) . "' "; + } + + // For different orderings, consider applying usort on the results. We thought of doing that + // inside this function or having some preset sorts, but don't wish to limit app developers. + + $r = q("SELECT * from mail WHERE channel_id = %d $where order by created desc $limit", + intval(local_user()) + ); + if(! $r) { + return array(); + } + + $chans = array(); + foreach($r as $rr) { + $s = "'" . dbesc(trim($rr['from_xchan'])) . "'"; + if(! in_array($s,$chans)) + $chans[] = $s; + $s = "'" . dbesc(trim($rr['to_xchan'])) . "'"; + if(! in_array($s,$chans)) + $chans[] = $s; + } + + $c = q("select * from xchan where xchan_hash in (" . implode(',',$chans) . ")"); + + foreach($r as $k => $rr) { + $r[$k]['from'] = find_xchan_in_array($rr['from_xchan'],$c); + $r[$k]['to'] = find_xchan_in_array($rr['to_xchan'],$c); + $r[$k]['seen'] = (($rr['mail_flags'] & MAIL_SEEN) ? 1 : 0); + if($r[$k]['mail_flags'] & MAIL_OBSCURED) { + logger('unencrypting'); + $key = get_config('system','prvkey'); + + if($r[$k]['title']) + $r[$k]['title'] = crypto_unencapsulate(json_decode_plus($r[$k]['title']),$key); + if($r[$k]['body']) + $r[$k]['body'] = crypto_unencapsulate(json_decode_plus($r[$k]['body']),$key); } } - - if($post_id) { - proc_run('php',"include/notifier.php","mail","$post_id"); - return intval($post_id); - } else { - return -3; + + return $r; +} + + + +function private_messages_fetch_message($channel_id, $messageitem_id, $updateseen = false) { + + $messages = q("select * from mail where id = %d and channel_id = %d order by created asc", + dbesc($messageitem_id), + intval($channel_id) + ); + + if(! $messages) + return array(); + + $chans = array(); + foreach($messages as $rr) { + $s = "'" . dbesc(trim($rr['from_xchan'])) . "'"; + if(! in_array($s,$chans)) + $chans[] = $s; + $s = "'" . dbesc(trim($rr['to_xchan'])) . "'"; + if(! in_array($s,$chans)) + $chans[] = $s; + } + + $c = q("select * from xchan where xchan_hash in (" . implode(',',$chans) . ")"); + + foreach($messages as $k => $message) { + $messages[$k]['from'] = find_xchan_in_array($message['from_xchan'],$c); + $messages[$k]['to'] = find_xchan_in_array($message['to_xchan'],$c); + if($messages[$k]['mail_flags'] & MAIL_OBSCURED) { + $key = get_config('system','prvkey'); + if($messages[$k]['title']) + $messages[$k]['title'] = crypto_unencapsulate(json_decode_plus($messages[$k]['title']),$key); + if($messages[$k]['body']) + $messages[$k]['body'] = crypto_unencapsulate(json_decode_plus($messages[$k]['body']),$key); + } + } + + if($updateseen) { + $r = q("UPDATE `mail` SET mail_flags = (mail_flags ^ %d) where not (mail_flags & %d) and id = %d AND channel_id = %d", + intval(MAIL_SEEN), + intval(MAIL_SEEN), + dbesc($messageitem_id), + intval($channel_id) + ); } + return $messages; + +} + + +function private_messages_drop($channel_id, $messageitem_id, $drop_conversation = false) { + + if($drop_conversation) { + // find the parent_id + $p = q("SELECT parent_mid FROM mail WHERE id = %d AND channel_id = %d LIMIT 1", + intval($messageitem_id), + intval($channel_id) + ); + if($p) { + $r = q("DELETE FROM mail WHERE parent_mid = '%s' AND channel_id = %d ", + dbesc($p[0]['parent_mid']), + intval($channel_id) + ); + if($r) + return true; + } + } + else { + $r = q("DELETE FROM mail WHERE id = %d AND channel_id = %d LIMIT 1", + intval($messageitem_id), + intval($channel_id) + ); + if($r) + return true; + } + return false; } +function private_messages_fetch_conversation($channel_id, $messageitem_id, $updateseen = false) { + + // find the parent_mid of the message being requested + + $r = q("SELECT parent_mid from mail WHERE channel_id = %d and id = %d limit 1", + intval($channel_id), + intval($messageitem_id) + ); + + if(! $r) + return array(); + + $messages = q("select * from mail where parent_mid = '%s' and channel_id = %d order by created asc", + dbesc($r[0]['parent_mid']), + intval($channel_id) + ); + + if(! $messages) + return array(); + + $chans = array(); + foreach($messages as $rr) { + $s = "'" . dbesc(trim($rr['from_xchan'])) . "'"; + if(! in_array($s,$chans)) + $chans[] = $s; + $s = "'" . dbesc(trim($rr['to_xchan'])) . "'"; + if(! in_array($s,$chans)) + $chans[] = $s; + } + + + $c = q("select * from xchan where xchan_hash in (" . implode(',',$chans) . ")"); + + foreach($messages as $k => $message) { + $messages[$k]['from'] = find_xchan_in_array($message['from_xchan'],$c); + $messages[$k]['to'] = find_xchan_in_array($message['to_xchan'],$c); + if($messages[$k]['mail_flags'] & MAIL_OBSCURED) { + $key = get_config('system','prvkey'); + if($messages[$k]['title']) + $messages[$k]['title'] = crypto_unencapsulate(json_decode_plus($messages[$k]['title']),$key); + if($messages[$k]['body']) + $messages[$k]['body'] = crypto_unencapsulate(json_decode_plus($messages[$k]['body']),$key); + } + } + + + if($updateseen) { + $r = q("UPDATE `mail` SET mail_flags = (mail_flags ^ %d) where not (mail_flags & %d) and parent_mid = '%s' AND channel_id = %d", + intval(MAIL_SEEN), + intval(MAIL_SEEN), + dbesc($r[0]['parent_mid']), + intval($channel_id) + ); + } + + return $messages; + +}
\ No newline at end of file diff --git a/include/nav.php b/include/nav.php index 5b5d80b17..80e4955e5 100644 --- a/include/nav.php +++ b/include/nav.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ function nav(&$a) { @@ -8,12 +8,10 @@ function nav(&$a) { * */ - $ssl_state = ((local_user()) ? true : false); - if(!(x($a->page,'nav'))) $a->page['nav'] = ''; - $base = $a->get_baseurl($ssl_state); + $base = z_root(); $a->page['htmlhead'] .= <<< EOT <script>$(document).ready(function() { @@ -22,6 +20,7 @@ function nav(&$a) { serviceUrl: '$base/acl', minChars: 2, width: 250, + id: 'nav-search-text-ac', }); a.setOptions({ autoSubmit: true, params: { type: 'x' }}); @@ -32,18 +31,6 @@ EOT; - /** - * Placeholder div for popup panel - */ - - /** - * - * Our network is distributed, and as you visit friends some of the - * sites look exactly the same - it isn't always easy to know where you are. - * Display the current site location as a navigation aid. - * - */ - if(local_user()) { $channel = $a->get_channel(); $observer = $a->get_observer(); @@ -57,6 +44,25 @@ EOT; $sitelocation = (($myident) ? $myident : $a->get_hostname()); + + /** + * + * Provide a banner/logo/whatever + * + */ + + $banner = get_config('system','banner'); + + if($banner === false) + $banner = get_config('system','sitename'); + + $a->page['header'] .= replace_macros(get_markup_template('hdr.tpl'), array( + '$baseurl' => $a->get_baseurl(), + '$sitelocation' => $sitelocation, + '$banner' => $banner + )); + + // nav links: array of array('href', 'text', 'extra css classes', 'title') $nav = Array(); @@ -71,35 +77,59 @@ EOT; $nav['logout'] = Array('logout',t('Logout'), "", t('End this session')); // user menu - $nav['usermenu'][] = Array('channel/' . $channel['channel_address'], t('Status'), "", t('Your posts and conversations')); + $nav['usermenu'][] = Array('channel/' . $channel['channel_address'], t('Home'), "", t('Your posts and conversations')); $nav['usermenu'][] = Array('profile/' . $channel['channel_address'], t('View Profile'), "", t('Your profile page')); if(feature_enabled(local_user(),'multi_profiles')) - $nav['usermenu'] = Array('profiles', t('Edit Profiles'),"", t('Manage/Edit Profiles')); + $nav['usermenu'][] = Array('profiles', t('Edit Profiles'),"", t('Manage/Edit profiles')); $nav['usermenu'][] = Array('photos/' . $channel['channel_address'], t('Photos'), "", t('Your photos')); -// $nav['usermenu'][] = Array('events/', t('Events'), "", t('Your events')); - + $nav['usermenu'][] = Array('cloud/' . $channel['channel_address'],t('Files'),"",t('Your files')); + + require_once('include/chat.php'); + $chats = chatroom_list(local_user()); + if (count($chats)) { + $nav['usermenu'][] = Array('chat/' . $channel['channel_address'],t('Chat'),"",t('Your chatrooms')); + } + + $nav['usermenu'][] = Array('bookmarks', t('Bookmarks'), "", t('Your bookmarks')); + if(feature_enabled($channel['channel_id'],'webpages')) + $nav['usermenu'][] = Array('webpages/' . $channel['channel_address'],t('Webpages'),"",t('Your webpages')); } else { - $nav['login'] = Array('login',t('Login'), ($a->module == 'login'?'selected':''), t('Sign in')); + if(! get_account_id()) + $nav['login'] = Array('login',t('Login'), ($a->module == 'login'?'selected':''), t('Sign in')); + else + $nav['alogout'] = Array('logout',t('Logout'), "", t('End this session')); + + } if($observer) { $userinfo = array( - 'icon' => $observer['xchan_photo_s'], + 'icon' => $observer['xchan_photo_m'], 'name' => $observer['xchan_addr'], ); } - - $nav['lock'] = array('rmagic','',(($observer) ? 'lock' : 'unlock'), (($observer) ? $observer['xchan_addr'] : t('Click to authenticate to your home hub'))); + if($observer) { + $nav['locked'] = true; + $nav['lock'] = array('logout','','lock', + sprintf( t('%s - click to logout'), $observer['xchan_addr'])); + } + else { + $nav['locked'] = false; + $nav['lock'] = array('rmagic','','unlock', + t('Click to authenticate to your home hub')); + } /** * "Home" should also take you home from an authenticated remote profile connection */ $homelink = get_my_url(); - if(! $homelink) - $homelink = ((x($_SESSION,'visitor_home')) ? $_SESSION['visitor_home'] : ''); + if(! $homelink) { + $observer = $a->get_observer(); + $homelink = (($observer) ? $observer['xchan_url'] : ''); + } if(($a->module != 'home') && (! (local_user()))) $nav['home'] = array($homelink, t('Home'), "", t('Home Page')); @@ -108,19 +138,19 @@ EOT; if(($a->config['system']['register_policy'] == REGISTER_OPEN) && (! local_user()) && (! remote_user())) $nav['register'] = array('register',t('Register'), "", t('Create an account')); - $help_url = $a->get_baseurl($ssl_state) . '/help'; + $help_url = z_root() . '/help?f=&cmd=' . $a->cmd; if(! get_config('system','hide_help')) $nav['help'] = array($help_url, t('Help'), "", t('Help and documentation')); - if(count($a->get_apps()) > 0) - $nav['apps'] = array('apps', t('Apps'), "", t('Addon applications, utilities, games')); + + $nav['apps'] = array('apps', t('Apps'), "", t('Applications, utilities, links, games')); $nav['search'] = array('search', t('Search'), "", t('Search site content')); - $gdirpath = 'directory'; - $nav['directory'] = array($gdirpath, t('Directory'), "", t('People directory')); + $nav['directory'] = array('directory', t('Directory'), "", t('Channel Locator')); + /** * @@ -130,30 +160,36 @@ EOT; if(local_user()) { - $nav['network'] = array('network', t('Network'), "", t('Conversations from your friends')); + $nav['network'] = array('network', t('Matrix'), "", t('Your matrix')); + $nav['network']['mark'] = array('', t('Mark all matrix notifications seen'), '',''); - $nav['home'] = array('channel/' . $channel['channel_address'], t('Home'), "", t('Your posts and conversations')); + $nav['home'] = array('channel/' . $channel['channel_address'], t('Channel Home'), "", t('Channel home')); + $nav['home']['mark'] = array('', t('Mark all channel notifications seen'), '',''); - $nav['intros'] = array('intro', t('Introductions'), "", t('New Connections')); + $nav['intros'] = array('connections/ifpending', t('Connections'), "", t('Connections')); - $nav['notifications'] = array('notifications', t('Notifications'), "", t('Notifications')); + + $nav['notifications'] = array('notifications/system', t('Notices'), "", t('Notifications')); $nav['notifications']['all']=array('notifications/system', t('See all notifications'), "", ""); $nav['notifications']['mark'] = array('', t('Mark all system notifications seen'), '',''); - $nav['messages'] = array('message', t('Messages'), "", t('Private mail')); + $nav['messages'] = array('message', t('Mail'), "", t('Private mail')); + $nav['messages']['all']=array('message', t('See all private messages'), "", ""); + $nav['messages']['mark'] = array('', t('Mark all private messages seen'), '',''); $nav['messages']['inbox'] = array('message', t('Inbox'), "", t('Inbox')); $nav['messages']['outbox']= array('message/sent', t('Outbox'), "", t('Outbox')); - $nav['messages']['new'] = array('message/new', t('New Message'), "", t('New Message')); + $nav['messages']['new'] = array('mail/new', t('New Message'), "", t('New Message')); $nav['all_events'] = array('events', t('Events'), "", t('Event Calendar')); + $nav['all_events']['all']=array('events', t('See all events'), "", ""); + $nav['all_events']['mark'] = array('', t('Mark all events seen'), '',''); $nav['manage'] = array('manage', t('Channel Select'), "", t('Manage Your Channels')); $nav['settings'] = array('settings', t('Settings'),"", t('Account/Channel Settings')); - $nav['contacts'] = array('connections', t('Connections'),"", t('Manage/Edit Friends and Connections')); } /** @@ -173,22 +209,23 @@ EOT; $banner = get_config('system','banner'); if($banner === false) -// $banner .= '<a href="http://friendica.com"><img id="logo-img" src="images/fred-32.png" alt="logo" /></a>'; - $banner = 'red'; + $banner = get_config('system','sitename'); + + $x = array('nav' => $nav, 'usermenu' => $userinfo ); + call_hooks('nav', $x); $tpl = get_markup_template('nav.tpl'); $a->page['nav'] .= replace_macros($tpl, array( '$baseurl' => $a->get_baseurl(), - '$langselector' => lang_selector(), '$sitelocation' => $sitelocation, - '$nav' => $nav, + '$nav' => $x['nav'], '$banner' => $banner, '$emptynotifications' => t('Nothing new here'), - '$userinfo' => $userinfo, + '$userinfo' => $x['usermenu'], '$localuser' => local_user(), '$sel' => $a->nav_sel, - '$apps' => $a->get_apps(), + '$pleasewait' => t('Please wait...') )); call_hooks('page_header', $a->page['nav']); @@ -214,5 +251,5 @@ function nav_set_selected($item){ 'manage' => null, 'register' => null, ); - $a->nav_sel[$item] = 'selected'; + $a->nav_sel[$item] = 'active'; } diff --git a/include/network.php b/include/network.php index 321eb375f..03faf9957 100644 --- a/include/network.php +++ b/include/network.php @@ -1,195 +1,33 @@ -<?php +<?php /** @file */ -// curl wrapper. If binary flag is true, return binary -// results. -if(! function_exists('fetch_url')) { -function fetch_url($url,$binary = false, &$redirects = 0, $timeout = 0, $accept_content=Null) { - - $a = get_app(); - - $ch = @curl_init($url); - if(($redirects > 8) || (! $ch)) - return false; - - @curl_setopt($ch, CURLOPT_HEADER, true); - - if (!is_null($accept_content)){ - curl_setopt($ch,CURLOPT_HTTPHEADER, array ( - "Accept: " . $accept_content - )); - } - - @curl_setopt($ch, CURLOPT_RETURNTRANSFER,true); - //@curl_setopt($ch, CURLOPT_USERAGENT, "Friendica"); - @curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; Friendica)"); - - - if(intval($timeout)) { - @curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); - } - else { - $curl_time = intval(get_config('system','curl_timeout')); - @curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60)); - } - // by default we will allow self-signed certs - // but you can override this - - $check_cert = get_config('system','verifyssl'); - @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false)); - - $prx = get_config('system','proxy'); - if(strlen($prx)) { - @curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1); - @curl_setopt($ch, CURLOPT_PROXY, $prx); - $prxusr = @get_config('system','proxyuser'); - if(strlen($prxusr)) - @curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr); - } - if($binary) - @curl_setopt($ch, CURLOPT_BINARYTRANSFER,1); - - $a->set_curl_code(0); - - // don't let curl abort the entire application - // if it throws any errors. - - $s = @curl_exec($ch); - - $base = $s; - $curl_info = @curl_getinfo($ch); - $http_code = $curl_info['http_code']; -// logger('fetch_url:' . $http_code . ' data: ' . $s); - $header = ''; - - // Pull out multiple headers, e.g. proxy and continuation headers - // allow for HTTP/2.x without fixing code - - while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) { - $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4); - $header .= $chunk; - $base = substr($base,strlen($chunk)); - } - - if($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307 || $http_code == 308) { - $matches = array(); - preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches); - $newurl = trim(array_pop($matches)); - if(strpos($newurl,'/') === 0) - $newurl = $url . $newurl; - $url_parsed = @parse_url($newurl); - if (isset($url_parsed)) { - $redirects++; - @curl_close($ch); - return fetch_url($newurl,$binary,$redirects,$timeout); - } - } - - $a->set_curl_code($http_code); - - $body = substr($s,strlen($header)); - $a->set_curl_headers($header); - @curl_close($ch); - return($body); -}} - -// post request to $url. $params is an array of post variables. - -if(! function_exists('post_url')) { -function post_url($url,$params, $headers = null, &$redirects = 0, $timeout = 0) { - $a = get_app(); - $ch = curl_init($url); - if(($redirects > 8) || (! $ch)) - return false; - - curl_setopt($ch, CURLOPT_HEADER, true); - curl_setopt($ch, CURLOPT_RETURNTRANSFER,true); - curl_setopt($ch, CURLOPT_POST,1); - curl_setopt($ch, CURLOPT_POSTFIELDS,$params); - curl_setopt($ch, CURLOPT_USERAGENT, "Friendica"); - - if(intval($timeout)) { - curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); - } - else { - $curl_time = intval(get_config('system','curl_timeout')); - curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60)); - } - - if(defined('LIGHTTPD')) { - if(!is_array($headers)) { - $headers = array('Expect:'); - } else { - if(!in_array('Expect:', $headers)) { - array_push($headers, 'Expect:'); - } - } - } - if($headers) - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - - $check_cert = get_config('system','verifyssl'); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false)); - $prx = get_config('system','proxy'); - if(strlen($prx)) { - curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1); - curl_setopt($ch, CURLOPT_PROXY, $prx); - $prxusr = get_config('system','proxyuser'); - if(strlen($prxusr)) - curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr); - } - - $a->set_curl_code(0); - - // don't let curl abort the entire application - // if it throws any errors. - - $s = @curl_exec($ch); - - $base = $s; - $curl_info = curl_getinfo($ch); - $http_code = $curl_info['http_code']; - - $header = ''; - - // Pull out multiple headers, e.g. proxy and continuation headers - // allow for HTTP/2.x without fixing code - - while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) { - $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4); - $header .= $chunk; - $base = substr($base,strlen($chunk)); - } - - if($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307 || $http_code == 308) { - $matches = array(); - preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches); - $newurl = trim(array_pop($matches)); - if(strpos($newurl,'/') === 0) - $newurl = $url . $newurl; - $url_parsed = @parse_url($newurl); - if (isset($url_parsed)) { - $redirects++; - @curl_close($ch); - if($http_code == 303) { - return fetch_url($newurl,false,$redirects,$timeout); - } else { - return post_url($newurl,$params,$redirects,$timeout); - } - } - } - $a->set_curl_code($http_code); - $body = substr($s,strlen($header)); - - $a->set_curl_headers($header); - - curl_close($ch); - return($body); -}} +function get_capath() { + return appdirpath() . '/library/cacert.pem'; +} -if(! function_exists('z_fetch_url')) { -function z_fetch_url($url,$binary = false, &$redirects = 0, $timeout = 0, $accept_content=Null) { +/** + * @function z_fetch_url + * @param string $url + * URL to fetch + * @param boolean $binary = false + * TRUE if asked to return binary results (file download) + * @param int $redirects = 0 + * internal use, recursion counter + * @param array $opts (optional parameters) + * 'accept_content' => supply Accept: header with 'accept_content' as the value + * 'timeout' => int seconds, default system config value or 60 seconds + * 'http_auth' => username:password + * 'novalidate' => do not validate SSL certs, default is to validate using our CA list + * + * @returns array + * 'return_code' => HTTP return code or 0 if timeout or failure + * 'success' => boolean true (if HTTP 2xx result) or false + * 'header' => HTTP headers + * 'body' => fetched content + */ + +function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) { $ret = array('return_code' => 0, 'success' => false, 'header' => "", 'body' => ""); @@ -200,26 +38,37 @@ function z_fetch_url($url,$binary = false, &$redirects = 0, $timeout = 0, $accep return false; @curl_setopt($ch, CURLOPT_HEADER, true); + @curl_setopt($ch, CURLOPT_CAINFO, get_capath()); + @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + @curl_setopt($ch, CURLOPT_RETURNTRANSFER,true); + @curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; Red)"); - if (!is_null($accept_content)){ - curl_setopt($ch,CURLOPT_HTTPHEADER, array ( - "Accept: " . $accept_content + $ciphers = @get_config('system','curl_ssl_ciphers'); + if($ciphers) + @curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, $ciphers); + + if (x($opts,'accept_content')){ + @curl_setopt($ch,CURLOPT_HTTPHEADER, array ( + "Accept: " . $opts['accept_content'] )); } - @curl_setopt($ch, CURLOPT_RETURNTRANSFER,true); - @curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; Friendica Red)"); - - - if(intval($timeout)) { - @curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); + if(x($opts,'timeout') && intval($opts['timeout'])) { + @curl_setopt($ch, CURLOPT_TIMEOUT, $opts['timeout']); } else { $curl_time = intval(get_config('system','curl_timeout')); @curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60)); } - @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + if(x($opts,'http_auth')) { + // "username" . ':' . "password" + @curl_setopt($ch, CURLOPT_USERPWD, $opts['http_auth']); + } + + @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, + ((x($opts,'novalidate') && intval($opts['novalidate'])) ? false : true)); + $prx = get_config('system','proxy'); if(strlen($prx)) { @@ -233,7 +82,7 @@ function z_fetch_url($url,$binary = false, &$redirects = 0, $timeout = 0, $accep @curl_setopt($ch, CURLOPT_BINARYTRANSFER,1); - // don't let curl abort the entire application + // don't let curl abort the entire application' // if it throws any errors. $s = @curl_exec($ch); @@ -241,7 +90,7 @@ function z_fetch_url($url,$binary = false, &$redirects = 0, $timeout = 0, $accep $base = $s; $curl_info = @curl_getinfo($ch); $http_code = $curl_info['http_code']; -// logger('fetch_url:' . $http_code . ' data: ' . $s); + //logger('fetch_url:' . $http_code . ' data: ' . $s); $header = ''; // Pull out multiple headers, e.g. proxy and continuation headers @@ -261,68 +110,81 @@ function z_fetch_url($url,$binary = false, &$redirects = 0, $timeout = 0, $accep $newurl = $url . $newurl; $url_parsed = @parse_url($newurl); if (isset($url_parsed)) { - $redirects++; @curl_close($ch); - return z_fetch_url($newurl,$binary,$redirects,$timeout,$accpt_content); + return z_fetch_url($newurl,$binary,$redirects++,$opts); } } $rc = intval($http_code); $ret['return_code'] = $rc; $ret['success'] = (($rc >= 200 && $rc <= 299) ? true : false); + if(! $ret['success']) { + $ret['error'] = curl_error($ch); + $ret['debug'] = $curl_info; + logger('z_fetch_url: error: ' . $url . ': ' . $ret['error'], LOGGER_DEBUG); + logger('z_fetch_url: debug: ' . print_r($curl_info,true), LOGGER_DATA); + } $ret['body'] = substr($s,strlen($header)); $ret['header'] = $header; + @curl_close($ch); return($ret); -}} +} -if(! function_exists('z_post_url')) { -function z_post_url($url,$params, $headers = null, &$redirects = 0, $timeout = 0) { +function z_post_url($url,$params, $redirects = 0, $opts = array()) { + $ret = array('return_code' => 0, 'success' => false, 'header' => "", 'body' => ""); $ch = curl_init($url); if(($redirects > 8) || (! $ch)) return ret; - curl_setopt($ch, CURLOPT_HEADER, true); - curl_setopt($ch, CURLOPT_RETURNTRANSFER,true); - curl_setopt($ch, CURLOPT_POST,1); - curl_setopt($ch, CURLOPT_POSTFIELDS,$params); - curl_setopt($ch, CURLOPT_USERAGENT, "Friendica"); + @curl_setopt($ch, CURLOPT_HEADER, true); + @curl_setopt($ch, CURLOPT_CAINFO, get_capath()); + @curl_setopt($ch, CURLOPT_RETURNTRANSFER,true); + @curl_setopt($ch, CURLOPT_POST,1); + @curl_setopt($ch, CURLOPT_POSTFIELDS,$params); + @curl_setopt($ch, CURLOPT_USERAGENT, "Red"); - if(intval($timeout)) { - curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); + $ciphers = @get_config('system','curl_ssl_ciphers'); + if($ciphers) + @curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, $ciphers); + + + if (x($opts,'accept_content')){ + @curl_setopt($ch,CURLOPT_HTTPHEADER, array ( + "Accept: " . $opts['accept_content'] + )); + } + if(x($opts,'headers')) + @curl_setopt($ch, CURLOPT_HTTPHEADER, $opts['headers']); + + if(x($opts,'timeout') && intval($opts['timeout'])) { + @curl_setopt($ch, CURLOPT_TIMEOUT, $opts['timeout']); } else { $curl_time = intval(get_config('system','curl_timeout')); - curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60)); + @curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60)); } - if(defined('LIGHTTPD')) { - if(!is_array($headers)) { - $headers = array('Expect:'); - } else { - if(!in_array('Expect:', $headers)) { - array_push($headers, 'Expect:'); - } - } + if(x($opts,'http_auth')) { + // "username" . ':' . "password" + @curl_setopt($ch, CURLOPT_USERPWD, $opts['http_auth']); } - if($headers) - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, + ((x($opts,'novalidate') && intval($opts['novalidate'])) ? false : true)); $prx = get_config('system','proxy'); if(strlen($prx)) { - curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1); - curl_setopt($ch, CURLOPT_PROXY, $prx); + @curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1); + @curl_setopt($ch, CURLOPT_PROXY, $prx); $prxusr = get_config('system','proxyuser'); if(strlen($prxusr)) - curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr); + @curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr); } // don't let curl abort the entire application @@ -331,7 +193,7 @@ function z_post_url($url,$params, $headers = null, &$redirects = 0, $timeout = 0 $s = @curl_exec($ch); $base = $s; - $curl_info = curl_getinfo($ch); + $curl_info = @curl_getinfo($ch); $http_code = $curl_info['http_code']; $header = ''; @@ -353,23 +215,29 @@ function z_post_url($url,$params, $headers = null, &$redirects = 0, $timeout = 0 $newurl = $url . $newurl; $url_parsed = @parse_url($newurl); if (isset($url_parsed)) { - $redirects++; curl_close($ch); if($http_code == 303) { - return z_fetch_url($newurl,false,$headers,$redirects,$timeout); + return z_fetch_url($newurl,false,$redirects++,$opts); } else { - return z_post_url($newurl,$params,$headers,$redirects,$timeout); + return z_post_url($newurl,$params,$redirects++,$opts); } } } $rc = intval($http_code); $ret['return_code'] = $rc; $ret['success'] = (($rc >= 200 && $rc <= 299) ? true : false); + if(! $ret['success']) { + $ret['error'] = curl_error($ch); + $ret['debug'] = $curl_info; + logger('z_post_url: error: ' . $url . ': ' . $ret['error'], LOGGER_DEBUG); + logger('z_post_url: debug: ' . print_r($curl_info,true), LOGGER_DATA); + } + $ret['body'] = substr($s,strlen($header)); $ret['header'] = $header; curl_close($ch); return($ret); -}} +} @@ -385,7 +253,7 @@ function json_return_and_die($x) { // Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable // of $st and an optional text <message> of $message and terminates the current process. -if(! function_exists('xml_status')) { + function xml_status($st, $message = '') { $xml_message = ((strlen($message)) ? "\t<message>" . xmlify($message) . "</message>\r\n" : ''); @@ -397,29 +265,37 @@ function xml_status($st, $message = '') { echo '<?xml version="1.0" encoding="UTF-8"?>'."\r\n"; echo "<result>\r\n\t<status>$st</status>\r\n$xml_message</result>\r\n"; killme(); -}} - +} -if(! function_exists('http_status_exit')) { -function http_status_exit($val) { +/** + * @function http_status_exit + * + * Send HTTP status header and exit + * @param int $val + * integer HTTP status result value + * @param string $msg + * optional message + * @returns (does not return, process is terminated) + */ + +function http_status_exit($val,$msg = '') { $err = ''; if($val >= 400) - $err = 'Error'; + $msg = (($msg) ? $msg : 'Error'); if($val >= 200 && $val < 300) - $err = 'OK'; + $msg = (($msg) ? $msg : 'OK'); - logger('http_status_exit ' . $val); - header($_SERVER["SERVER_PROTOCOL"] . ' ' . $val . ' ' . $err); + logger('http_status_exit ' . $val . ' ' . $msg); + header($_SERVER['SERVER_PROTOCOL'] . ' ' . $val . ' ' . $msg); killme(); - -}} +} // convert an XML document to a normalised, case-corrected array // used by webfinger -if(! function_exists('convert_xml_element_to_array')) { + function convert_xml_element_to_array($xml_element, &$recursion_depth=0) { // If we're getting too deep, bail out @@ -459,7 +335,7 @@ function convert_xml_element_to_array($xml_element, &$recursion_depth=0) { } else { return (trim(strval($xml_element))); } -}} +} // Given an email style address, perform webfinger lookup and // return the resulting DFRN profile URL, or if no DFRN profile URL @@ -473,7 +349,7 @@ function convert_xml_element_to_array($xml_element, &$recursion_depth=0) { // amended 7/9/2011 to return an hcard which could save potentially loading // a lengthy content page to scrape dfrn attributes -if(! function_exists('webfinger_dfrn')) { + function webfinger_dfrn($s,&$hcard) { if(! strstr($s,'@')) { return $s; @@ -493,14 +369,14 @@ function webfinger_dfrn($s,&$hcard) { } } return $profile_link; -}} +} // Given an email style address, perform webfinger lookup and // return the array of link attributes from the personal XRD file. // On error/failure return an empty array. -if(! function_exists('webfinger')) { + function webfinger($s, $debug = false) { $host = ''; if(strstr($s,'@')) { @@ -523,177 +399,8 @@ function webfinger($s, $debug = false) { } } return array(); -}} - -if(! function_exists('lrdd')) { -function lrdd($uri, $debug = false) { - - $a = get_app(); - - // default priority is host priority, host-meta first - - $priority = 'host'; - - // All we have is an email address. Resource-priority is irrelevant - // because our URI isn't directly resolvable. - - if(strstr($uri,'@')) { - return(webfinger($uri)); - } - - // get the host meta file - - $host = @parse_url($uri); - - if($host) { - $url = ((x($host,'scheme')) ? $host['scheme'] : 'http') . '://'; - $url .= $host['host'] . '/.well-known/host-meta' ; - } - else - return array(); - - logger('lrdd: constructed url: ' . $url); - - $xml = fetch_url($url); - $headers = $a->get_curl_headers(); - - if (! $xml) - return array(); - - logger('lrdd: host_meta: ' . $xml, LOGGER_DATA); - - if(! stristr($xml,'<xrd')) - return array(); - - $h = parse_xml_string($xml); - if(! $h) - return array(); - - $arr = convert_xml_element_to_array($h); - - if(isset($arr['xrd']['property'])) { - $property = $arr['crd']['property']; - if(! isset($property[0])) - $properties = array($property); - else - $properties = $property; - foreach($properties as $prop) - if((string) $prop['@attributes'] === 'http://lrdd.net/priority/resource') - $priority = 'resource'; - } - - // save the links in case we need them - - $links = array(); - - if(isset($arr['xrd']['link'])) { - $link = $arr['xrd']['link']; - if(! isset($link[0])) - $links = array($link); - else - $links = $link; - } - - // do we have a template or href? - - if(count($links)) { - foreach($links as $link) { - if($link['@attributes']['rel'] && attribute_contains($link['@attributes']['rel'],'lrdd')) { - if(x($link['@attributes'],'template')) - $tpl = $link['@attributes']['template']; - elseif(x($link['@attributes'],'href')) - $href = $link['@attributes']['href']; - } - } - } - - if((! isset($tpl)) || (! strpos($tpl,'{uri}'))) - $tpl = ''; - - if($priority === 'host') { - if(strlen($tpl)) - $pxrd = str_replace('{uri}', urlencode($uri), $tpl); - elseif(isset($href)) - $pxrd = $href; - if(isset($pxrd)) { - logger('lrdd: (host priority) pxrd: ' . $pxrd); - $links = fetch_xrd_links($pxrd); - return $links; - } - - $lines = explode("\n",$headers); - if(count($lines)) { - foreach($lines as $line) { - if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) { - return(fetch_xrd_links($matches[1])); - break; - } - } - } - } - - - // priority 'resource' - - - $html = fetch_url($uri); - $headers = $a->get_curl_headers(); - logger('lrdd: headers=' . $headers, LOGGER_DEBUG); - - // don't try and parse raw xml as html - if(! strstr($html,'<?xml')) { - require_once('library/HTML5/Parser.php'); - - try { - $dom = HTML5_Parser::parse($html); - } catch (DOMException $e) { - logger('lrdd: parse error: ' . $e); - } - - if(isset($dom) && $dom) { - $items = $dom->getElementsByTagName('link'); - foreach($items as $item) { - $x = $item->getAttribute('rel'); - if($x == "lrdd") { - $pagelink = $item->getAttribute('href'); - break; - } - } - } - } - - if(isset($pagelink)) - return(fetch_xrd_links($pagelink)); - - // next look in HTTP headers - - $lines = explode("\n",$headers); - if(count($lines)) { - foreach($lines as $line) { - // TODO alter the following regex to support multiple relations (space separated) - if((stristr($line,'link:')) && preg_match('/<([^>].*)>.*rel\=[\'\"]lrdd[\'\"]/',$line,$matches)) { - $pagelink = $matches[1]; - break; - } - // don't try and run feeds through the html5 parser - if(stristr($line,'content-type:') && ((stristr($line,'application/atom+xml')) || (stristr($line,'application/rss+xml')))) - return array(); - if(stristr($html,'<rss') || stristr($html,'<feed')) - return array(); - } - } - - if(isset($pagelink)) - return(fetch_xrd_links($pagelink)); - - // If we haven't found any links, return the host xrd links (which we have already fetched) - - if(isset($links)) - return $links; - - return array(); +} -}} @@ -701,7 +408,7 @@ function lrdd($uri, $debug = false) { // host. Returns the LRDD template or an empty string on // error/failure. -if(! function_exists('fetch_lrdd_template')) { + function fetch_lrdd_template($host) { $tpl = ''; @@ -723,65 +430,13 @@ function fetch_lrdd_template($host) { if(! strpos($tpl,'{uri}')) $tpl = ''; return $tpl; -}} - -// Given a URL, retrieve the page as an XRD document. -// Return an array of links. -// on error/failure return empty array. - -if(! function_exists('fetch_xrd_links')) { -function fetch_xrd_links($url) { - - $xrd_timeout = intval(get_config('system','xrd_timeout')); - $redirects = 0; - $xml = fetch_url($url,false,$redirects,(($xrd_timeout) ? $xrd_timeout : 30)); - - logger('fetch_xrd_links: ' . $xml, LOGGER_DATA); - - if ((! $xml) || (! stristr($xml,'<xrd'))) - return array(); - - // fix diaspora's bad xml - $xml = str_replace(array('href="','"/>'),array('href="','"/>'),$xml); - - $arr = xml2array($xml); - - logger('fetch_xrd_links: ' . print_r($arr,true), LOGGER_DATA); - - $links = array(); - - if(isset($arr['xrd']['link'])) { - $link = $arr['xrd']['link']; - if(! isset($link[0])) - $links = array($link); - else - $links = $link; - } - if(isset($arr['xrd']['alias'])) { - $alias = $arr['xrd']['alias']; - if(! isset($alias[0])) - $aliases = array($alias); - else - $aliases = $alias; - if(is_array($aliases) && count($aliases)) { - foreach($aliases as $alias) { - $links[]['@attributes'] = array('rel' => 'alias' , 'href' => $alias); - } - } - } - - logger('fetch_xrd_links: ' . print_r($links,true), LOGGER_DATA); - - return $links; - -}} - +} // Take a URL from the wild, prepend http:// if necessary // and check DNS to see if it's real (or check if is a valid IP address) // return true if it's OK, false if something is wrong with it -if(! function_exists('validate_url')) { + function validate_url(&$url) { // no naked subdomains (allow localhost for tests) @@ -795,11 +450,11 @@ function validate_url(&$url) { return true; } return false; -}} +} // checks that email is an actual resolvable internet address -if(! function_exists('validate_email')) { + function validate_email($addr) { if(get_config('system','disable_email_validation')) @@ -813,14 +468,14 @@ function validate_email($addr) { return true; } return false; -}} +} // Check $url against our list of allowed sites, // wildcards allowed. If allowed_sites is unset return true; // If url is allowed, return true. // otherwise, return false -if(! function_exists('allowed_url')) { + function allowed_url($url) { $h = @parse_url($url); @@ -855,14 +510,14 @@ function allowed_url($url) { } } return $found; -}} +} // check if email address is allowed to register here. // Compare against our list (wildcards allowed). // Returns false if not allowed, true if allowed or if // allowed list is not configured. -if(! function_exists('allowed_email')) { + function allowed_email($email) { @@ -889,10 +544,10 @@ function allowed_email($email) { } } return $found; -}} +} + -if(! function_exists('avatar_img')) { function avatar_img($email) { $a = get_app(); @@ -905,14 +560,14 @@ function avatar_img($email) { call_hooks('avatar_lookup', $avatar); if(! $avatar['success']) - $avatar['url'] = $a->get_baseurl() . '/images/person-175.jpg'; + $avatar['url'] = $a->get_baseurl() . '/' . get_default_profile_photo(); logger('Avatar: ' . $avatar['email'] . ' ' . $avatar['url'], LOGGER_DEBUG); return $avatar['url']; -}} +} + -if(! function_exists('parse_xml_string')) { function parse_xml_string($s,$strict = true) { if($strict) { if(! strstr($s,'<?xml')) @@ -931,63 +586,6 @@ function parse_xml_string($s,$strict = true) { libxml_clear_errors(); } return $x; -}} - -function add_fcontact($arr,$update = false) { - - if($update) { - $r = q("UPDATE `fcontact` SET - `name` = '%s', - `photo` = '%s', - `request` = '%s', - `nick` = '%s', - `addr` = '%s', - `batch` = '%s', - `notify` = '%s', - `poll` = '%s', - `confirm` = '%s', - `alias` = '%s', - `pubkey` = '%s', - `updated` = '%s' - WHERE `url` = '%s' AND `network` = '%s' LIMIT 1", - dbesc($arr['name']), - dbesc($arr['photo']), - dbesc($arr['request']), - dbesc($arr['nick']), - dbesc($arr['addr']), - dbesc($arr['batch']), - dbesc($arr['notify']), - dbesc($arr['poll']), - dbesc($arr['confirm']), - dbesc($arr['alias']), - dbesc($arr['pubkey']), - dbesc(datetime_convert()), - dbesc($arr['url']), - dbesc($arr['network']) - ); - } - else { - $r = q("insert into fcontact ( `url`,`name`,`photo`,`request`,`nick`,`addr`, - `batch`, `notify`,`poll`,`confirm`,`network`,`alias`,`pubkey`,`updated` ) - values('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')", - dbesc($arr['url']), - dbesc($arr['name']), - dbesc($arr['photo']), - dbesc($arr['request']), - dbesc($arr['nick']), - dbesc($arr['addr']), - dbesc($arr['batch']), - dbesc($arr['notify']), - dbesc($arr['poll']), - dbesc($arr['confirm']), - dbesc($arr['network']), - dbesc($arr['alias']), - dbesc($arr['pubkey']), - dbesc(datetime_convert()) - ); - } - - return $r; } @@ -996,17 +594,24 @@ function scale_external_images($s, $include_link = true, $scale_replace = false) $a = get_app(); // Picture addresses can contain special characters - $s = htmlspecialchars_decode($s); + $s = htmlspecialchars_decode($s, ENT_COMPAT); $matches = null; - $c = preg_match_all('/\[img.*?\](.*?)\[\/img\]/ism',$s,$matches,PREG_SET_ORDER); + $c = preg_match_all('/\[img(.*?)\](.*?)\[\/img\]/ism',$s,$matches,PREG_SET_ORDER); if($c) { - require_once('include/Photo.php'); + require_once('include/photo/photo_driver.php'); + foreach($matches as $mtch) { - logger('scale_external_image: ' . $mtch[1]); + logger('scale_external_image: ' . $mtch[1] . ' ' . $mtch[2]); + + if(substr($mtch[1],0,1) == '=') { + $owidth = intval(substr($mtch[1],1)); + if(intval($owidth) > 0 && intval($owidth) < 640) + continue; + } $hostname = str_replace('www.','',substr($a->get_baseurl(),strpos($a->get_baseurl(),'://')+3)); - if(stristr($mtch[1],$hostname)) + if(stristr($mtch[2],$hostname)) continue; // $scale_replace, if passed, is an array of two elements. The @@ -1015,22 +620,23 @@ function scale_external_images($s, $include_link = true, $scale_replace = false) // This allows Friendica to display the smaller remote image if // one exists, while still linking to the full-size image if($scale_replace) - $scaled = str_replace($scale_replace[0], $scale_replace[1], $mtch[1]); + $scaled = str_replace($scale_replace[0], $scale_replace[1], $mtch[2]); else - $scaled = $mtch[1]; - $i = fetch_url($scaled); + $scaled = $mtch[2]; + $i = z_fetch_url($scaled); + $cache = get_config('system','itemcache'); if (($cache != '') and is_dir($cache)) { $cachefile = $cache."/".hash("md5", $scaled); - file_put_contents($cachefile, $i); + file_put_contents($cachefile, $i['body']); } // guess mimetype from headers or filename - $type = guess_image_type($mtch[1],true); + $type = guess_image_type($mtch[2],$i['header']); - if($i) { - $ph = new Photo($i, $type); + if($i['success']) { + $ph = photo_factory($i['body'], $type); if($ph->is_valid()) { $orig_width = $ph->getWidth(); $orig_height = $ph->getHeight(); @@ -1043,7 +649,7 @@ function scale_external_images($s, $include_link = true, $scale_replace = false) logger('scale_external_images: ' . $orig_width . '->' . $new_width . 'w ' . $orig_height . '->' . $new_height . 'h' . ' match: ' . $mtch[0], LOGGER_DEBUG); $s = str_replace($mtch[0],'[img=' . $new_width . 'x' . $new_height. ']' . $scaled . '[/img]' . "\n" . (($include_link) - ? '[url=' . $mtch[1] . ']' . t('view full size') . '[/url]' . "\n" + ? '[zrl=' . $mtch[2] . ']' . t('view full size') . '[/zrl]' . "\n" : ''),$s); logger('scale_external_images: new string: ' . $s, LOGGER_DEBUG); } @@ -1059,52 +665,6 @@ function scale_external_images($s, $include_link = true, $scale_replace = false) return $s; } - -function fix_contact_ssl_policy(&$contact,$new_policy) { - - $ssl_changed = false; - if((intval($new_policy) == SSL_POLICY_SELFSIGN || $new_policy === 'self') && strstr($contact['url'],'https:')) { - $ssl_changed = true; - $contact['url'] = str_replace('https:','http:',$contact['url']); - $contact['request'] = str_replace('https:','http:',$contact['request']); - $contact['notify'] = str_replace('https:','http:',$contact['notify']); - $contact['poll'] = str_replace('https:','http:',$contact['poll']); - $contact['confirm'] = str_replace('https:','http:',$contact['confirm']); - $contact['poco'] = str_replace('https:','http:',$contact['poco']); - } - - if((intval($new_policy) == SSL_POLICY_FULL || $new_policy === 'full') && strstr($contact['url'],'http:')) { - $ssl_changed = true; - $contact['url'] = str_replace('http:','https:',$contact['url']); - $contact['request'] = str_replace('http:','https:',$contact['request']); - $contact['notify'] = str_replace('http:','https:',$contact['notify']); - $contact['poll'] = str_replace('http:','https:',$contact['poll']); - $contact['confirm'] = str_replace('http:','https:',$contact['confirm']); - $contact['poco'] = str_replace('http:','https:',$contact['poco']); - } - - if($ssl_changed) { - q("update contact set - url = '%s', - request = '%s', - notify = '%s', - poll = '%s', - confirm = '%s', - poco = '%s' - where id = %d limit 1", - dbesc($contact['url']), - dbesc($contact['request']), - dbesc($contact['notify']), - dbesc($contact['poll']), - dbesc($contact['confirm']), - dbesc($contact['poco']), - intval($contact['id']) - ); - } -} - - - /** * xml2array() will convert the given XML text to an array in the XML structure. * Link: http://www.bin-co.com/php/scripts/xml2array/ @@ -1269,7 +829,7 @@ function xml2array($contents, $namespaces = true, $get_attributes=1, $priority = } -function email_header_encode($in_str, $charset) { +function email_header_encode($in_str, $charset = 'UTF-8') { $out_str = $in_str; $need_to_convert = false; diff --git a/include/notifier.php b/include/notifier.php index feb1c693c..59f472539 100644 --- a/include/notifier.php +++ b/include/notifier.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ require_once("boot.php"); require_once('include/queue_fn.php'); @@ -53,6 +53,9 @@ require_once('include/html2plain.php'); * * ZOT * permission_update abook_id + * refresh_all channel_id + * purge_all channel_id + * expire channel_id * relay item_id (item was relayed to owner, we will deliver it as owner) * */ @@ -87,6 +90,9 @@ function notifier_run($argv, $argc){ if(! $item_id) return; + require_once('include/identity.php'); + $sys = get_sys_channel(); + if($cmd == 'permission_update') { // Get the recipient $r = q("select abook.*, hubloc.* from abook @@ -136,6 +142,7 @@ function notifier_run($argv, $argc){ $recipients = array(); $url_recipients = array(); $normal_mode = true; + $packet_type = 'undefined'; if($cmd === 'mail') { $normal_mode = false; @@ -164,14 +171,24 @@ function notifier_run($argv, $argc){ elseif($cmd === 'expire') { $normal_mode = false; $expire = true; - $items = q("SELECT * FROM `item` WHERE `uid` = %d AND `wall` = 1 - AND `deleted` = 1 AND `changed` > UTC_TIMESTAMP() - INTERVAL 10 MINUTE", - intval($item_id) + $items = q("SELECT * FROM item WHERE uid = %d AND ( item_flags & %d ) + AND ( item_restrict & %d ) AND `changed` > UTC_TIMESTAMP() - INTERVAL 10 MINUTE", + intval($item_id), + intval(ITEM_WALL), + intval(ITEM_DELETED) ); $uid = $item_id; $item_id = 0; - if(! count($items)) + if(! $items) return; + +// FIXME +// This will require a special zot packet containing a list of item message_id's to be expired. +// This packet will be public, since we cannot selectively deliver here. +// We need the handling on this end to create the array, and the handling on the remote end +// to verify permissions (for each item) and process it. Until this is complete, the expire feature will be disabled. + + return; } elseif($cmd === 'suggest') { $normal_mode = false; @@ -186,6 +203,46 @@ function notifier_run($argv, $argc){ $recipients[] = $suggest[0]['cid']; $item = $suggest[0]; } + elseif($cmd === 'refresh_all') { + logger('notifier: refresh_all: ' . $item_id); + $s = q("select * from channel where channel_id = %d limit 1", + intval($item_id) + ); + if($s) + $channel = $s[0]; + $uid = $item_id; + $recipients = array(); + $r = q("select abook_xchan from abook where abook_channel = %d", + intval($item_id) + ); + if($r) { + foreach($r as $rr) { + $recipients[] = $rr['abook_xchan']; + } + } + $private = false; + $packet_type = 'refresh'; + } + elseif($cmd === 'purge_all') { + logger('notifier: purge_all: ' . $item_id); + $s = q("select * from channel where channel_id = %d limit 1", + intval($item_id) + ); + if($s) + $channel = $s[0]; + $uid = $item_id; + $recipients = array(); + $r = q("select abook_xchan from abook where abook_channel = %d", + intval($item_id) + ); + if($r) { + foreach($r as $rr) { + $recipients[] = $rr['abook_xchan']; + } + } + $private = false; + $packet_type = 'purge'; + } else { // Normal items @@ -204,13 +261,27 @@ function notifier_run($argv, $argc){ $r = fetch_post_tags($r); $target_item = $r[0]; - + + if($target_item['item_restrict'] & ITEM_DELETED) + logger('notifier: target item ITEM_DELETED', LOGGER_DEBUG); + + $unforwardable = ITEM_UNPUBLISHED|ITEM_DELAYED_PUBLISH|ITEM_WEBPAGE|ITEM_BUILDBLOCK|ITEM_PDL; + if($target_item['item_restrict'] & $unforwardable) { + logger('notifier: target item not forwardable: flags ' . $target_item['item_restrict'], LOGGER_DEBUG); + return; + } + $s = q("select * from channel where channel_id = %d limit 1", intval($target_item['uid']) ); if($s) $channel = $s[0]; + if($channel['channel_hash'] !== $target_item['author_xchan'] && $channel['channel_hash'] !== $target_item['owner_xchan']) { + logger("notifier: Sending channel {$channel['channel_hash']} is not owner {$target_item['owner_xchan']} or author {$target_item['author_xchan']}"); + return; + } + if($target_item['id'] == $target_item['parent']) { $parent_item = $target_item; @@ -231,22 +302,34 @@ function notifier_run($argv, $argc){ $top_level_post = false; } + // avoid looping of discover items 12/4/2014 + + if($sys && $parent_item['uid'] == $sys['channel_id']) + return; $encoded_item = encode_item($target_item); - $relay_to_owner = (((! $top_level_post) && ($target_item['item_flags'] & ITEM_ORIGIN)) ? true : false); + $uplink = false; + // $cmd === 'relay' indicates the owner is sending it to the original recipients // don't allow the item in the relay command to relay to owner under any circumstances, it will loop - logger('notifier: relay_to_owner: ' . (($relay_to_owner) ? 'true' : 'false')); - logger('notifier: top_level_post: ' . (($top_level_post) ? 'true' : 'false')); - logger('notifier: target_item_flags: ' . $target_item['item_flags'] . ' ' . (($target_item['item_flags'] & ITEM_ORIGIN ) ? 'true' : 'false')); + logger('notifier: relay_to_owner: ' . (($relay_to_owner) ? 'true' : 'false'), LOGGER_DATA); + logger('notifier: top_level_post: ' . (($top_level_post) ? 'true' : 'false'), LOGGER_DATA); + logger('notifier: target_item_flags: ' . $target_item['item_flags'] . ' ' . (($target_item['item_flags'] & ITEM_ORIGIN ) ? 'true' : 'false'), LOGGER_DATA); + + // tag_deliver'd post which needs to be sent back to the original author + + if(($cmd === 'uplink') && ($parent_item['item_flags'] & ITEM_UPLINK) && (! $top_level_post)) { + logger('notifier: uplink'); + $uplink = true; + } - if(($relay_to_owner) && ($cmd !== 'relay')) { + if(($relay_to_owner || $uplink) && ($cmd !== 'relay')) { logger('notifier: followup relay', LOGGER_DEBUG); - $recipients = array($parent_item['owner_xchan']); + $recipients = array(($uplink) ? $parent_item['source_xchan'] : $parent_item['owner_xchan']); $private = true; if(! $encoded_item['flags']) $encoded_item['flags'] = array(); @@ -256,6 +339,15 @@ function notifier_run($argv, $argc){ logger('notifier: normal distribution', LOGGER_DEBUG); if($cmd === 'relay') logger('notifier: owner relay'); + + // if our parent is a tag_delivery recipient, uplink to the original author causing + // a delivery fork. + + if(($parent_item['item_flags'] & ITEM_UPLINK) && (! $top_level_post) && ($cmd !== 'uplink')) { + logger('notifier: uplinking this item'); + proc_run('php','include/notifier.php','uplink',$item_id); + } + $private = false; $recipients = collect_recipients($parent_item,$private); @@ -275,40 +367,103 @@ function notifier_run($argv, $argc){ // Generic delivery section, we have an encoded item and recipients // Now start the delivery process - logger('notifier: encoded item: ' . print_r($encoded_item,true)); + $x = $encoded_item; + $x['title'] = 'private'; + $x['body'] = 'private'; + logger('notifier: encoded item: ' . print_r($x,true), LOGGER_DATA); stringify_array_elms($recipients); if(! $recipients) return; - logger('notifier: recipients: ' . print_r($recipients,true)); +// logger('notifier: recipients: ' . print_r($recipients,true)); - $env_recips = null; - if($private) { - $r = q("select xchan_guid, xchan_guid_sig from xchan where xchan_hash in (" . implode(',',$recipients) . ")"); - if($r) { - $env_recips = array(); - foreach($r as $rr) - $env_recips[] = array('guid' => $rr['xchan_guid'],'guid_sig' => $rr['xchan_guid_sig']); + $env_recips = (($private) ? array() : null); + + $details = q("select xchan_hash, xchan_instance_url, xchan_addr, xchan_guid, xchan_guid_sig from xchan where xchan_hash in (" . implode(',',$recipients) . ")"); + + $recip_list = array(); + + if($details) { + foreach($details as $d) { + + // If the recipient is federated from a traditional network they won't be able to + // handle nomadic identity. If we're publishing from a site that they aren't + // directly connected with, ignore them. + + // FIXME: make sure we run through a notifier loop on the hub they're connected + // with if this post comes in from a different hub - so that we will deliver to them. + + // On the down side, these channels will stop working if the hub they connected with + // goes down permanently, as they are (doh) not nomadic. + + if(($d['xchan_instance_url']) && ($d['xchan_instance_url'] != z_root())) + continue; + + + $recip_list[] = $d['xchan_addr'] . ' (' . $d['xchan_hash'] . ')'; + if($private) + $env_recips[] = array('guid' => $d['xchan_guid'],'guid_sig' => $d['xchan_guid_sig']); } } + if(($private) && (! $env_recips)) { + // shouldn't happen + logger('notifier: private message with no envelope recipients.' . print_r($argv,true)); + } + + logger('notifier: recipients (may be delivered to more if public): ' . print_r($recip_list,true), LOGGER_DEBUG); + + // Now we have collected recipients (except for external mentions, FIXME) // Let's reduce this to a set of hubs. // for public posts always include our own hub - $sql_extra = (($private) ? "" : " or hubloc_url = '" . z_root() . "' "); + $sql_extra = (($private) ? "" : " or hubloc_url = '" . dbesc(z_root()) . "' "); + + + if($relay_to_owner && (! $private) && ($cmd !== 'relay')) { + + // If sending a followup to the post owner, only send it to one channel clone - to avoid race conditions. + // In this case we'll pick the most recently contacted hub, as their primary might be down and the most + // recently contacted has the best chance of being alive. + + // For private posts or uplinks we have to do things differently as only the sending clone will have the recipient list. + // We have to send to all clone channels of the owner to find out who has the definitive list. Posts with + // item_private set (but no ACL list) will return empty recipients (except for the sender and owner) in + // collect_recipients() above. The end result is we should get only one delivery per delivery chain if we + // aren't the owner or author. + + + $r = q("select hubloc_sitekey, hubloc_flags, hubloc_callback, hubloc_host from hubloc + where hubloc_hash in (" . implode(',',$recipients) . ") group by hubloc_sitekey order by hubloc_connected desc limit 1"); + } + else { + $r = q("select hubloc_sitekey, hubloc_flags, hubloc_callback, hubloc_host from hubloc + where hubloc_hash in (" . implode(',',$recipients) . ") $sql_extra group by hubloc_sitekey"); + } - $r = q("select distinct(hubloc_callback),hubloc_host,hubloc_sitekey from hubloc - where hubloc_hash in (" . implode(',',$recipients) . ") $sql_extra group by hubloc_callback"); if(! $r) { logger('notifier: no hubs'); return; } $hubs = $r; + $hublist = array(); + $keys = array(); + + foreach($hubs as $hub) { + // don't try to deliver to deleted hublocs - and inexplicably SQL "distinct" and "group by" + // both return records with duplicate keys in rare circumstances + if((! ($hub['hubloc_flags'] & HUBLOC_FLAGS_DELETED)) && (! in_array($hub['hubloc_sitekey'],$keys))) { + $hublist[] = $hub['hubloc_host']; + $keys[] = $hub['hubloc_sitekey']; + } + } + + logger('notifier: will notify/deliver to these hubs: ' . print_r($hublist,true), LOGGER_DEBUG); $interval = ((get_config('system','delivery_interval') !== false) ? intval(get_config('system','delivery_interval')) : 2 ); @@ -321,19 +476,45 @@ function notifier_run($argv, $argc){ $deliver = array(); foreach($hubs as $hub) { + + if(defined('DIASPORA_RELIABILITY_EMULATION')) { + $cointoss = mt_rand(0,2); + if($cointoss == 2) { + continue; + } + } + $hash = random_string(); - $n = zot_build_packet($channel,'notify',$env_recips,(($private) ? $hub['hubloc_sitekey'] : null),$hash); - q("insert into outq ( outq_hash, outq_account, outq_channel, outq_posturl, outq_async, outq_created, outq_updated, outq_notify, outq_msg ) values ( '%s', %d, %d, '%s', %d, '%s', '%s', '%s', '%s' )", - dbesc($hash), - intval($target_item['aid']), - intval($target_item['uid']), - dbesc($hub['hubloc_callback']), - intval(1), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc($n), - dbesc(json_encode($encoded_item)) - ); + if($packet_type === 'refresh' || $packet_type === 'purge') { + $n = zot_build_packet($channel,$packet_type); + q("insert into outq ( outq_hash, outq_account, outq_channel, outq_driver, outq_posturl, outq_async, outq_created, outq_updated, outq_notify, outq_msg ) values ( '%s', %d, %d, '%s', '%s', %d, '%s', '%s', '%s', '%s' )", + dbesc($hash), + intval($channel['channel_account_id']), + intval($channel['channel_id']), + dbesc('zot'), + dbesc($hub['hubloc_callback']), + intval(1), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc($n), + dbesc('') + ); + } + else { + $n = zot_build_packet($channel,'notify',$env_recips,(($private) ? $hub['hubloc_sitekey'] : null),$hash); + q("insert into outq ( outq_hash, outq_account, outq_channel, outq_driver, outq_posturl, outq_async, outq_created, outq_updated, outq_notify, outq_msg ) values ( '%s', %d, %d, '%s', '%s', %d, '%s', '%s', '%s', '%s' )", + dbesc($hash), + intval($target_item['aid']), + intval($target_item['uid']), + dbesc('zot'), + dbesc($hub['hubloc_callback']), + intval(1), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc($n), + dbesc(json_encode($encoded_item)) + ); + } $deliver[] = $hash; if(count($deliver) >= $deliveries_per_process) { @@ -349,12 +530,16 @@ function notifier_run($argv, $argc){ if(count($deliver)) { proc_run('php','include/deliver.php',$deliver); } + + logger('notifier: basic loop complete.', LOGGER_DEBUG); if($normal_mode) call_hooks('notifier_normal',$target_item); + call_hooks('notifier_end',$target_item); + logger('notifer: complete.'); return; } diff --git a/include/notify.php b/include/notify.php index 9517e06dc..aa96fa279 100644 --- a/include/notify.php +++ b/include/notify.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ function format_notification($item) { @@ -15,20 +15,19 @@ function format_notification($item) { localize_item($item); -// FIXME - we may need the parent - - if(! $item['localize']) { + if($item_localize) { + $itemem_text = $item['localize']; + } + else { $itemem_text = (($item['item_flags'] & ITEM_THREAD_TOP) - ? sprintf( t("%s created a new post"), $item['author']['xchan_name']) - : sprintf( t("%s commented on %s's post"), $item['author']['xchan_name'], $item['pname'])); + ? t('created a new post') + : sprintf( t('commented on %s\'s post'), $item['owner']['xchan_name'])); } - else - $itemem_text = $item['localize']; // convert this logic into a json array just like the system notifications return array( - 'notify_link' => z_root() . '/notify/view_item/' . $item['id'], + 'notify_link' => $item['llink'], 'name' => $item['author']['xchan_name'], 'url' => $item['author']['xchan_url'], 'photo' => $item['author']['xchan_photo_s'], diff --git a/include/oauth.php b/include/oauth.php index 99fc16eef..8eb8a83d8 100644 --- a/include/oauth.php +++ b/include/oauth.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ /** * OAuth server * Based on oauth2-php <http://code.google.com/p/oauth2-php/> @@ -18,11 +18,12 @@ class FKOAuthDataStore extends OAuthDataStore { function lookup_consumer($consumer_key) { logger(__function__.":".$consumer_key); - //echo "<pre>"; var_dump($consumer_key); killme(); - +// echo "<pre>"; var_dump($consumer_key); killme(); + $r = q("SELECT client_id, pw, redirect_uri FROM clients WHERE client_id='%s'", dbesc($consumer_key) ); + if (count($r)) return new OAuthConsumer($r[0]['client_id'],$r[0]['pw'],$r[0]['redirect_uri']); return null; @@ -30,11 +31,13 @@ class FKOAuthDataStore extends OAuthDataStore { function lookup_token($consumer, $token_type, $token) { logger(__function__.":".$consumer.", ". $token_type.", ".$token); + $r = q("SELECT id, secret,scope, expires, uid FROM tokens WHERE client_id='%s' AND scope='%s' AND id='%s'", dbesc($consumer->key), dbesc($token_type), dbesc($token) ); + if (count($r)){ $ot=new OAuthToken($r[0]['id'],$r[0]['secret']); $ot->scope=$r[0]['scope']; @@ -46,12 +49,14 @@ class FKOAuthDataStore extends OAuthDataStore { } function lookup_nonce($consumer, $token, $nonce, $timestamp) { - //echo __file__.":".__line__."<pre>"; var_dump($consumer,$key); killme(); +// echo __file__.":".__line__."<pre>"; var_dump($consumer,$key); killme(); + $r = q("SELECT id, secret FROM tokens WHERE client_id='%s' AND id='%s' AND expires=%d", dbesc($consumer->key), dbesc($nonce), intval($timestamp) ); + if (count($r)) return new OAuthToken($r[0]['id'],$r[0]['secret']); return null; @@ -67,13 +72,14 @@ class FKOAuthDataStore extends OAuthDataStore { } else { $k = $consumer; } - + $r = q("INSERT INTO tokens (id, secret, client_id, scope, expires) VALUES ('%s','%s','%s','%s', UNIX_TIMESTAMP()+%d)", dbesc($key), dbesc($sec), dbesc($k), 'request', intval(REQUEST_TOKEN_DURATION)); + if (!$r) return null; return new OAuthToken($key,$sec); } @@ -95,6 +101,7 @@ class FKOAuthDataStore extends OAuthDataStore { $key = $this->gen_token(); $sec = $this->gen_token(); + $r = q("INSERT INTO tokens (id, secret, client_id, scope, expires, uid) VALUES ('%s','%s','%s','%s', UNIX_TIMESTAMP()+%d, %d)", dbesc($key), dbesc($sec), @@ -102,6 +109,7 @@ class FKOAuthDataStore extends OAuthDataStore { 'access', intval(ACCESS_TOKEN_DURATION), intval($uverifier)); + if ($r) $ret = new OAuthToken($key,$sec); } @@ -131,9 +139,9 @@ class FKOAuth1 extends OAuthServer { } function loginUser($uid){ - logger("FKOAuth1::loginUser $uid"); + logger("RedOAuth1::loginUser $uid"); $a = get_app(); - $r = q("SELECT * FROM `user` WHERE uid=%d AND `blocked` = 0 AND `account_expired` = 0 AND `verified` = 1 LIMIT 1", + $r = q("SELECT * FROM channel WHERE channel_id = %d LIMIT 1", intval($uid) ); if(count($r)){ @@ -143,35 +151,43 @@ class FKOAuth1 extends OAuthServer { header('HTTP/1.0 401 Unauthorized'); die('This api requires login'); } - $_SESSION['uid'] = $record['uid']; - $_SESSION['theme'] = $record['theme']; - $_SESSION['mobile-theme'] = get_pconfig($record['uid'], 'system', 'mobile_theme'); + + $_SESSION['uid'] = $record['channel_id']; + $_SESSION['theme'] = $record['channel_theme']; + $_SESSION['account_id'] = $record['channel_account_id']; + $_SESSION['mobile_theme'] = get_pconfig($record['channel_id'], 'system', 'mobile_theme'); $_SESSION['authenticated'] = 1; - $_SESSION['page_flags'] = $record['page-flags']; - $_SESSION['my_url'] = $a->get_baseurl() . '/channel/' . $record['nickname']; + $_SESSION['my_url'] = $a->get_baseurl() . '/channel/' . $record['channel_address']; $_SESSION['addr'] = $_SERVER['REMOTE_ADDR']; + $_SESSION['allow_api'] = true; + $x = q("select * from account where account_id = %d limit 1", + intval($record['channel_account_id']) + ); + if($x) + $a->account = $x[0]; - //notice( t("Welcome back ") . $record['username'] . EOL); - $a->user = $record; + change_channel($record['channel_id']); - if(strlen($a->user['timezone'])) { - date_default_timezone_set($a->user['timezone']); - $a->timezone = $a->user['timezone']; - } + $a->channel = $record; - $r = q("SELECT * FROM `contact` WHERE `uid` = %s AND `self` = 1 LIMIT 1", - intval($_SESSION['uid'])); - if(count($r)) { - $a->contact = $r[0]; - $a->cid = $r[0]['id']; - $_SESSION['cid'] = $a->cid; + if(strlen($a->channel['channel_timezone'])) { + date_default_timezone_set($a->channel['channel_timezone']); +// $a->timezone = $a->user['timezone']; } - q("UPDATE `user` SET `login_date` = '%s' WHERE `uid` = %d LIMIT 1", - dbesc(datetime_convert()), - intval($_SESSION['uid']) - ); - call_hooks('logged_in', $a->user); +// $r = q("SELECT * FROM `contact` WHERE `uid` = %s AND `self` = 1 LIMIT 1", +// intval($_SESSION['uid'])); +// if(count($r)) { +// $a->contact = $r[0]; +// $a->cid = $r[0]['id']; +// $_SESSION['cid'] = $a->cid; +// } +// q("UPDATE `user` SET `login_date` = '%s' WHERE `uid` = %d LIMIT 1", +// dbesc(datetime_convert()), +// intval($_SESSION['uid']) +// ); +// +// call_hooks('logged_in', $a->user); } } diff --git a/include/oembed.php b/include/oembed.php index 6fc4c5371..42a9881ed 100755 --- a/include/oembed.php +++ b/include/oembed.php @@ -1,12 +1,10 @@ -<?php +<?php /** @file */ function oembed_replacecb($matches){ -// logger('oembedcb'); + $embedurl=$matches[1]; $j = oembed_fetch_url($embedurl); - $s = oembed_format_object($j); - return $s;//oembed_iframe($s,$j->width,$j->height); - - + $s = oembed_format_object($j); + return $s; } @@ -16,6 +14,10 @@ function oembed_fetch_url($embedurl){ $txt = Cache::get($a->videowidth . $embedurl); + if(strstr($txt,'youtu')) { + $txt = str_replace('http:','https:',$txt); + } + // These media files should now be caught in bbcode.php // left here as a fallback in case this is called from another source @@ -26,10 +28,28 @@ function oembed_fetch_url($embedurl){ if(is_null($txt)){ $txt = ""; - if (!in_array($ext, $noexts)){ + if (in_array($ext, $noexts)) { + $m = @parse_url($embedurl); + $zrl = false; + if($m['host']) { + $r = q("select hubloc_url from hubloc where hubloc_host = '%s' limit 1", + dbesc($m['host']) + ); + if($r) + $zrl = true; + } + if($zrl) { + $embedurl = zid($embedurl); + } + } + else { // try oembed autodiscovery $redirects = 0; - $html_text = fetch_url($embedurl, false, $redirects, 15, "text/*"); + + $result = z_fetch_url($embedurl, false, $redirects, array('timeout' => 15, 'accept_content' => "text/*", 'novalidate' => true )); + if($result['success']) + $html_text = $result['body']; + if($html_text){ $dom = @DOMDocument::loadHTML($html_text); if ($dom){ @@ -40,17 +60,19 @@ function oembed_fetch_url($embedurl){ $entries = $xpath->query("//link[@type='application/json+oembed']"); foreach($entries as $e){ $href = $e->getAttributeNode("href")->nodeValue; - $txt = fetch_url($href . '&maxwidth=' . $a->videowidth); + $x = z_fetch_url($href . '&maxwidth=' . $a->videowidth); + $txt = $x['body']; break; } } } } - if ($txt==false || $txt==""){ - // try oohembed service - $ourl = "http://oohembed.com/oohembed/?url=".urlencode($embedurl).'&maxwidth=' . $a->videowidth; - $txt = fetch_url($ourl); + if ($txt==false || $txt=="") { + $x = array('url' => $embedurl,'videowidth' => $a->videowidth); + call_hooks('oembed_probe',$x); + if(array_key_exists('embed',$x)) + $txt = $x['embed']; } $txt=trim($txt); @@ -70,6 +92,7 @@ function oembed_format_object($j){ $a = get_app(); $embedurl = $j->embedurl; $jhtml = oembed_iframe($j->embedurl,(isset($j->width) ? $j->width : null), (isset($j->height) ? $j->height : null) ); + $ret="<span class='oembed ".$j->type."'>"; switch ($j->type) { case "video": { @@ -80,6 +103,13 @@ function oembed_format_object($j){ $th=120; $tw = $th*$tr; $tpl=get_markup_template('oembed_video.tpl'); + if(strstr($embedurl,'youtu')) { + $embedurl = str_replace('http:','https:',$embedurl); + $j->thumbnail_url = str_replace('http:','https:', $j->thumbnail_url); + $jhtml = str_replace('http:','https:', $jhtml); + $j->html = str_replace('http:','https:', $j->html); + + } $ret.=replace_macros($tpl, array( '$baseurl' => $a->get_baseurl(), '$embedurl'=>$embedurl, @@ -112,6 +142,7 @@ function oembed_format_object($j){ if ( $j->type!='rich' || !strpos($j->html,$embedurl) ){ $embedlink = (isset($j->title))?$j->title:$embedurl; $ret .= "<a href='$embedurl' rel='oembed'>$embedlink</a>"; + $ret .= "<br>"; if (isset($j->author_name)) $ret.=" by ".$j->author_name; if (isset($j->provider_name)) $ret.=" on ".$j->provider_name; } else { @@ -133,8 +164,11 @@ function oembed_iframe($src,$width,$height) { $a = get_app(); + $sandbox = ((strpos($src,get_app()->get_hostname())) ? ' sandbox="allow-scripts" ' : ''); + $s = $a->get_baseurl()."/oembed/".base64url_encode($src); - return '<iframe height="' . $height . '" width="' . $width . '" src="' . $s . '" frameborder="no" >' . t('Embedded content') . '</iframe>'; + + return '<iframe ' . $sandbox . ' height="' . $height . '" width="' . $width . '" src="' . $s . '" frameborder="no" >' . t('Embedded content') . '</iframe>'; } diff --git a/include/onedirsync.php b/include/onedirsync.php new file mode 100644 index 000000000..8ae1df5e5 --- /dev/null +++ b/include/onedirsync.php @@ -0,0 +1,61 @@ +<?php /** @file */ + +require_once('boot.php'); +require_once('include/cli_startup.php'); +require_once('include/zot.php'); +require_once('include/dir_fns.php'); + + +function onedirsync_run($argv, $argc){ + + + cli_startup(); + $a = get_app(); + + logger('onedirsync: start ' . intval($argv[1])); + + if(($argc > 1) && (intval($argv[1]))) + $update_id = intval($argv[1]); + + if(! $update_id) { + logger('onedirsync: no update'); + return; + } + + $r = q("select * from updates where ud_id = %d limit 1", + intval($update_id) + ); + + if(! $r) + return; + if(($r[0]['ud_flags'] & UPDATE_FLAGS_UPDATED) || (! $r[0]['ud_addr'])) + return; + + // Have we probed this channel more recently than the other directory server + // (where we received this update from) ? + // If we have, we don't need to do anything except mark any older entries updated + + $x = q("select * from updates where ud_addr = '%s' and ud_date > '%s' and ( ud_flags & %d ) order by ud_date desc limit 1", + dbesc($r[0]['ud_addr']), + dbesc($r[0]['ud_date']), + intval(UPDATE_FLAGS_UPDATED) + ); + if($x) { + $y = q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not ( ud_flags & %d ) and ud_date < '%s' ", + intval(UPDATE_FLAGS_UPDATED), + dbesc($r[0]['ud_addr']), + intval(UPDATE_FLAGS_UPDATED), + dbesc($x[0]['ud_date']) + ); + return; + } + + update_directory_entry($r[0]); + + return; +} + +if (array_search(__file__,get_included_files())===0){ + onedirsync_run($argv,$argc); + killme(); +} diff --git a/include/onepoll.php b/include/onepoll.php index 4b44ff5b9..e81d8bcf7 100644 --- a/include/onepoll.php +++ b/include/onepoll.php @@ -1,8 +1,11 @@ -<?php +<?php /** @file */ require_once('boot.php'); require_once('include/cli_startup.php'); require_once('include/zot.php'); +require_once('include/socgraph.php'); +require_once('include/Contact.php'); + function onepoll_run($argv, $argc){ @@ -26,30 +29,25 @@ function onepoll_run($argv, $argc){ return; } - $d = datetime_convert(); - - $contacts = q("SELECT abook.*, xchan.*, account.* FROM abook LEFT JOIN account on abook_account = account_id left join xchan on xchan_hash = abook_xchan where abook_id = %d - AND (( abook_flags = %d ) OR ( abook_flags = %d )) - AND (( account_flags = %d ) OR ( account_flags = %d )) ORDER BY RAND()", + AND (( abook_flags & %d ) OR ( abook_flags = %d )) + AND (( account_flags = %d ) OR ( account_flags = %d )) limit 1", intval($contact_id), - intval(ABOOK_FLAG_HIDDEN), + intval(ABOOK_FLAG_HIDDEN|ABOOK_FLAG_PENDING|ABOOK_FLAG_UNCONNECTED), intval(0), intval(ACCOUNT_OK), intval(ACCOUNT_UNVERIFIED) ); if(! $contacts) { + logger('onepoll: abook_id not found: ' . $contact_id); return; } - if(! $contacts) - return; - $contact = $contacts[0]; $t = $contact['abook_updated']; @@ -67,40 +65,62 @@ function onepoll_run($argv, $argc){ logger("onepoll: poll: ({$contact['id']}) IMPORTER: {$importer['xchan_name']}, CONTACT: {$contact['xchan_name']}"); - $last_update = (($contact['last_update'] === '0000-00-00 00:00:00') + $last_update = ((($contact['abook_updated'] === $contact['abook_created']) || ($contact['abook_updated'] === '0000-00-00 00:00:00')) ? datetime_convert('UTC','UTC','now - 7 days') - : datetime_convert('UTC','UTC',$contact['abook_updated']) + : datetime_convert('UTC','UTC',$contact['abook_updated'] . ' - 2 days') ); // update permissions $x = zot_refresh($contact,$importer); + $responded = false; + $updated = datetime_convert(); if(! $x) { - // mark for death - + // mark for death by not updating abook_connected, this is caught in include/poller.php + q("update abook set abook_updated = '%s' where abook_id = %d limit 1", + dbesc($updated), + intval($contact['abook_id']) + ); } else { - q("update abook set abook_updated = '%s' where abook_id = %d limit 1", - dbesc(datetime_convert()), + q("update abook set abook_updated = '%s', abook_connected = '%s' where abook_id = %d limit 1", + dbesc($updated), + dbesc($updated), intval($contact['abook_id']) ); - - // if marked for death, reset - + $responded = true; } + if(! $responded) + return; + if($contact['xchan_connurl']) { - $feedurl = str_replace('/poco/','/zotfeed/',$channel['xchan_connurl']); - - $x = z_fetch_url($feedurl . '?f=$mindate=' . $last_update); - if($x['success']) { + $fetch_feed = true; + $x = null; + + if(! ($contact['abook_their_perms'] & PERMS_R_STREAM )) + $fetch_feed = false; + + if($fetch_feed) { + + $feedurl = str_replace('/poco/','/zotfeed/',$contact['xchan_connurl']); + $x = z_fetch_url($feedurl . '?f=&mindate=' . urlencode($last_update)); + + logger('feed_update: ' . print_r($x,true), LOGGER_DATA); + + } + + if(($x) && ($x['success'])) { $total = 0; + logger('onepoll: feed update ' . $contact['xchan_name']); + $j = json_decode($x['body'],true); if($j['success'] && $j['messages']) { foreach($j['messages'] as $message) { - $results = process_delivery(array('hash' => $contact['xchan_hash']),$message, + $results = process_delivery(array('hash' => $contact['xchan_hash']), get_item_elements($message), array(array('hash' => $importer['xchan_hash'])), false); + logger('onepoll: feed_update: process_delivery: ' . print_r($results,true)); $total ++; } logger("onepoll: $total messages processed"); @@ -113,16 +133,14 @@ function onepoll_run($argv, $argc){ // set last updated timestamp - $r = null; - if($contact['xchan_connurl']) { $r = q("SELECT xlink_id from xlink - where xlink_xchan = '%s' and xlink_updated > UTC_TIMESTAMP() - INTERVAL 1 DAY", + where xlink_xchan = '%s' and xlink_updated > UTC_TIMESTAMP() - INTERVAL 1 DAY limit 1", intval($contact['xchan_hash']) ); - } - if($r) { - poco_load($contact['xchan_hash'],$contact['xchan_connurl']); + if(! $r) { + poco_load($contact['xchan_hash'],$contact['xchan_connurl']); + } } return; diff --git a/include/page_widgets.php b/include/page_widgets.php new file mode 100644 index 000000000..49d1439be --- /dev/null +++ b/include/page_widgets.php @@ -0,0 +1,49 @@ +<?php + +// A basic toolbar for observers with write_pages permissions +function writepages_widget ($who,$which){ + return replace_macros(get_markup_template('write_pages.tpl'), array( + '$new' => t('New Page'), + '$newurl' => "webpages/$who", + '$edit' => t('Edit'), + '$editurl' => "editwebpage/$who/$which" + )); +} + + + +// Chan is channel_id, $which is channel_address - we'll need to pass observer later too. +function pagelist_widget ($owner,$which){ + + $r = q("select * from item_id left join item on item_id.iid = item.id where item_id.uid = %d and service = 'WEBPAGE' order by item.created desc", + intval($owner) + ); + + $pages = null; + + if($r) { + $pages = array(); + foreach($r as $rr) { + $pages[$rr['iid']][] = array('url' => $rr['iid'],'pagetitle' => $rr['sid'],'title' => $rr['title'],'created' => datetime_convert('UTC',date_default_timezone_get(),$rr['created']),'edited' => datetime_convert('UTC',date_default_timezone_get(),$rr['edited'])); + } + } + + //Build the base URL for edit links + $url = z_root() . "/editwebpage/" . $which; + // This isn't pretty, but it works. Until I figure out what to do with the UI, it's Good Enough(TM). + return $o . replace_macros(get_markup_template("webpagelist.tpl"), array( + '$baseurl' => $url, + '$edit' => t('Edit'), + '$pages' => $pages, + '$channel' => $which, + '$view' => t('View'), + '$preview' => t('Preview'), + '$actions_txt' => t('Actions'), + '$pagelink_txt' => t('Page Link'), + '$title_txt' => t('Title'), + '$created_txt' => t('Created'), + '$edited_txt' => t('Edited') + + )); + +} diff --git a/include/permissions.php b/include/permissions.php index e74486a06..a3ec13925 100644 --- a/include/permissions.php +++ b/include/permissions.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ function get_perms() { @@ -15,6 +15,7 @@ function get_perms() { 'view_photos' => array('channel_r_photos', intval(PERMS_R_PHOTOS), true, t('Can view my "public" photo albums'), ''), 'view_contacts' => array('channel_r_abook', intval(PERMS_R_ABOOK), true, t('Can view my "public" address book'), ''), 'view_storage' => array('channel_r_storage', intval(PERMS_R_STORAGE), true, t('Can view my "public" file storage'), ''), + 'view_pages' => array('channel_r_pages', intval(PERMS_R_PAGES), true, t('Can view my "public" pages'), ''), // Write permissions 'send_stream' => array('channel_w_stream', intval(PERMS_W_STREAM), false, t('Can send me their channel stream and posts'), ''), @@ -22,26 +23,31 @@ function get_perms() { 'post_comments' => array('channel_w_comment', intval(PERMS_W_COMMENT), false, t('Can comment on my posts'), ''), 'post_mail' => array('channel_w_mail', intval(PERMS_W_MAIL), false, t('Can send me private mail messages'), ''), 'post_photos' => array('channel_w_photos', intval(PERMS_W_PHOTOS), false, t('Can post photos to my photo albums'), ''), - 'tag_deliver' => array('channel_w_tagwall', intval(PERMS_W_TAGWALL), false, t('Can forward to all my channel contacts via post tags'), t('Advanced - useful for creating group forum channels')), - 'chat' => array('channel_w_chat', intval(PERMS_W_CHAT), false, t('Can chat with me (when available)'), t('Requires compatible chat plugin')), + 'tag_deliver' => array('channel_w_tagwall', intval(PERMS_W_TAGWALL), false, t('Can forward to all my channel contacts via post @mentions'), t('Advanced - useful for creating group forum channels')), + 'chat' => array('channel_w_chat', intval(PERMS_W_CHAT), false, t('Can chat with me (when available)'), t('')), 'write_storage' => array('channel_w_storage', intval(PERMS_W_STORAGE), false, t('Can write to my "public" file storage'), ''), + 'write_pages' => array('channel_w_pages', intval(PERMS_W_PAGES), false, t('Can edit my "public" pages'), ''), + + 'republish' => array('channel_a_republish', intval(PERMS_A_REPUBLISH), false, t('Can source my "public" posts in derived channels'), t('Somewhat advanced - very useful in open communities')), 'delegate' => array('channel_a_delegate', intval(PERMS_A_DELEGATE), false, t('Can administer my channel resources'), t('Extremely advanced. Leave this alone unless you know what you are doing')), ); - return $global_perms; + $ret = array('global_permissions' => $global_perms); + call_hooks('global_permissions',$ret); + return $ret['global_permissions']; } /** - * get_all_perms($uid,$observer) + * get_all_perms($uid,$observer_xchan) * * @param $uid : The channel_id associated with the resource owner - * @param $observer: The xchan_hash representing the observer + * @param $observer_xchan: The xchan_hash representing the observer * * @returns: array of all permissions, key is permission name, value is true or false */ -function get_all_perms($uid,$observer,$internal_use = true) { +function get_all_perms($uid,$observer_xchan,$internal_use = true) { $global_perms = get_perms(); @@ -81,13 +87,26 @@ function get_all_perms($uid,$observer,$internal_use = true) { // Next we're going to check for blocked or ignored contacts. // These take priority over all other settings. - if($observer) { + if($observer_xchan) { + if($r[0][$channel_perm] & PERMS_AUTHED) { + $ret[$perm_name] = true; + continue; + } + if(! $abook_checked) { - $x = q("select abook_my_perms, abook_flags from abook - where abook_channel = %d and abook_xchan = '%s' limit 1", + $x = q("select abook_my_perms, abook_flags, xchan_network from abook left join xchan on abook_xchan = xchan_hash + where abook_channel = %d and abook_xchan = '%s' and not ( abook_flags & %d ) limit 1", intval($uid), - dbesc($observer) + dbesc($observer_xchan), + intval(ABOOK_FLAG_SELF) ); + if(! $x) { + // not in address book, see if they've got an xchan + $y = q("select xchan_network from xchan where xchan_hash = '%s' limit 1", + dbesc($observer_xchan) + ); + } + $abook_checked = true; } @@ -107,10 +126,10 @@ function get_all_perms($uid,$observer,$internal_use = true) { } } - // Check if this $uid is actually the $observer - if it's your content + // Check if this $uid is actually the $observer_xchan - if it's your content // you always have permission to do anything - if(($observer) && ($r[0]['channel_hash'] === $observer)) { + if(($observer_xchan) && ($r[0]['channel_hash'] === $observer_xchan)) { $ret[$perm_name] = true; continue; } @@ -125,16 +144,18 @@ function get_all_perms($uid,$observer,$internal_use = true) { // From here on out, we need to know who they are. If we can't figure it // out, permission is denied. - if(! $observer) { + if(! $observer_xchan) { $ret[$perm_name] = false; continue; } - // If we're still here, we have an observer, which means they're in the network. + // If we're still here, we have an observer, check the network. if($r[0][$channel_perm] & PERMS_NETWORK) { - $ret[$perm_name] = true; - continue; + if(($x && $x[0]['xchan_network'] === 'zot') || ($y && $y[0]['xchan_network'] === 'zot')) { + $ret[$perm_name] = true; + continue; + } } // If PERMS_SITE is specified, find out if they've got an account on this hub @@ -142,7 +163,7 @@ function get_all_perms($uid,$observer,$internal_use = true) { if($r[0][$channel_perm] & PERMS_SITE) { if(! $onsite_checked) { $c = q("select channel_hash from channel where channel_hash = '%s' limit 1", - dbesc($observer) + dbesc($observer_xchan) ); $onsite_checked = true; @@ -156,25 +177,36 @@ function get_all_perms($uid,$observer,$internal_use = true) { continue; } - // If PERMS_CONTACTS or PERMS_SPECIFIC, they need to be in your address book - // $x is a valid address book entry + // From here on we require that the observer be a connection and + // handle whether we're allowing any, approved or specific ones if(! $x) { $ret[$perm_name] = false; continue; } - if(($r) && ($r[0][$channel_perm] & PERMS_CONTACTS)) { + // They are in your address book, but haven't been approved + + if($r[0][$channel_perm] & PERMS_PENDING) { + $ret[$perm_name] = true; + continue; + } + + if($x[0]['abook_flags'] & ABOOK_FLAG_PENDING) { + $ret[$perm_name] = false; + continue; + } - // They're a contact, so they have permission + // They're a contact, so they have permission + if($r[0][$channel_perm] & PERMS_CONTACTS) { $ret[$perm_name] = true; continue; } // Permission granted to certain channels. Let's see if the observer is one of them - if(($r) && ($r[0][$channel_perm] & PERMS_SPECIFIC)) { + if($r[0][$channel_perm] & PERMS_SPECIFIC) { if(($x[0]['abook_my_perms'] & $global_perms[$perm_name][1])) { $ret[$perm_name] = true; continue; @@ -188,11 +220,27 @@ function get_all_perms($uid,$observer,$internal_use = true) { } - return $ret; + $arr = array( + 'channel_id' => $uid, + 'observer_hash' => $observer_xchan, + 'permissions' => $ret); + + call_hooks('get_all_perms',$arr); + return $arr['permissions']; } -function perm_is_allowed($uid,$observer,$permission) { +function perm_is_allowed($uid,$observer_xchan,$permission) { + + $arr = array( + 'channel_id' => $uid, + 'observer_hash' => $observer_xchan, + 'permission' => $permission, + 'result' => false); + + call_hooks('perm_is_allowed',$arr); + if($arr['result']) + return true; $global_perms = get_perms(); @@ -207,10 +255,15 @@ function perm_is_allowed($uid,$observer,$permission) { if(! $r) return false; - if($observer) { - $x = q("select abook_my_perms, abook_flags from abook where abook_channel = %d and abook_xchan = '%s' limit 1", + if($observer_xchan) { + if($r[0][$channel_perm] & PERMS_AUTHED) + return true; + + $x = q("select abook_my_perms, abook_flags, xchan_network from abook left join xchan on abook_xchan = xchan_hash + where abook_channel = %d and abook_xchan = '%s' and not ( abook_flags & %d ) limit 1", intval($uid), - dbesc($observer) + dbesc($observer_xchan), + intval(ABOOK_FLAG_SELF) ); // If they're blocked - they can't read or write @@ -221,12 +274,17 @@ function perm_is_allowed($uid,$observer,$permission) { if(($x) && (! $global_perms[$permission][2]) && ($x[0]['abook_flags'] & ABOOK_FLAG_IGNORED)) return false; + if(! $x) { + // not in address book, see if they've got an xchan + $y = q("select xchan_network from xchan where xchan_hash = '%s' limit 1", + dbesc($observer_xchan) + ); + } } + // Check if this $uid is actually the $observer_xchan - // Check if this $uid is actually the $observer - - if($r[0]['channel_hash'] === $observer) + if($r[0]['channel_hash'] === $observer_xchan) return true; @@ -235,31 +293,47 @@ function perm_is_allowed($uid,$observer,$permission) { // If it's an unauthenticated observer, we only need to see if PERMS_PUBLIC is set - if(! $observer) { + if(! $observer_xchan) { return false; } - // If we're still here, we have an observer, which means they're in the network. - - if($r[0][$channel_perm] & PERMS_NETWORK) - return true; + // If we're still here, we have an observer, check the network. + if($r[0][$channel_perm] & PERMS_NETWORK) { + if (($x && $x[0]['xchan_network'] === 'zot') || ($y && $y[0]['xchan_network'] === 'zot')) + return true; + } // If PERMS_SITE is specified, find out if they've got an account on this hub if($r[0][$channel_perm] & PERMS_SITE) { $c = q("select channel_hash from channel where channel_hash = '%s' limit 1", - dbesc($observer) + dbesc($observer_xchan) ); if($c) return true; return false; - } + } + + // From here on we require that the observer be a connection and + // handle whether we're allowing any, approved or specific ones if(! $x) { return false; } + // They are in your address book, but haven't been approved + + if($r[0][$channel_perm] & PERMS_PENDING) { + return true; + } + + if($x[0]['abook_flags'] & ABOOK_FLAG_PENDING) { + return false; + } + + // They're a contact, so they have permission + if($r[0][$channel_perm] & PERMS_CONTACTS) { return true; } @@ -274,9 +348,51 @@ function perm_is_allowed($uid,$observer,$permission) { // No permissions allowed. return false; +} + + +// Check a simple array of observers against a permissions +// return a simple array of those with permission +function check_list_permissions($uid,$arr,$perm) { + $result = array(); + if($arr) + foreach($arr as $x) + if(perm_is_allowed($uid,$x,$perm)) + $result[] = $x; + return($result); } +function site_default_perms() { + + $typical = array( + 'view_stream' => PERMS_PUBLIC, + 'view_profile' => PERMS_PUBLIC, + 'view_photos' => PERMS_PUBLIC, + 'view_contacts' => PERMS_PUBLIC, + 'view_storage' => PERMS_PUBLIC, + 'view_pages' => PERMS_PUBLIC, + 'send_stream' => PERMS_SPECIFIC, + 'post_wall' => PERMS_SPECIFIC, + 'post_comments' => PERMS_SPECIFIC, + 'post_mail' => PERMS_SPECIFIC, + 'post_photos' => 0, + 'tag_deliver' => PERMS_SPECIFIC, + 'chat' => PERMS_SPECIFIC, + 'write_storage' => 0, + 'write_pages' => 0, + 'delegate' => 0, + ); + $global_perms = get_perms(); + $ret = array(); + foreach($global_perms as $perm => $v) { + $x = get_config('default_perms',$perm); + if($x === false) + $x = $typical[$perm]; + $ret[$perm] = $x; + } + return $ret; +} diff --git a/include/photo/photo_driver.php b/include/photo/photo_driver.php new file mode 100644 index 000000000..f5e915402 --- /dev/null +++ b/include/photo/photo_driver.php @@ -0,0 +1,674 @@ +<?php /** @file */ + +function photo_factory($data, $type = null) { + $ph = null; + + if(class_exists('Imagick')) { + require_once('include/photo/photo_imagick.php'); + $ph = new photo_imagick($data,$type); + } + else { + require_once('include/photo/photo_gd.php'); + $ph = new photo_gd($data,$type); + } + + return $ph; +} + + + + +abstract class photo_driver { + + protected $image; + protected $width; + protected $height; + protected $valid; + protected $type; + protected $types; + + abstract function supportedTypes(); + + abstract function load($data,$type); + + abstract function destroy(); + + abstract function setDimensions(); + + abstract function getImage(); + + abstract function doScaleImage($new_width,$new_height); + + abstract function rotate($degrees); + + abstract function flip($horiz = true, $vert = false); + + abstract function cropImage($max,$x,$y,$w,$h); + + abstract function imageString(); + + + public function __construct($data, $type='') { + $this->types = $this->supportedTypes(); + if (! array_key_exists($type,$this->types)){ + $type='image/jpeg'; + } + $this->type = $type; + $this->valid = false; + $this->load($data,$type); + } + + public function __destruct() { + if($this->is_valid()) + $this->destroy(); + } + + public function is_valid() { + return $this->valid; + } + + public function getWidth() { + if(!$this->is_valid()) + return FALSE; + return $this->width; + } + + public function getHeight() { + if(!$this->is_valid()) + return FALSE; + return $this->height; + } + + + public function saveImage($path) { + if(!$this->is_valid()) + return FALSE; + file_put_contents($path, $this->imageString()); + } + + + public function getType() { + if(!$this->is_valid()) + return FALSE; + + return $this->type; + } + + public function getExt() { + if(!$this->is_valid()) + return FALSE; + + return $this->types[$this->getType()]; + } + + public function scaleImage($max) { + if(!$this->is_valid()) + return FALSE; + + $width = $this->width; + $height = $this->height; + + $dest_width = $dest_height = 0; + + if((! $width)|| (! $height)) + return FALSE; + + if($width > $max && $height > $max) { + + // very tall image (greater than 16:9) + // constrain the width - let the height float. + + if((($height * 9) / 16) > $width) { + $dest_width = $max; + $dest_height = intval(( $height * $max ) / $width); + } + + // else constrain both dimensions + + elseif($width > $height) { + $dest_width = $max; + $dest_height = intval(( $height * $max ) / $width); + } + else { + $dest_width = intval(( $width * $max ) / $height); + $dest_height = $max; + } + } + else { + if( $width > $max ) { + $dest_width = $max; + $dest_height = intval(( $height * $max ) / $width); + } + else { + if( $height > $max ) { + + // very tall image (greater than 16:9) + // but width is OK - don't do anything + + if((($height * 9) / 16) > $width) { + $dest_width = $width; + $dest_height = $height; + } + else { + $dest_width = intval(( $width * $max ) / $height); + $dest_height = $max; + } + } + else { + $dest_width = $width; + $dest_height = $height; + } + } + } + $this->doScaleImage($dest_width,$dest_height); + } + + public function scaleImageUp($min) { + if(!$this->is_valid()) + return FALSE; + + + $width = $this->width; + $height = $this->height; + + $dest_width = $dest_height = 0; + + if((! $width)|| (! $height)) + return FALSE; + + if($width < $min && $height < $min) { + if($width > $height) { + $dest_width = $min; + $dest_height = intval(( $height * $min ) / $width); + } + else { + $dest_width = intval(( $width * $min ) / $height); + $dest_height = $min; + } + } + else { + if( $width < $min ) { + $dest_width = $min; + $dest_height = intval(( $height * $min ) / $width); + } + else { + if( $height < $min ) { + $dest_width = intval(( $width * $min ) / $height); + $dest_height = $min; + } + else { + $dest_width = $width; + $dest_height = $height; + } + } + } + $this->doScaleImage($dest_width,$dest_height); + } + + public function scaleImageSquare($dim) { + if(!$this->is_valid()) + return FALSE; + $this->doScaleImage($dim,$dim); + } + + + + + public function orient($filename) { + + /** + * This function is a bit unusual, because it is operating on a file, but you must + * first create an image from that file to initialise the type and check validity + * of the image. + */ + + if(! $this->is_valid()) + return FALSE; + + if((! function_exists('exif_read_data')) || ($this->getType() !== 'image/jpeg')) + return; + + $exif = @exif_read_data($filename); + if($exif) { + $ort = $exif['Orientation']; + + switch($ort) + { + case 1: // nothing + break; + + case 2: // horizontal flip + $this->flip(); + break; + + case 3: // 180 rotate left + $this->rotate(180); + break; + + case 4: // vertical flip + $this->flip(false, true); + break; + + case 5: // vertical flip + 90 rotate right + $this->flip(false, true); + $this->rotate(-90); + break; + + case 6: // 90 rotate right + $this->rotate(-90); + break; + + case 7: // horizontal flip + 90 rotate right + $this->flip(); + $this->rotate(-90); + break; + + case 8: // 90 rotate left + $this->rotate(90); + break; + } + } + } + + + public function save($arr) { + + $p = array(); + + $p['aid'] = ((intval($arr['aid'])) ? intval($arr['aid']) : 0); + $p['uid'] = ((intval($arr['uid'])) ? intval($arr['uid']) : 0); + $p['xchan'] = (($arr['xchan']) ? $arr['xchan'] : ''); + $p['resource_id'] = (($arr['resource_id']) ? $arr['resource_id'] : ''); + $p['filename'] = (($arr['filename']) ? $arr['filename'] : ''); + $p['album'] = (($arr['album']) ? $arr['album'] : ''); + $p['scale'] = ((intval($arr['scale'])) ? intval($arr['scale']) : 0); + $p['photo_flags'] = ((intval($arr['photo_flags'])) ? intval($arr['photo_flags']) : 0); + $p['allow_cid'] = (($arr['allow_cid']) ? $arr['allow_cid'] : ''); + $p['allow_gid'] = (($arr['allow_gid']) ? $arr['allow_gid'] : ''); + $p['deny_cid'] = (($arr['deny_cid']) ? $arr['deny_cid'] : ''); + $p['deny_gid'] = (($arr['deny_gid']) ? $arr['deny_gid'] : ''); + + // temporary until we get rid of photo['profile'] and just use photo['photo_flags'] + // but this will require updating all existing photos in the DB. + + $p['profile'] = (($p['photo_flags'] & PHOTO_PROFILE) ? 1 : 0); + + + $x = q("select id from photo where resource_id = '%s' and uid = %d and xchan = '%s' and `scale` = %d limit 1", + dbesc($p['resource_id']), + intval($p['uid']), + dbesc($p['xchan']), + intval($p['scale']) + ); + if($x) { + $r = q("UPDATE `photo` set + `aid` = %d, + `uid` = %d, + `xchan` = '%s', + `resource_id` = '%s', + `created` = '%s', + `edited` = '%s', + `filename` = '%s', + `type` = '%s', + `album` = '%s', + `height` = %d, + `width` = %d, + `data` = '%s', + `size` = %d, + `scale` = %d, + `profile` = %d, + `photo_flags` = %d, + `allow_cid` = '%s', + `allow_gid` = '%s', + `deny_cid` = '%s', + `deny_gid` = '%s' + where id = %d limit 1", + + intval($p['aid']), + intval($p['uid']), + dbesc($p['xchan']), + dbesc($p['resource_id']), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(basename($p['filename'])), + dbesc($this->getType()), + dbesc($p['album']), + intval($this->getHeight()), + intval($this->getWidth()), + dbesc($this->imageString()), + intval(strlen($this->imageString())), + intval($p['scale']), + intval($p['profile']), + intval($p['photo_flags']), + dbesc($p['allow_cid']), + dbesc($p['allow_gid']), + dbesc($p['deny_cid']), + dbesc($p['deny_gid']), + intval($x[0]['id']) + ); + } + else { + $r = q("INSERT INTO `photo` + ( `aid`, `uid`, `xchan`, `resource_id`, `created`, `edited`, `filename`, type, `album`, `height`, `width`, `data`, `size`, `scale`, `profile`, `photo_flags`, `allow_cid`, `allow_gid`, `deny_cid`, `deny_gid` ) + VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', %d, %d, %d, %d, '%s', '%s', '%s', '%s' )", + intval($p['aid']), + intval($p['uid']), + dbesc($p['xchan']), + dbesc($p['resource_id']), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(basename($p['filename'])), + dbesc($this->getType()), + dbesc($p['album']), + intval($this->getHeight()), + intval($this->getWidth()), + dbesc($this->imageString()), + intval(strlen($this->imageString())), + intval($p['scale']), + intval($p['profile']), + intval($p['photo_flags']), + dbesc($p['allow_cid']), + dbesc($p['allow_gid']), + dbesc($p['deny_cid']), + dbesc($p['deny_gid']) + ); + } + return $r; + } + + public function store($aid, $uid, $xchan, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '') { + + $x = q("select id from photo where `resource_id` = '%s' and uid = %d and `xchan` = '%s' and `scale` = %d limit 1", + dbesc($rid), + intval($uid), + dbesc($xchan), + intval($scale) + ); + if(count($x)) { + $r = q("UPDATE `photo` + set `aid` = %d, + `uid` = %d, + `xchan` = '%s', + `resource_id` = '%s', + `created` = '%s', + `edited` = '%s', + `filename` = '%s', + `type` = '%s', + `album` = '%s', + `height` = %d, + `width` = %d, + `data` = '%s', + `size` = %d, + `scale` = %d, + `profile` = %d, + `allow_cid` = '%s', + `allow_gid` = '%s', + `deny_cid` = '%s', + `deny_gid` = '%s' + where id = %d limit 1", + + intval($aid), + intval($uid), + dbesc($xchan), + dbesc($rid), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(basename($filename)), + dbesc($this->getType()), + dbesc($album), + intval($this->getHeight()), + intval($this->getWidth()), + dbesc($this->imageString()), + intval(strlen($this->imageString())), + intval($scale), + intval($profile), + dbesc($allow_cid), + dbesc($allow_gid), + dbesc($deny_cid), + dbesc($deny_gid), + intval($x[0]['id']) + ); + } + else { + $r = q("INSERT INTO `photo` + ( `aid`, `uid`, `xchan`, `resource_id`, `created`, `edited`, `filename`, type, `album`, `height`, `width`, `data`, `size`, `scale`, `profile`, `allow_cid`, `allow_gid`, `deny_cid`, `deny_gid` ) + VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', %d, %d, %d, '%s', '%s', '%s', '%s' )", + intval($aid), + intval($uid), + dbesc($xchan), + dbesc($rid), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(basename($filename)), + dbesc($this->getType()), + dbesc($album), + intval($this->getHeight()), + intval($this->getWidth()), + dbesc($this->imageString()), + intval(strlen($this->imageString())), + intval($scale), + intval($profile), + dbesc($allow_cid), + dbesc($allow_gid), + dbesc($deny_cid), + dbesc($deny_gid) + ); + } + return $r; + } + +} + + + + + + + + +/** + * Guess image mimetype from filename or from Content-Type header + * + * @arg $filename string Image filename + * @arg $fromcurl boolean Check Content-Type header from curl request + */ + +function guess_image_type($filename, $headers = '') { + logger('Photo: guess_image_type: '.$filename . ($fromcurl?' from curl headers':''), LOGGER_DEBUG); + $type = null; + if ($headers) { + $a = get_app(); + $hdrs=array(); + $h = explode("\n",$headers); + foreach ($h as $l) { + list($k,$v) = array_map("trim", explode(":", trim($l), 2)); + $hdrs[$k] = $v; + } + if (array_key_exists('Content-Type', $hdrs)) + $type = $hdrs['Content-Type']; + } + if (is_null($type)){ +// FIXME!!!! + // Guessing from extension? Isn't that... dangerous? + if(class_exists('Imagick') && file_exists($filename) && is_readable($filename)) { + /** + * Well, this not much better, + * but at least it comes from the data inside the image, + * we won't be tricked by a manipulated extension + */ + $image = new Imagick($filename); + $type = $image->getImageMimeType(); + } else { + $ext = pathinfo($filename, PATHINFO_EXTENSION); + $ph = photo_factory(''); + $types = $ph->supportedTypes(); + $type = "image/jpeg"; + foreach ($types as $m=>$e){ + if ($ext==$e) $type = $m; + } + } + } + logger('Photo: guess_image_type: type='.$type, LOGGER_DEBUG); + return $type; + +} + +function import_profile_photo($photo,$xchan,$thing = false) { + + $a = get_app(); + + $flags = (($thing) ? PHOTO_THING : PHOTO_XCHAN); + $album = (($thing) ? 'Things' : 'Contact Photos'); + + logger('import_profile_photo: updating channel photo from ' . $photo . ' for ' . $xchan, LOGGER_DEBUG); + + if($thing) + $hash = photo_new_resource(); + else { + $r = q("select resource_id from photo where xchan = '%s' and (photo_flags & %d ) and scale = 4 limit 1", + dbesc($xchan), + intval(PHOTO_XCHAN) + ); + if($r) { + $hash = $r[0]['resource_id']; + } + else { + $hash = photo_new_resource(); + } + } + + $photo_failure = false; + $img_str = ''; + + if($photo) { + $filename = basename($photo); + $type = guess_image_type($photo,true); + + if(! $type) + $type = 'image/jpeg'; + + $result = z_fetch_url($photo,true); + + if($result['success']) + $img_str = $result['body']; + } + + $img = photo_factory($img_str, $type); + if($img->is_valid()) { + $width = $img->getWidth(); + $height = $img->getHeight(); + + if($width && $height) { + if(($width / $height) > 1.2) { + // crop out the sides + $margin = $width - $height; + $img->cropImage(175,($margin / 2),0,$height,$height); + } + elseif(($height / $width) > 1.2) { + // crop out the bottom + $margin = $height - $width; + $img->cropImage(175,0,0,$width,$width); + + } + else { + $img->scaleImageSquare(175); + } + + } + else + $photo_failure = true; + + $p = array('xchan' => $xchan,'resource_id' => $hash, 'filename' => basename($photo), 'album' => $album, 'photo_flags' => $flags, 'scale' => 4); + + $r = $img->save($p); + + if($r === false) + $photo_failure = true; + + $img->scaleImage(80); + $p['scale'] = 5; + + $r = $img->save($p); + + if($r === false) + $photo_failure = true; + + $img->scaleImage(48); + $p['scale'] = 6; + + $r = $img->save($p); + + if($r === false) + $photo_failure = true; + + $photo = $a->get_baseurl() . '/photo/' . $hash . '-4'; + $thumb = $a->get_baseurl() . '/photo/' . $hash . '-5'; + $micro = $a->get_baseurl() . '/photo/' . $hash . '-6'; + } + else { + logger('import_profile_photo: invalid image from ' . $photo); + $photo_failure = true; + } + if($photo_failure) { + $photo = $a->get_baseurl() . '/' . get_default_profile_photo(); + $thumb = $a->get_baseurl() . '/' . get_default_profile_photo(80); + $micro = $a->get_baseurl() . '/' . get_default_profile_photo(48); + $type = 'image/jpeg'; + } + + return(array($photo,$thumb,$micro,$type,$photo_failure)); + +} + + + +function import_channel_photo($photo,$type,$aid,$uid) { + + $a = get_app(); + + logger('import_channel_photo: importing channel photo for ' . $uid, LOGGER_DEBUG); + + $hash = photo_new_resource(); + + $photo_failure = false; + + + $filename = $hash; + + $img = photo_factory($photo, $type); + if($img->is_valid()) { + + $img->scaleImageSquare(175); + + $p = array('aid' => $aid, 'uid' => $uid, 'resource_id' => $hash, 'filename' => $filename, 'album' => t('Profile Photos'), 'photo_flags' => PHOTO_PROFILE, 'scale' => 4); + + $r = $img->save($p); + + if($r === false) + $photo_failure = true; + + $img->scaleImage(80); + $p['scale'] = 5; + + $r = $img->save($p); + + if($r === false) + $photo_failure = true; + + $img->scaleImage(48); + $p['scale'] = 6; + + $r = $img->save($p); + + if($r === false) + $photo_failure = true; + + } + else { + logger('import_channel_photo: invalid image.'); + $photo_failure = true; + } + + return(($photo_failure)? false : true); + +} diff --git a/include/photo/photo_gd.php b/include/photo/photo_gd.php new file mode 100644 index 000000000..466f8c23a --- /dev/null +++ b/include/photo/photo_gd.php @@ -0,0 +1,140 @@ +<?php /** @file */ + + +require_once('include/photo/photo_driver.php'); + + +class photo_gd extends photo_driver { + + function supportedTypes() { + $t = array(); + $t['image/jpeg'] ='jpg'; + if (imagetypes() & IMG_PNG) $t['image/png'] = 'png'; + + return $t; + + } + + function load($data, $type) { + $this->valid = false; + if(! $data) + return; + + $this->image = @imagecreatefromstring($data); + if($this->image !== FALSE) { + $this->valid = true; + $this->setDimensions(); + imagealphablending($this->image, false); + imagesavealpha($this->image, true); + } + } + + function setDimensions() { + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + } + + + public function destroy() { + if($this->is_valid()) { + imagedestroy($this->image); + } + } + + public function getImage() { + if(!$this->is_valid()) + return FALSE; + + return $this->image; + } + + public function doScaleImage($dest_width,$dest_height) { + + $dest = imagecreatetruecolor( $dest_width, $dest_height ); + $width = imagesx($this->image); + $height = imagesy($this->image); + + imagealphablending($dest, false); + imagesavealpha($dest, true); + if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha + imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height); + if($this->image) + imagedestroy($this->image); + $this->image = $dest; + $this->setDimensions(); + } + + public function rotate($degrees) { + if(!$this->is_valid()) + return FALSE; + + $this->image = imagerotate($this->image,$degrees,0); + $this->setDimensions(); + } + + public function flip($horiz = true, $vert = false) { + if(!$this->is_valid()) + return FALSE; + + $w = imagesx($this->image); + $h = imagesy($this->image); + $flipped = imagecreate($w, $h); + if($horiz) { + for ($x = 0; $x < $w; $x++) { + imagecopy($flipped, $this->image, $x, 0, $w - $x - 1, 0, 1, $h); + } + } + if($vert) { + for ($y = 0; $y < $h; $y++) { + imagecopy($flipped, $this->image, 0, $y, 0, $h - $y - 1, $w, 1); + } + } + $this->image = $flipped; + $this->setDimensions(); // Shouldn't really be necessary + } + + public function cropImage($max,$x,$y,$w,$h) { + if(!$this->is_valid()) + return FALSE; + + $dest = imagecreatetruecolor( $max, $max ); + imagealphablending($dest, false); + imagesavealpha($dest, true); + if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha + imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $max, $max, $w, $h); + if($this->image) + imagedestroy($this->image); + $this->image = $dest; + $this->setDimensions(); + } + + public function imageString() { + if(!$this->is_valid()) + return FALSE; + + $quality = FALSE; + + ob_start(); + + switch($this->getType()){ + case "image/png": + $quality = get_config('system','png_quality'); + if((! $quality) || ($quality > 9)) + $quality = PNG_QUALITY; + imagepng($this->image,NULL, $quality); + break; + case "image/jpeg": + default: + $quality = get_config('system','jpeg_quality'); + if((! $quality) || ($quality > 100)) + $quality = JPEG_QUALITY; + imagejpeg($this->image,NULL,$quality); + break; + } + $string = ob_get_contents(); + ob_end_clean(); + + return $string; + } + +}
\ No newline at end of file diff --git a/include/photo/photo_imagick.php b/include/photo/photo_imagick.php new file mode 100644 index 000000000..3f84fd06c --- /dev/null +++ b/include/photo/photo_imagick.php @@ -0,0 +1,177 @@ +<?php /** @file */ + + +require_once('include/photo/photo_driver.php'); + + +class photo_imagick extends photo_driver { + + + function supportedTypes() { + return array( + 'image/jpeg' => 'jpg', + 'image/png' => 'png', + 'image/gif' => 'gif' + ); + } + + public function get_FormatsMap() { + return array( + 'image/jpeg' => 'JPG', + 'image/png' => 'PNG', + 'image/gif' => 'GIF' + ); + } + + + function load($data, $type) { + $this->valid = false; + $this->image = new Imagick(); + + if(! $data) + return; + + $this->image->readImageBlob($data); + + + /** + * Setup the image to the format it will be saved to + */ + + $map = $this->get_FormatsMap(); + $format = $map[$type]; + + if($this->image) { + $this->image->setFormat($format); + + // Always coalesce, if it is not a multi-frame image it won't hurt anyway + $this->image = $this->image->coalesceImages(); + + + $this->valid = true; + $this->setDimensions(); + + /** + * setup the compression here, so we'll do it only once + */ + switch($this->getType()) { + case "image/png": + $quality = get_config('system','png_quality'); + if((! $quality) || ($quality > 9)) + $quality = PNG_QUALITY; + /** + * From http://www.imagemagick.org/script/command-line-options.php#quality: + * + * 'For the MNG and PNG image formats, the quality value sets + * the zlib compression level (quality / 10) and filter-type (quality % 10). + * The default PNG "quality" is 75, which means compression level 7 with adaptive PNG filtering, + * unless the image has a color map, in which case it means compression level 7 with no PNG filtering' + */ + $quality = $quality * 10; + $this->image->setCompressionQuality($quality); + break; + case "image/jpeg": + $quality = get_config('system','jpeg_quality'); + if((! $quality) || ($quality > 100)) + $quality = JPEG_QUALITY; + $this->image->setCompressionQuality($quality); + default: + break; + + } + } + } + + public function destroy() { + if($this->is_valid()) { + $this->image->clear(); + $this->image->destroy(); + } + } + + + public function setDimensions() { + $this->width = $this->image->getImageWidth(); + $this->height = $this->image->getImageHeight(); + } + + + public function getImage() { + if(!$this->is_valid()) + return FALSE; + + $this->image = $this->image->deconstructImages(); + return $this->image; + } + + public function doScaleImage($dest_width,$dest_height) { + + /** + * If it is not animated, there will be only one iteration here, + * so don't bother checking + */ + // Don't forget to go back to the first frame + $this->image->setFirstIterator(); + do { + $this->image->scaleImage($dest_width, $dest_height); + } while ($this->image->nextImage()); + + $this->setDimensions(); + } + + public function rotate($degrees) { + if(!$this->is_valid()) + return FALSE; + + $this->image->setFirstIterator(); + do { + // ImageMagick rotates in the opposite direction of imagerotate() + $this->image->rotateImage(new ImagickPixel(), -$degrees); + } while ($this->image->nextImage()); + + $this->setDimensions(); + } + + public function flip($horiz = true, $vert = false) { + if(!$this->is_valid()) + return FALSE; + + $this->image->setFirstIterator(); + do { + if($horiz) $this->image->flipImage(); + if($vert) $this->image->flopImage(); + } while ($this->image->nextImage()); + + $this->setDimensions(); // Shouldn't really be necessary + } + + public function cropImage($max,$x,$y,$w,$h) { + if(!$this->is_valid()) + return FALSE; + + $this->image->setFirstIterator(); + do { + $this->image->cropImage($w, $h, $x, $y); + /** + * We need to remove the canvas, + * or the image is not resized to the crop: + * http://php.net/manual/en/imagick.cropimage.php#97232 + */ + $this->image->setImagePage(0, 0, 0, 0); + } while ($this->image->nextImage()); + + $this->doScaleImage($max,$max); + } + + public function imageString() { + if(!$this->is_valid()) + return FALSE; + + /* Clean it */ + $this->image = $this->image->deconstructImages(); + return $this->image->getImagesBlob(); + } + + + +}
\ No newline at end of file diff --git a/include/photos.php b/include/photos.php new file mode 100644 index 000000000..9819c7ef2 --- /dev/null +++ b/include/photos.php @@ -0,0 +1,440 @@ +<?php /** @file */ + +require_once('include/permissions.php'); +require_once('include/items.php'); +require_once('include/photo/photo_driver.php'); + + +function photo_upload($channel, $observer, $args) { + + $ret = array('success' => false); + $channel_id = $channel['channel_id']; + $account_id = $channel['channel_account_id']; + + if(! perm_is_allowed($channel_id, $observer['xchan_hash'], 'post_photos')) { + $ret['message'] = t('Permission denied.'); + return $ret; + } + + call_hooks('photo_upload_begin', $args); + + /** + * Determine the album to use + */ + + $album = $args['album']; + $newalbum = $args['newalbum']; + + logger('photo_upload: album= ' . $album . ' newalbum= ' . $newalbum , LOGGER_DEBUG); + + if(! $album) { + if($newalbum) + $album = $newalbum; + else + $album = datetime_convert('UTC',date_default_timezone_get(),'now', 'Y'); + } + + /** + * + * We create a wall item for every photo, but we don't want to + * overwhelm the data stream with a hundred newly uploaded photos. + * So we will make the first photo uploaded to this album in the last several hours + * visible by default, the rest will become visible over time when and if + * they acquire comments, likes, dislikes, and/or tags + * + */ + + $r = q("SELECT * FROM photo WHERE album = '%s' AND uid = %d AND created > UTC_TIMESTAMP() - INTERVAL 3 HOUR ", + dbesc($album), + intval($channel_id) + ); + if((! $r) || ($album == t('Profile Photos'))) + $visible = 1; + else + $visible = 0; + + if(intval($args['not_visible']) || $args['not_visible'] === 'true') + $visible = 0; + + $str_group_allow = perms2str(((is_array($args['group_allow'])) ? $args['group_allow'] : explode(',',$args['group_allow']))); + $str_contact_allow = perms2str(((is_array($args['contact_allow'])) ? $args['contact_allow'] : explode(',',$args['contact_allow']))); + $str_group_deny = perms2str(((is_array($args['group_deny'])) ? $args['group_deny'] : explode(',',$args['group_deny']))); + $str_contact_deny = perms2str(((is_array($args['contact_deny'])) ? $args['contact_deny'] : explode(',',$args['contact_deny']))); + + $f = array('src' => '', 'filename' => '', 'filesize' => 0, 'type' => ''); + + call_hooks('photo_upload_file',$f); + + if(x($f,'src') && x($f,'filesize')) { + $src = $f['src']; + $filename = $f['filename']; + $filesize = $f['filesize']; + $type = $f['type']; + } + else { + $src = $_FILES['userfile']['tmp_name']; + $filename = basename($_FILES['userfile']['name']); + $filesize = intval($_FILES['userfile']['size']); + $type = $_FILES['userfile']['type']; + } + + if (! $type) + $type=guess_image_type($filename); + + logger('photo_upload: received file: ' . $filename . ' as ' . $src . ' ('. $type . ') ' . $filesize . ' bytes', LOGGER_DEBUG); + + $maximagesize = get_config('system','maximagesize'); + + if(($maximagesize) && ($filesize > $maximagesize)) { + $ret['message'] = sprintf ( t('Image exceeds website size limit of %lu bytes'), $maximagesize); + @unlink($src); + call_hooks('photo_upload_end',$ret); + return $ret; + } + + if(! $filesize) { + $ret['message'] = t('Image file is empty.'); + @unlink($src); + call_hooks('photo_post_end',$ret); + return $ret; + } + + logger('photo_upload: loading the contents of ' . $src , LOGGER_DEBUG); + + $imagedata = @file_get_contents($src); + + $r = q("select sum(size) as total from photo where aid = %d and scale = 0 ", + intval($account_id) + ); + + $limit = service_class_fetch($channel_id,'photo_upload_limit'); + + if(($r) && ($limit !== false) && (($r[0]['total'] + strlen($imagedata)) > $limit)) { + $ret['message'] = upgrade_message(); + @unlink($src); + call_hooks('photo_post_end',$ret); + return $ret; + } + + + $ph = photo_factory($imagedata, $type); + + if(! $ph->is_valid()) { + $ret['message'] = t('Unable to process image'); + logger('photo_upload: unable to process image'); + @unlink($src); + call_hooks('photo_upload_end',$ret); + return $ret; + } + + $ph->orient($src); + @unlink($src); + + $max_length = get_config('system','max_image_length'); + if(! $max_length) + $max_length = MAX_IMAGE_LENGTH; + if($max_length > 0) + $ph->scaleImage($max_length); + + $width = $ph->getWidth(); + $height = $ph->getHeight(); + + $smallest = 0; + + $photo_hash = photo_new_resource(); + + $visitor = ''; + if($channel['channel_hash'] !== $observer['xchan_hash']) + $visitor = $observer['xchan_hash']; + + $errors = false; + + $p = array('aid' => $account_id, 'uid' => $channel_id, 'xchan' => $visitor, 'resource_id' => $photo_hash, + 'filename' => $filename, 'album' => $album, 'scale' => 0, 'photo_flags' => PHOTO_NORMAL, + 'allow_cid' => $str_contact_allow, 'allow_gid' => $str_group_allow, + 'deny_cid' => $str_contact_deny, 'deny_gid' => $str_group_deny + ); + + $r1 = $ph->save($p); + if(! $r1) + $errors = true; + + if(($width > 640 || $height > 640) && (! $errors)) { + $ph->scaleImage(640); + $p['scale'] = 1; + $r2 = $ph->save($p); + $smallest = 1; + if(! $r2) + $errors = true; + } + + if(($width > 320 || $height > 320) && (! $errors)) { + $ph->scaleImage(320); + $p['scale'] = 2; + $r3 = $ph->save($p); + $smallest = 2; + if(! $r3) + $errors = true; + } + + + if($errors) { + q("delete from photo where resource_id = '%s' and uid = %d", + dbesc($photo_hash), + intval($channel_id) + ); + $ret['message'] = t('Photo storage failed.'); + logger('photo_upload: photo store failed.'); + call_hooks('photo_upload_end',$ret); + return $ret; + } + + // This will be the width and height of the smallest representation + + $width_x_height = $ph->getWidth() . 'x' . $ph->getHeight(); + + $basename = basename($filename); + $mid = item_message_id(); + + // Create item container + + $item_flags = ITEM_WALL|ITEM_ORIGIN|ITEM_THREAD_TOP; + $item_restrict = (($visible) ? ITEM_VISIBLE : ITEM_HIDDEN); + $title = ''; + $mid = item_message_id(); + + $arr = array(); + + $arr['aid'] = $account_id; + $arr['uid'] = $channel_id; + $arr['mid'] = $mid; + $arr['parent_mid'] = $mid; + $arr['item_flags'] = $item_flags; + $arr['item_restrict'] = $item_restrict; + $arr['resource_type'] = 'photo'; + $arr['resource_id'] = $photo_hash; + $arr['owner_xchan'] = $channel['channel_hash']; + $arr['author_xchan'] = $observer['xchan_hash']; + $arr['title'] = $title; + $arr['allow_cid'] = $str_contact_allow; + $arr['allow_gid'] = $str_group_allow; + $arr['deny_cid'] = $str_contact_deny; + $arr['deny_gid'] = $str_group_deny; + $arr['verb'] = ACTIVITY_POST; + + $arr['plink'] = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . $arr['mid']; + + if ($width_x_height) + $tag = '[zmg=' . $width_x_height. ']'; + else + $tag = '[zmg]'; + + $arr['body'] = '[zrl=' . z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo_hash . ']' + . $tag . z_root() . "/photo/{$photo_hash}-{$smallest}.".$ph->getExt() . '[/zmg]' + . '[/zrl]'; + + $result = item_store($arr); + $item_id = $result['item_id']; + + if($visible) + proc_run('php', "include/notifier.php", 'wall-new', $item_id); + + $ret['success'] = true; + $ret['body'] = $arr['body']; + $ret['resource_id'] = $photo_hash; + $ret['photoitem_id'] = $item_id; + + call_hooks('photo_upload_end',$ret); + + return $ret; +} + + + + +function photos_albums_list($channel,$observer) { + + $channel_id = $channel['channel_id']; + $observer_xchan = (($observer) ? $observer['xchan_hash'] : ''); + + if(! perm_is_allowed($channel_id,$observer_xchan,'view_photos')) + return false; + + // FIXME - create a permissions SQL which works on arbitrary observers and channels, regardless of login or web status + + $sql_extra = permissions_sql($channel_id); + + $albums = q("SELECT count( distinct resource_id ) as total, album from photo where uid = %d and ( photo_flags = %d or photo_flags = %d ) $sql_extra group by album order by created desc", + intval($channel_id), + intval(PHOTO_NORMAL), + intval(PHOTO_PROFILE) + + ); + + // add various encodings to the array so we can just loop through and pick them out in a template + + $ret = array('success' => false); + + if($albums) { + $ret['success'] = true; + $ret['albums'] = array(); + foreach($albums as $k => $album) { + $entry = array( + 'text' => $album['album'], + 'total' => $album['total'], + 'url' => z_root() . '/photos/' . $channel['channel_address'] . '/album/' . bin2hex($album['album']), + 'urlencode' => urlencode($album['album']), + 'bin2hex' => bin2hex($album['album'])); + $ret['albums'][] = $entry; + } + } + return $ret; + +} + +function photos_album_widget($channelx,$observer,$albums = null) { + + $o = ''; + + // If we weren't passed an album list, see if the photos module + // dropped one for us to find in $a->data['albums']. + // If all else fails, load it. + + if(! $albums) { + if(array_key_exists('albums', get_app()->data)) + $albums = get_app()->data['albums']; + else + $albums = photos_albums_list($channelx,$observer); + } + + if($albums['success']) { + $o = replace_macros(get_markup_template('photo_albums.tpl'),array( + '$nick' => $channelx['channel_address'], + '$title' => t('Photo Albums'), + '$albums' => $albums['albums'], + '$baseurl' => z_root(), + '$upload' => ((perm_is_allowed($channelx['channel_id'],(($observer) ? $observer['xchan_hash'] : ''),'post_photos')) + ? t('Upload New Photos') : '') + )); + } + return $o; +} + + +function photos_list_photos($channel,$observer,$album = '') { + + $channel_id = $channel['channel_id']; + $observer_xchan = (($observer) ? $observer['xchan_hash'] : ''); + + if(! perm_is_allowed($channel_id,$observer_xchan,'view_photos')) + return false; + + $sql_extra = permissions_sql($channel_id); + + if($album) + $sql_extra .= " and album = '" . protect_sprintf(dbesc($album)) . "' "; + + $ret = array('success' => false); + + $r = q("select resource_id, created, edited, title, description, album, filename, type, height, width, size, scale, profile, photo_flags, allow_cid, allow_gid, deny_cid, deny_gid from photo where uid = %d and ( photo_flags = %d or photo_flags = %d ) $sql_extra ", + intval($channel_id), + intval(PHOTO_NORMAL), + intval(PHOTO_PROFILE) + ); + + if($r) { + for($x = 0; $x < count($r); $x ++) { + $r[$x]['src'] = z_root() . '/photo/' . $r[$x]['resource_id'] . '-' . $r[$x]['scale']; + } + $ret['success'] = true; + $ret['photos'] = $r; + } + + return $ret; +} + + + +function photos_album_exists($channel_id,$album) { + $r = q("SELECT id from photo where album = '%s' and uid = %d limit 1", + dbesc($album), + intval($channel_id) + ); + return (($r) ? true : false); +} + +function photos_album_rename($channel_id,$oldname,$newname) { + return q("UPDATE photo SET album = '%s' WHERE album = '%s' AND uid = %d", + dbesc($newname), + dbesc($oldname), + intval($channel_id) + ); +} + + +function photos_album_get_db_idstr($channel_id,$album,$remote_xchan = '') { + + if($remote_xchan) { + $r = q("SELECT distinct resource_id as from photo where xchan = '%s' and uid = %d and album = '%s' ", + dbesc($remote_xchan), + intval($channel_id), + dbesc($album) + ); + } + else { + $r = q("SELECT distinct resource_id from photo where uid = %d and album = '%s' ", + intval($channel_id), + dbesc($album) + ); + } + if($r) { + $arr = array(); + foreach($r as $rr) { + $arr[] = "'" . dbesc($rr['resource_id']) . "'" ; + } + $str = implode(',',$arr); + return $str; + } + return false; + +} + +function photos_create_item($channel, $creator_hash, $photo, $visible = false) { + + // Create item container + + $item_flags = ITEM_WALL|ITEM_ORIGIN|ITEM_THREAD_TOP; + $item_restrict = (($visible) ? ITEM_HIDDEN : ITEM_VISIBLE); + + $title = ''; + $mid = item_message_id(); + + $arr = array(); + + $arr['aid'] = $channel['channel_account_id']; + $arr['uid'] = $channel['channel_id']; + $arr['mid'] = $mid; + $arr['parent_mid'] = $mid; + $arr['item_flags'] = $item_flags; + $arr['item_restrict'] = $item_restrict; + $arr['resource_type'] = 'photo'; + $arr['resource_id'] = $photo['resource_id']; + $arr['owner_xchan'] = $channel['channel_hash']; + $arr['author_xchan'] = $creator_hash; + + $arr['allow_cid'] = $photo['allow_cid']; + $arr['allow_gid'] = $photo['allow_gid']; + $arr['deny_cid'] = $photo['deny_cid']; + $arr['deny_gid'] = $photo['deny_gid']; + + $arr['plink'] = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . $arr['mid']; + + $arr['body'] = '[zrl=' . z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo['resource_id'] . ']' + . '[zmg]' . z_root() . '/photo/' . $photo['resource_id'] . '-' . $photo['scale'] . '[/zmg]' + . '[/zrl]'; + + $result = item_store($arr); + $item_id = $result['item_id']; + return $item_id; + +} diff --git a/include/plugin.php b/include/plugin.php index 6a35a6187..5c425ac58 100644..100755 --- a/include/plugin.php +++ b/include/plugin.php @@ -1,25 +1,43 @@ -<?php +<?php /** @file */ +require_once("include/friendica_smarty.php"); // install and uninstall plugin -if (! function_exists('uninstall_plugin')){ -function uninstall_plugin($plugin){ - logger("Addons: uninstalling " . $plugin, LOGGER_DEBUG); - q("DELETE FROM `addon` WHERE `name` = '%s' ", - dbesc($plugin) - ); + +function unload_plugin($plugin){ + logger("Addons: unloading " . $plugin, LOGGER_DEBUG); @include_once('addon/' . $plugin . '/' . $plugin . '.php'); + if(function_exists($plugin . '_unload')) { + $func = $plugin . '_unload'; + $func(); + } +} + + + +function uninstall_plugin($plugin) { + + unload_plugin($plugin); + + if(! file_exists('addon/' . $plugin . '/' . $plugin . '.php')) + return false; + + logger("Addons: uninstalling " . $plugin); + $t = @filemtime('addon/' . $plugin . '/' . $plugin . '.php'); + @include_once('addon/' . $plugin . '/' . $plugin . '.php'); if(function_exists($plugin . '_uninstall')) { $func = $plugin . '_uninstall'; $func(); } -}} -if (! function_exists('install_plugin')){ -function install_plugin($plugin) { - // silently fail if plugin was removed + q("DELETE FROM `addon` WHERE `name` = '%s' ", + dbesc($plugin) + ); +} + +function install_plugin($plugin) { if(! file_exists('addon/' . $plugin . '/' . $plugin . '.php')) return false; @@ -29,14 +47,34 @@ function install_plugin($plugin) { if(function_exists($plugin . '_install')) { $func = $plugin . '_install'; $func(); + } + + $plugin_admin = (function_exists($plugin . "_plugin_admin") ? 1 : 0); - $plugin_admin = (function_exists($plugin . "_plugin_admin") ? 1 : 0); + $r = q("INSERT INTO `addon` (`name`, `installed`, `timestamp`, `plugin_admin`) VALUES ( '%s', 1, %d , %d ) ", + dbesc($plugin), + intval($t), + $plugin_admin + ); + + load_plugin($plugin); + +} + + +function load_plugin($plugin) { + // silently fail if plugin was removed + + if(! file_exists('addon/' . $plugin . '/' . $plugin . '.php')) + return false; + + logger("Addons: loading " . $plugin); + $t = @filemtime('addon/' . $plugin . '/' . $plugin . '.php'); + @include_once('addon/' . $plugin . '/' . $plugin . '.php'); + if(function_exists($plugin . '_load')) { + $func = $plugin . '_load'; + $func(); - $r = q("INSERT INTO `addon` (`name`, `installed`, `timestamp`, `plugin_admin`) VALUES ( '%s', 1, %d , %d ) ", - dbesc($plugin), - intval($t), - $plugin_admin - ); // we can add the following with the previous SQL // once most site tables have been updated. @@ -50,15 +88,25 @@ function install_plugin($plugin) { return true; } else { - logger("Addons: FAILED installing " . $plugin); + logger("Addons: FAILED loading " . $plugin); return false; } -}} +} + +function plugin_is_installed($name) { + $r = q("select name from addon where name = '%s' and installed = 1 limit 1", + dbesc($name) + ); + if($r) + return true; + return false; +} + + // reload all updated plugins -if(! function_exists('reload_plugins')) { function reload_plugins() { $plugins = get_config('system','addon'); if(strlen($plugins)) { @@ -85,12 +133,12 @@ function reload_plugins() { logger('Reloading plugin: ' . $i['name']); @include_once($fname); - if(function_exists($pl . '_uninstall')) { - $func = $pl . '_uninstall'; + if(function_exists($pl . '_unload')) { + $func = $pl . '_unload'; $func(); } - if(function_exists($pl . '_install')) { - $func = $pl . '_install'; + if(function_exists($pl . '_load')) { + $func = $pl . '_load'; $func(); } q("UPDATE `addon` SET `timestamp` = %d WHERE `id` = %d LIMIT 1", @@ -103,14 +151,13 @@ function reload_plugins() { } } } - -}} +} -if(! function_exists('register_hook')) { + function register_hook($hook,$file,$function,$priority=0) { $r = q("SELECT * FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1", @@ -128,47 +175,82 @@ function register_hook($hook,$file,$function,$priority=0) { dbesc($priority) ); return $r; -}} +} + -if(! function_exists('unregister_hook')) { function unregister_hook($hook,$file,$function) { - $r = q("DELETE FROM `hook` WHERE `hook` = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1", + $r = q("DELETE FROM hook WHERE hook = '%s' AND `file` = '%s' AND `function` = '%s' LIMIT 1", dbesc($hook), dbesc($file), dbesc($function) ); return $r; -}} +} // // It might not be obvious but themes can manually add hooks to the $a->hooks // array in their theme_init() and use this to customise the app behaviour. +// UPDATE: use insert_hook($hookname,$function_name) to do this // -if(! function_exists('load_hooks')) { + function load_hooks() { $a = get_app(); - $a->hooks = array(); - $r = q("SELECT * FROM `hook` WHERE 1 ORDER BY `priority` DESC"); - if(count($r)) { +// if(! is_array($a->hooks)) + $a->hooks = array(); + $r = q("SELECT * FROM hook WHERE true ORDER BY priority DESC"); + if($r) { foreach($r as $rr) { if(! array_key_exists($rr['hook'],$a->hooks)) $a->hooks[$rr['hook']] = array(); $a->hooks[$rr['hook']][] = array($rr['file'],$rr['function']); } } -}} +//logger('hooks: ' . print_r($a->hooks,true)); + +} + +/** + * + * @function insert_hook($hook,$fn) + * + * Insert a short-lived hook into the running page request. + * Hooks are normally persistent so that they can be called + * across asynchronous processes such as delivery and poll + * processes. + * + * insert_hook lets you attach a hook callback immediately + * which will not persist beyond the life of this page request + * or the current process. + * + * @param string $hook; + * name of hook to attach callback + * @param string $fn; + * function name of callback handler + * + */ + +function insert_hook($hook,$fn) { + $a = get_app(); + if(! is_array($a->hooks)) + $a->hooks = array(); + if(! array_key_exists($hook,$a->hooks)) + $a->hooks[$hook] = array(); + $a->hooks[$hook][] = array('',$fn); +} + + -if(! function_exists('call_hooks')) { function call_hooks($name, &$data = null) { $a = get_app(); if((is_array($a->hooks)) && (array_key_exists($name,$a->hooks))) { foreach($a->hooks[$name] as $hook) { - @include_once($hook[0]); + if($hook[0]) + @include_once($hook[0]); if(function_exists($hook[1])) { $func = $hook[1]; $func($a,$data); @@ -184,7 +266,7 @@ function call_hooks($name, &$data = null) { } } -}} +} /* @@ -200,7 +282,7 @@ function call_hooks($name, &$data = null) { * * */ -if (! function_exists('get_plugin_info')){ + function get_plugin_info($plugin){ $info=Array( 'name' => $plugin, @@ -240,7 +322,7 @@ function get_plugin_info($plugin){ } return $info; -}} +} /* @@ -256,16 +338,16 @@ function get_plugin_info($plugin){ * * */ -if (! function_exists('get_theme_info')){ + function get_theme_info($theme){ $info=Array( 'name' => $theme, 'description' => "", 'author' => array(), - 'maintainer' => array(), 'version' => "", - 'credits' => "", 'compat' => "", + 'credits' => "", + 'maintainer' => array(), 'experimental' => false, 'unsupported' => false ); @@ -315,7 +397,7 @@ function get_theme_info($theme){ } return $info; -}} +} function get_theme_screenshot($theme) { @@ -338,12 +420,14 @@ function get_theme_screenshot($theme) { function service_class_allows($uid,$property,$usage = false) { - + $a = get_app(); if($uid == local_user()) { - $service_class = $a->user['service_class']; + $service_class = $a->account['account_service_class']; } else { - $r = q("select service_class from user where uid = %d limit 1", + $r = q("select account_service_class as service_class + from channel c, account a + where c.channel_account_id=a.account_id and c.channel_id= %d limit 1", intval($uid) ); if($r !== false and count($r)) { @@ -368,13 +452,15 @@ function service_class_allows($uid,$property,$usage = false) { function service_class_fetch($uid,$property) { - + $a = get_app(); if($uid == local_user()) { - $service_class = $a->user['service_class']; + $service_class = $a->account['account_service_class']; } else { - $r = q("select service_class from user where uid = %d limit 1", - intval($uid) + $r = q("select account_service_class as service_class + from channel c, account a + where c.channel_account_id=a.account_id and c.channel_id= %d limit 1", + intval($uid) ); if($r !== false and count($r)) { $service_class = $r[0]['service_class']; @@ -384,6 +470,7 @@ function service_class_fetch($uid,$property) { return false; // everything is allowed $arr = get_config('service_class',$service_class); + if(! is_array($arr) || (! count($arr))) return false; @@ -396,7 +483,7 @@ function upgrade_link($bbcode = false) { if(! $l) return ''; if($bbcode) - $t = sprintf('[url=%s]' . t('Click here to upgrade.') . '[/url]', $l); + $t = sprintf('[zrl=%s]' . t('Click here to upgrade.') . '[/zrl]', $l); else $t = sprintf('<a href="%s">' . t('Click here to upgrade.') . '</div>', $l); return $t; @@ -418,6 +505,15 @@ function head_add_css($src,$media = 'screen') { get_app()->css_sources[] = array($src,$media); } + +function head_remove_css($src,$media = 'screen') { + $a = get_app(); + $index = array_search(array($src,$media),$a->css_sources); + if($index !== false) + unset($a->css_sources[$index]); + +} + function head_get_css() { $str = ''; $sources = get_app()->css_sources; @@ -435,24 +531,67 @@ function format_css_if_exists($source) { $path = theme_include($source[0]); if($path) - return '<link rel="stylesheet" href="' . z_root() . '/' . $path . '" type="text/css" media="' . $source[1] . '" />' . "\r\n"; + return '<link rel="stylesheet" href="' . script_path() . '/' . $path . '" type="text/css" media="' . $source[1] . '" />' . "\r\n"; } +function script_path() { + if(x($_SERVER,'HTTPS') && $_SERVER['HTTPS']) + $scheme = 'https'; + elseif(x($_SERVER,'SERVER_PORT') && (intval($_SERVER['SERVER_PORT']) == 443)) + $scheme = 'https'; + else + $scheme = 'http'; + + if(x($_SERVER,'SERVER_NAME')) { + $hostname = $_SERVER['SERVER_NAME']; + } + else { + return z_root(); + } + + if(x($_SERVER,'SERVER_PORT') && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) { + $hostname .= ':' . $_SERVER['SERVER_PORT']; + } + + return $scheme . '://' . $hostname; +} function head_add_js($src) { get_app()->js_sources[] = $src; } +function head_remove_js($src) { + $a = get_app(); + $index = array_search($src,$a->js_sources); + if($index !== false) + unset($a->js_sources[$index]); + +} + function head_get_js() { $str = ''; $sources = get_app()->js_sources; if(count($sources)) - foreach($sources as $source) + foreach($sources as $source) { + if($source === 'main.js') + continue; $str .= format_js_if_exists($source); + } return $str; } +function head_get_main_js() { + $str = ''; + $sources = array('main.js'); + if(count($sources)) + foreach($sources as $source) + $str .= format_js_if_exists($source,true); + return $str; +} + + + function format_js_if_exists($source) { if(strpos($source,'/') !== false) @@ -460,68 +599,63 @@ function format_js_if_exists($source) { else $path = theme_include($source); if($path) - return '<script src="' . z_root() . '/' . $path . '" ></script>' . "\r\n" ; + return '<script src="' . script_path() . '/' . $path . '" ></script>' . "\r\n" ; } -function theme_include($file) { +function theme_include($file, $root = '') { - global $t; // use builtin template processor + $a = get_app(); - $paths = array( - 'view/theme/$theme/$ext/$file', - 'view/theme/$theme/$file', - 'view/theme/$parent/$ext/$file', - 'view/theme/$parent/$file', - 'view/$ext/$file', - 'view/$file' - ); + // Make sure $root ends with a slash / if it's not blank + if($root !== '' && $root[strlen($root)-1] !== '/') + $root = $root . '/'; - $theme_info = get_app()->theme_info; + $theme_info = $a->theme_info; if(array_key_exists('extends',$theme_info)) $parent = $theme_info['extends']; else $parent = 'NOPATH'; + $theme = current_theme(); + + $ext = substr($file,strrpos($file,'.')+1); + + $paths = array( + "{$root}view/theme/$theme/$ext/$file", + "{$root}view/theme/$parent/$ext/$file", + "{$root}view/$ext/$file", + ); + foreach($paths as $p) { - $f = $t->replace($p,array( - '$theme' => current_theme(), - '$ext' => substr($file,strrpos($file,'.')+1), - '$parent' => $parent, - '$file' => $file - )); - if(strstr($f,'NOPATH')) + // strpos() is faster than strstr when checking if one string is in another (http://php.net/manual/en/function.strstr.php) + if(strpos($p,'NOPATH') !== false) continue; - if(file_exists($f)) - return $f; + if(file_exists($p)) + return $p; } return ''; } -if(! function_exists('get_intltext_template')) { -function get_intltext_template($s) { - global $a; - if(! isset($a->language)) - $a->language = 'en'; +function get_intltext_template($s, $root = '') { + $a = get_app(); + $t = $a->template_engine(); - if(file_exists("view/{$a->language}/$s")) - return file_get_contents("view/{$a->language}/$s"); - elseif(file_exists("view/en/$s")) - return file_get_contents("view/en/$s"); - else - return file_get_contents("view/$s"); -}} + $template = $t->get_intltext_template($s, $root); + return $template; -if(! function_exists('get_markup_template')) { -function get_markup_template($s) { +} - $x = theme_include($s); - if($x) - return file_get_contents($x); -}} + +function get_markup_template($s, $root = '') { + $a = get_app(); + $t = $a->template_engine(); + $template = $t->get_markup_template($s, $root); + return $template; +} diff --git a/include/poller.php b/include/poller.php index 00e336453..ec013c9c7 100644 --- a/include/poller.php +++ b/include/poller.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ require_once('boot.php'); require_once('include/cli_startup.php'); @@ -21,23 +21,66 @@ function poller_run($argv, $argc){ } } + $interval = intval(get_config('system','poll_interval')); + if(! $interval) + $interval = ((get_config('system','delivery_interval') === false) ? 3 : intval(get_config('system','delivery_interval'))); + + logger('poller: start'); // run queue delivery process in the background proc_run('php',"include/queue.php"); - // expire any expired accounts - - q("UPDATE account - SET account_flags = account_flags | %d - where not account_flags & %d - and account_expires != '0000-00-00 00:00:00' - and account_expires < UTC_TIMESTAMP() ", - intval(ACCOUNT_EXPIRED), - intval(ACCOUNT_EXPIRED) + + // expire any expired mail + + q("delete from mail where expires != '0000-00-00 00:00:00' and expires < UTC_TIMESTAMP() "); + + // expire any expired items + + $r = q("select id from item where expires != '0000-00-00 00:00:00' and expires < UTC_TIMESTAMP() + and not ( item_restrict & %d ) ", + intval(ITEM_DELETED) + ); + if($r) { + require_once('include/items.php'); + foreach($r as $rr) + drop_item($rr['id'],false); + } + + + // Ensure that every channel pings a directory server once a month. This way we can discover + // channels and sites that quietly vanished and prevent the directory from accumulating stale + // or dead entries. + + $r = q("select channel_id from channel where channel_dirdate < UTC_TIMESTAMP() - INTERVAL 30 DAY"); + if($r) { + foreach($r as $rr) { + proc_run('php','include/directory.php',$rr['channel_id'],'force'); + if($interval) + @time_sleep_until(microtime(true) + (float) $interval); + } + } + + // publish any applicable items that were set to be published in the future + // (time travel posts) + + $r = q("select id from item where ( item_restrict & %d ) and created <= UTC_TIMESTAMP() ", + intval(ITEM_DELAYED_PUBLISH) ); - + if($r) { + foreach($r as $rr) { + $x = q("update item set item_restrict = ( item_restrict ^ %d ) where id = %d limit 1", + intval(ITEM_DELAYED_PUBLISH), + intval($rr['id']) + ); + if($x) { + proc_run('php','include/notifier.php','wall-new',$rr['id']); + } + } + } + $abandon_days = intval(get_config('system','account_abandon_days')); if($abandon_days < 1) $abandon_days = 0; @@ -45,17 +88,101 @@ function poller_run($argv, $argc){ // once daily run birthday_updates and then expire in background + // FIXME: add birthday updates, both locally and for xprof for use + // by directory servers + $d1 = get_config('system','last_expire_day'); $d2 = intval(datetime_convert('UTC','UTC','now','d')); + $dirmode = get_config('system','directory_mode'); + + /** + * Cron Daily + * + * Actions in the following block are executed once per day, not on every poller run + * + */ + if($d2 != intval($d1)) { -// update_suggestions(); + call_hooks('cron_daily',datetime_convert()); + + + $d3 = intval(datetime_convert('UTC','UTC','now','N')); + if($d3 == 7) { + + /** + * Cron Weekly + * + * Actions in the following block are executed once per day only on Sunday (once per week). + * + */ + + + call_hooks('cron_weekly',datetime_convert()); + + + + require_once('include/hubloc.php'); + prune_hub_reinstalls(); + + require_once('include/Contact.php'); + mark_orphan_hubsxchans(); + + } + + update_birthdays(); + + // expire any read notifications over a month old + + q("delete from notify where seen = 1 and date < UTC_TIMESTAMP() - INTERVAL 30 DAY"); + + // expire any expired accounts + require_once('include/account.php'); + downgrade_accounts(); + + // If this is a directory server, request a sync with an upstream + // directory at least once a day, up to once every poll interval. + // Pull remote changes and push local changes. + // potential issue: how do we keep from creating an endless update loop? + + if($dirmode == DIRECTORY_MODE_SECONDARY || $dirmode == DIRECTORY_MODE_PRIMARY) { + require_once('include/dir_fns.php'); + sync_directories($dirmode); + } set_config('system','last_expire_day',$d2); + proc_run('php','include/expire.php'); + proc_run('php','include/cli_suggest.php'); + } + // update any photos which didn't get imported properly + // This should be rare + + $r = q("select xchan_photo_l, xchan_hash from xchan where xchan_photo_l != '' and xchan_photo_m = '' + and xchan_photo_date < UTC_TIMESTAMP() - INTERVAL 1 DAY"); + if($r) { + require_once('include/photo/photo_driver.php'); + foreach($r as $rr) { + $photos = import_profile_photo($rr['xchan_photo_l'],$rr['xchan_hash']); + $x = q("update xchan set xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' + where xchan_hash = '%s' limit 1", + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc($photos[3]), + dbesc($rr['xchan_hash']) + ); + } + } + + + // pull in some public posts + if(! get_config('system','disable_discover_tab')) + proc_run('php','include/externals.php'); + $manual_id = 0; $generation = 0; @@ -63,24 +190,21 @@ function poller_run($argv, $argc){ $force = false; $restart = false; - if((argc() > 1) && (argv(1) == 'force')) + if(($argc > 1) && ($argv[1] == 'force')) $force = true; - if((argc() > 1) && (argv(1) == 'restart')) { + if(($argc > 1) && ($argv[1] == 'restart')) { $restart = true; - $generation = intval(argv(2)); + $generation = intval($argv[2]); if(! $generation) killme(); } - if((argc() > 1) && intval(argv(1))) { - $manual_id = intval(argv(1)); + if(($argc > 1) && intval($argv[1])) { + $manual_id = intval($argv[1]); $force = true; } - $interval = intval(get_config('system','poll_interval')); - if(! $interval) - $interval = ((get_config('system','delivery_interval') === false) ? 3 : intval(get_config('system','delivery_interval'))); $sql_extra = (($manual_id) ? " AND abook_id = $manual_id " : ""); @@ -88,55 +212,120 @@ function poller_run($argv, $argc){ $d = datetime_convert(); -//TODO check to see if there are any cronhooks before wasting a process + //TODO check to see if there are any cronhooks before wasting a process if(! $restart) proc_run('php','include/cronhooks.php'); - // Only poll from those with suitable relationships, - // and which have a polling address and ignore Diaspora since - // we are unable to match those posts with a Diaspora GUID and prevent duplicates. + // Only poll from those with suitable relationships $abandon_sql = (($abandon_days) ? sprintf(" AND account_lastlog > UTC_TIMESTAMP() - INTERVAL %d DAY ", intval($abandon_days)) : '' ); - $contacts = q("SELECT abook_id, abook_updated, abook_closeness, abook_channel + + $contacts = q("SELECT abook_id, abook_flags, abook_updated, abook_connected, abook_closeness, abook_channel FROM abook LEFT JOIN account on abook_account = account_id where 1 $sql_extra - AND (( abook_flags = %d ) OR ( abook_flags = %d )) + AND (( abook_flags & %d ) OR ( abook_flags = %d )) AND (( account_flags = %d ) OR ( account_flags = %d )) $abandon_sql ORDER BY RAND()", - - intval(ABOOK_FLAG_HIDDEN), + intval(ABOOK_FLAG_HIDDEN|ABOOK_FLAG_PENDING|ABOOK_FLAG_UNCONNECTED), intval(0), intval(ACCOUNT_OK), intval(ACCOUNT_UNVERIFIED) // FIXME ); - if(! $contacts) { - return; - } + if($contacts) { + + foreach($contacts as $contact) { + + $update = false; + + $t = $contact['abook_updated']; + $c = $contact['abook_connected']; - foreach($contacts as $contact) { - $update = false; + if($c == $t) { + if(datetime_convert('UTC','UTC', 'now') > datetime_convert('UTC','UTC', $t . " + 1 day")) + $update = true; + } + else { + // if we've never connected with them, start the mark for death countdown from now - $t = $contact['abook_updated']; + if($c == '0000-00-00 00:00:00') { + $r = q("update abook set abook_connected = '%s' where abook_id = %d limit 1", + dbesc(datetime_convert()), + intval($contact['abook_id']) + ); + $c = datetime_convert(); + $update = true; + } - if(datetime_convert('UTC','UTC', 'now') > datetime_convert('UTC','UTC', $t . " + 1 day")) - $update = true; + // He's dead, Jim + + if(strcmp(datetime_convert('UTC','UTC', 'now'),datetime_convert('UTC','UTC', $c . " + 30 day")) > 0) { + $r = q("update abook set abook_flags = (abook_flags | %d) where abook_id = %d limit 1", + intval(ABOOK_FLAG_ARCHIVED), + intval($contact['abook_id']) + ); + $update = false; + continue; + } + + if($contact['abook_flags'] & ABOOK_FLAG_ARCHIVED) { + $update = false; + continue; + } + + // might be dead, so maybe don't poll quite so often + + // recently deceased, so keep up the regular schedule for 3 days + + if((strcmp(datetime_convert('UTC','UTC', 'now'),datetime_convert('UTC','UTC', $c . " + 3 day")) > 0) + && (strcmp(datetime_convert('UTC','UTC', 'now'),datetime_convert('UTC','UTC', $t . " + 1 day")) > 0)) + $update = true; - if((! $update) && (! $force)) - continue; + // After that back off and put them on a morphine drip - proc_run('php','include/onepoll.php',$contact['abook_id']); - if($interval) - @time_sleep_until(microtime(true) + (float) $interval); + if(strcmp(datetime_convert('UTC','UTC', 'now'),datetime_convert('UTC','UTC', $t . " + 2 day")) > 0) { + $update = true; + } + + } + + if((! $update) && (! $force)) + continue; + + proc_run('php','include/onepoll.php',$contact['abook_id']); + if($interval) + @time_sleep_until(microtime(true) + (float) $interval); + + } } + if($dirmode == DIRECTORY_MODE_SECONDARY || $dirmode == DIRECTORY_MODE_PRIMARY) { + $r = q("select distinct ud_addr, updates.* from updates where not ( ud_flags & %d ) and ud_addr != '' and ( ud_last = '0000-00-00 00:00:00' OR ud_last > UTC_TIMESTAMP() - INTERVAL 7 DAY ) group by ud_addr ", + intval(UPDATE_FLAGS_UPDATED) + ); + if($r) { + foreach($r as $rr) { + + // If they didn't respond when we attempted before, back off to once a day + // After 7 days we won't bother anymore + + if($rr['ud_last'] != '0000-00-00 00:00:00') + if($rr['ud_last'] > datetime_convert('UTC','UTC', 'now - 1 day')) + continue; + proc_run('php','include/onedirsync.php',$rr['ud_id']); + if($interval) + @time_sleep_until(microtime(true) + (float) $interval); + } + } + } + return; } diff --git a/include/probe.php b/include/probe.php new file mode 100644 index 000000000..29635f963 --- /dev/null +++ b/include/probe.php @@ -0,0 +1,99 @@ +<?php /** @file */ + + +/** + * Functions to assist in probing various legacy networks to figure out what kind of capabilities might be present. + */ + + +function net_have_driver($net) { + + if(function_exists('net_discover_' . $net)) + return true; + return false; +} + +function probe_well_known($addr) { + + $ret = array(); + + $ret['src'] = $addr; + + if(strpos($addr,'@') !== false) { + $ret['address'] = $addr; + } + else { + $ret['url'] = $addr; + } + + if(stristr($addr,'facebook.com')) { + $ret['network'] = 'facebook'; + } + if(stristr($addr,'google.com')) { + $ret['network'] = 'google'; + } + if(stristr($addr,'linkedin.com')) { + $ret['network'] = 'linkedin'; + } + + call_hooks('probe_well_known', $ret); + + if(array_key_exists('network',$ret) && net_have_driver($ret['network'])) { + $fn = 'net_discover_' . $ret['network']; + $ret = $fn($ret); + } + + + return $ret; + +} + + + + +function probe_webfinger($addr) { + + + + + +} + + +function probe_legacy_webfinger($addr) { + + + + +} + +function probe_zot($addr) { + + + +} + +function probe_dfrn($addr) { + + +} + + +function probe_diaspora($addr) { + + +} + + +function probe_legacy_feed($addr) { + + + +} + + +function probe_activity_stream($addr) { + + +} + diff --git a/include/profile_advanced.php b/include/profile_advanced.php deleted file mode 100644 index 749c79a3b..000000000 --- a/include/profile_advanced.php +++ /dev/null @@ -1,90 +0,0 @@ -<?php - -function advanced_profile(&$a) { - - $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'] ); - - - if(($a->profile['dob']) && ($a->profile['dob'] != '0000-00-00')) { - - $year_bd_format = t('j F, Y'); - $short_bd_format = t('j F'); - - - $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'] = $a->profile['with']; - - if(strlen($a->profile['howlong']) && $a->profile['howlong'] !== '0000-00-00 00:00:00') { - $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['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 ); - - return replace_macros($tpl, array( - '$title' => t('Profile'), - '$profile' => $profile, - )); - } - - return ''; -} diff --git a/include/profile_selectors.php b/include/profile_selectors.php index 8d29fd099..1ffcd49be 100644 --- a/include/profile_selectors.php +++ b/include/profile_selectors.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ function gender_selector($current="",$suffix="") { diff --git a/include/queue.php b/include/queue.php index 5467f2c20..239d61fc0 100644 --- a/include/queue.php +++ b/include/queue.php @@ -1,7 +1,8 @@ -<?php +<?php /** @file */ require_once("boot.php"); require_once('include/cli_startup.php'); require_once('include/queue_fn.php'); +require_once('include/zot.php'); function queue_run($argv, $argc){ @@ -32,21 +33,25 @@ function queue_run($argv, $argc){ // For the first 12 hours we'll try to deliver every 15 minutes // After that, we'll only attempt delivery once per hour. - - $r = q("SELECT * FROM outq WHERE outq_delivered = 0 and (( outq_created > UTC_TIMESTAMP() - INTERVAL 12 HOUR and outq_updated < UTC_TIMESTAMP() - INTERVAL 15 MINUTE ) OR ( outq_updated < UTC_TIMESTAMP() - INTERVAL 1 HOUR ))"); + // This currently only handles the default queue drivers ('zot' or '') which we will group by posturl + // so that we don't start off a thousand deliveries for a couple of dead hubs. + // The zot driver will deliver everything destined for a single hub once contact is made (*if* contact is made). + // Other drivers will have to do something different here and may need their own query. + + $r = q("SELECT * FROM outq WHERE outq_delivered = 0 and (( outq_created > UTC_TIMESTAMP() - INTERVAL 12 HOUR and outq_updated < UTC_TIMESTAMP() - INTERVAL 15 MINUTE ) OR ( outq_updated < UTC_TIMESTAMP() - INTERVAL 1 HOUR )) and outq_driver in ('','zot') group by outq_posturl"); } if(! $r) return; foreach($r as $rr) { - if(in_array($rr['outq_hub'],$deadguys)) + if(in_array($rr['outq_posturl'],$deadguys)) continue; $result = zot_zot($rr['outq_posturl'],$rr['outq_notify']); if($result['success']) { zot_process_response($rr['outq_posturl'],$result, $rr); } else { - $deadguys[] = $rr['outq_hub']; + $deadguys[] = $rr['outq_posturl']; $y = q("update outq set outq_updated = '%s' where outq_hash = '%s' limit 1", dbesc(datetime_convert()), dbesc($rr['outq_hash']) diff --git a/include/queue_fn.php b/include/queue_fn.php index c9782b939..512edb531 100644 --- a/include/queue_fn.php +++ b/include/queue_fn.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ function update_queue_time($id) { logger('queue: requeue item ' . $id); diff --git a/include/reddav.php b/include/reddav.php new file mode 100644 index 000000000..fe05af606 --- /dev/null +++ b/include/reddav.php @@ -0,0 +1,1150 @@ +<?php /** @file */ + +use Sabre\DAV; +require_once('vendor/autoload.php'); + +require_once('include/attach.php'); + +class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { + + private $red_path; + private $folder_hash; + private $ext_path; + private $root_dir = ''; + private $auth; + private $os_path = ''; + + function __construct($ext_path,&$auth_plugin) { + logger('RedDirectory::__construct() ' . $ext_path, LOGGER_DEBUG); + $this->ext_path = $ext_path; + $this->red_path = ((strpos($ext_path,'/cloud') === 0) ? substr($ext_path,6) : $ext_path); + if(! $this->red_path) + $this->red_path = '/'; + $this->auth = $auth_plugin; + $this->folder_hash = ''; + + $this->getDir(); + + if($this->auth->browser) + $this->auth->browser->set_writeable(); + + } + + + function log() { + logger('RedDirectory::log() ext_path ' . $this->ext_path, LOGGER_DATA); + logger('RedDirectory::log() os_path ' . $this->os_path, LOGGER_DATA); + logger('RedDirectory::log() red_path ' . $this->red_path, LOGGER_DATA); + } + + function getChildren() { + + logger('RedDirectory::getChildren() called for ' . $this->ext_path, LOGGER_DATA); + + $this->log(); + + if(get_config('system','block_public') && (! $this->auth->channel_id) && (! $this->auth->observer)) { + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + if(($this->auth->owner_id) && (! perm_is_allowed($this->auth->owner_id,$this->auth->observer,'view_storage'))) { + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + $contents = RedCollectionData($this->red_path,$this->auth); + return $contents; + } + + + function getChild($name) { + + logger('RedDirectory::getChild : ' . $name, LOGGER_DATA); + + if(get_config('system','block_public') && (! $this->auth->channel_id) && (! $this->auth->observer)) { + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + if(($this->auth->owner_id) && (! perm_is_allowed($this->auth->owner_id,$this->auth->observer,'view_storage'))) { + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + if($this->red_path === '/' && $name === 'cloud') { + return new RedDirectory('/cloud', $this->auth); + } + + $x = RedFileData($this->ext_path . '/' . $name, $this->auth); + if($x) + return $x; + + throw new DAV\Exception\NotFound('The file with name: ' . $name . ' could not be found'); + + } + + function getName() { + logger('RedDirectory::getName returns: ' . basename($this->red_path), LOGGER_DATA); + return (basename($this->red_path)); + } + + + + + function createFile($name,$data = null) { + logger('RedDirectory::createFile : ' . $name, LOGGER_DEBUG); + + if(! $this->auth->owner_id) { + logger('createFile: permission denied'); + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + if(! perm_is_allowed($this->auth->owner_id,$this->auth->observer,'write_storage')) { + logger('createFile: permission denied'); + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + $mimetype = z_mime_content_type($name); + + + $c = q("select * from channel where channel_id = %d and not (channel_pageflags & %d) limit 1", + intval($this->auth->owner_id), + intval(PAGE_REMOVED) + + ); + + if(! $c) { + logger('createFile: no channel'); + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + + $filesize = 0; + $hash = random_string(); + + $r = q("INSERT INTO attach ( aid, uid, hash, creator, filename, folder, flags, filetype, filesize, revision, data, created, edited, allow_cid, allow_gid, deny_cid, deny_gid ) + VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", + intval($c[0]['channel_account_id']), + intval($c[0]['channel_id']), + dbesc($hash), + dbesc($this->auth->observer), + dbesc($name), + dbesc($this->folder_hash), + dbesc(ATTACH_FLAG_OS), + dbesc($mimetype), + intval($filesize), + intval(0), + dbesc($this->os_path . '/' . $hash), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc($c[0]['channel_allow_cid']), + dbesc($c[0]['channel_allow_gid']), + dbesc($c[0]['channel_deny_cid']), + dbesc($c[0]['channel_deny_gid']) + + + ); + + $f = 'store/' . $this->auth->owner_nick . '/' . (($this->os_path) ? $this->os_path . '/' : '') . $hash; + + file_put_contents($f, $data); + $size = filesize($f); + + $edited = datetime_convert(); + + $d = q("update attach set filesize = '%s', edited = '%s' where hash = '%s' and uid = %d limit 1", + dbesc($size), + dbesc($edited), + dbesc($hash), + intval($c[0]['channel_id']) + ); + + $e = q("update attach set edited = '%s' where folder = '%s' and uid = %d limit 1", + dbesc($edited), + dbesc($this->folder_hash), + intval($c[0]['channel_id']) + ); + + $maxfilesize = get_config('system','maxfilesize'); + + if(($maxfilesize) && ($size > $maxfilesize)) { + attach_delete($c[0]['channel_id'],$hash); + return; + } + + $limit = service_class_fetch($c[0]['channel_id'],'attach_upload_limit'); + if($limit !== false) { + $x = q("select sum(filesize) as total from attach where aid = %d ", + intval($c[0]['channel_account_id']) + ); + if(($x) && ($x[0]['total'] + $size > $limit)) { + logger('reddav: service class limit exceeded for ' . $c[0]['channel_name'] . ' total usage is ' . $x[0]['total'] . ' limit is ' . $limit); + attach_delete($c[0]['channel_id'],$hash); + return; + } + } + } + + + function createDirectory($name) { + + logger('RedDirectory::createDirectory: ' . $name, LOGGER_DEBUG); + + if((! $this->auth->owner_id) || (! perm_is_allowed($this->auth->owner_id,$this->auth->observer,'write_storage'))) { + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + $r = q("select * from channel where channel_id = %d and not (channel_pageflags & %d) limit 1", + intval($this->auth->owner_id), + intval(PAGE_REMOVED) + ); + + if($r) { + $result = attach_mkdir($r[0],$this->auth->observer,array('filename' => $name,'folder' => $this->folder_hash)); + if(! $result['success']) + logger('RedDirectory::createDirectory: ' . print_r($result,true), LOGGER_DEBUG); + } + } + + + function childExists($name) { + + if($this->red_path === '/' && $name === 'cloud') { + logger('RedDirectory::childExists /cloud: true', LOGGER_DATA); + return true; + } + + $x = RedFileData($this->ext_path . '/' . $name, $this->auth,true); + logger('RedFileData returns: ' . print_r($x,true), LOGGER_DATA); + if($x) + return true; + return false; + } + + function getDir() { + logger('getDir: ' . $this->ext_path, LOGGER_DEBUG); + $this->auth->log(); + + $file = $this->ext_path; + + $x = strpos($file,'/cloud'); + if($x === false) + return; + if($x === 0) { + $file = substr($file,6); + } + + if((! $file) || ($file === '/')) { + return; + } + + $file = trim($file,'/'); + $path_arr = explode('/', $file); + + if(! $path_arr) + return; + + + logger('getDir(): path: ' . print_r($path_arr,true), LOGGER_DEBUG); + + $channel_name = $path_arr[0]; + + + $r = q("select channel_id from channel where channel_address = '%s' and not ( channel_pageflags & %d ) limit 1", + dbesc($channel_name), + intval(PAGE_REMOVED) + ); + + if(! $r) { + throw new DAV\Exception\NotFound('The file with name: ' . $channel_name . ' could not be found'); + + return; + } + $channel_id = $r[0]['channel_id']; + $this->auth->owner_id = $channel_id; + $this->auth->owner_nick = $channel_name; + + $path = '/' . $channel_name; + + $folder = ''; + $os_path = ''; + + for($x = 1; $x < count($path_arr); $x ++) { + + $r = q("select id, hash, filename, flags from attach where folder = '%s' and filename = '%s' and uid = %d and (flags & %d)", + dbesc($folder), + dbesc($path_arr[$x]), + intval($channel_id), + intval(ATTACH_FLAG_DIR) + ); + + if($r && ( $r[0]['flags'] & ATTACH_FLAG_DIR)) { + $folder = $r[0]['hash']; + if(strlen($os_path)) + $os_path .= '/'; + $os_path .= $folder; + + $path = $path . '/' . $r[0]['filename']; + } + } + $this->folder_hash = $folder; + $this->os_path = $os_path; + return; + } + + + function getLastModified() { + $r = q("select edited from attach where folder = '%s' and uid = %d order by edited desc limit 1", + dbesc($this->folder_hash), + intval($this->auth->owner_id) + ); + if($r) + return datetime_convert('UTC','UTC', $r[0]['edited'],'U'); + return ''; + } + + + public function getQuotaInfo() { + + $limit = disk_total_space('store'); + $free = disk_free_space('store'); + + if($this->auth->owner_id) { + + $c = q("select * from channel where channel_id = %d and not (channel_pageflags & %d) limit 1", + intval($this->auth->owner_id), + intval(PAGE_REMOVED) + + ); + + $ulimit = service_class_fetch($c[0]['channel_id'],'attach_upload_limit'); + $limit = (($ulimit) ? $ulimit : $limit); + + $x = q("select sum(filesize) as total from attach where aid = %d ", + intval($c[0]['channel_account_id']) + ); + $free = (($x) ? $limit - $x[0]['total'] : 0); + } + + return array( + $limit - $free, + $free + ); + + } + +} + + +class RedFile extends DAV\Node implements DAV\IFile { + + private $data; + private $auth; + private $name; + + function __construct($name, $data, &$auth) { + $this->name = $name; + $this->data = $data; + $this->auth = $auth; + + logger('RedFile::_construct: ' . print_r($this->data,true), LOGGER_DATA); + } + + + function getName() { + logger('RedFile::getName: ' . basename($this->name), LOGGER_DEBUG); + return basename($this->name); + + } + + + function setName($newName) { + logger('RedFile::setName: ' . basename($this->name) . ' -> ' . $newName, LOGGER_DEBUG); + + if((! $newName) || (! $this->auth->owner_id) || (! perm_is_allowed($this->auth->owner_id,$this->auth->observer,'write_storage'))) { + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + $newName = str_replace('/','%2F',$newName); + + $r = q("update attach set filename = '%s' where hash = '%s' and id = %d limit 1", + dbesc($this->data['filename']), + intval($this->data['id']) + ); + + } + + + function put($data) { + logger('RedFile::put: ' . basename($this->name), LOGGER_DEBUG); + + $c = q("select * from channel where channel_id = %d and not (channel_pageflags & %d) limit 1", + intval(PAGE_REMOVED), + intval($this->auth->owner_id) + ); + + $r = q("select flags, folder, data from attach where hash = '%s' and uid = %d limit 1", + dbesc($this->data['hash']), + intval($c[0]['channel_id']) + ); + if($r) { + if($r[0]['flags'] & ATTACH_FLAG_OS) { + $f = 'store/' . $this->auth->owner_nick . '/' . (($r[0]['data']) ? $r[0]['data'] : ''); + @file_put_contents($f, $data); + $size = @filesize($f); + logger('reddav: put() filename: ' . $f . ' size: ' . $size, LOGGER_DEBUG); + } + else { + $r = q("update attach set data = '%s' where hash = '%s' and uid = %d limit 1", + dbesc(stream_get_contents($data)), + dbesc($this->data['hash']), + intval($this->data['uid']) + ); + $r = q("select length(data) as fsize from attach where hash = '%s' and uid = %d limit 1", + dbesc($this->data['hash']), + intval($this->data['uid']) + ); + if($r) + $size = $r[0]['fsize']; + } + + } + + $edited = datetime_convert(); + + $d = q("update attach set filesize = '%s', edited = '%s' where hash = '%s' and uid = %d limit 1", + dbesc($size), + dbesc($edited), + dbesc($this->data['hash']), + intval($c[0]['channel_id']) + ); + + $e = q("update attach set edited = '%s' where folder = '%s' and uid = %d limit 1", + dbesc($edited), + dbesc($r[0]['folder']), + intval($c[0]['channel_id']) + ); + + $maxfilesize = get_config('system','maxfilesize'); + + if(($maxfilesize) && ($size > $maxfilesize)) { + attach_delete($c[0]['channel_id'],$this->data['hash']); + return; + } + + $limit = service_class_fetch($c[0]['channel_id'],'attach_upload_limit'); + if($limit !== false) { + $x = q("select sum(filesize) as total from attach where aid = %d ", + intval($c[0]['channel_account_id']) + ); + if(($x) && ($x[0]['total'] + $size > $limit)) { + logger('reddav: service class limit exceeded for ' . $c[0]['channel_name'] . ' total usage is ' . $x[0]['total'] . ' limit is ' . $limit); + attach_delete($c[0]['channel_id'],$this->data['hash']); + return; + } + } + } + + + function get() { + logger('RedFile::get: ' . basename($this->name), LOGGER_DEBUG); + + $r = q("select data, flags, filename, filetype from attach where hash = '%s' and uid = %d limit 1", + dbesc($this->data['hash']), + intval($this->data['uid']) + ); + if($r) { + $unsafe_types = array('text/html','text/css','application/javascript'); + + if(in_array($r[0]['filetype'],$unsafe_types)) { + header('Content-disposition: attachment; filename="' . $r[0]['filename'] . '"'); + header('Content-type: text/plain'); + } + + if($r[0]['flags'] & ATTACH_FLAG_OS ) { + $f = 'store/' . $this->auth->owner_nick . '/' . (($this->os_path) ? $this->os_path . '/' : '') . $r[0]['data']; + return fopen($f,'rb'); + } + return $r[0]['data']; + } + + } + + function getETag() { + return $this->data['hash']; + } + + + function getContentType() { + $unsafe_types = array('text/html','text/css','application/javascript'); + if(in_array($this->data['filetype'],$unsafe_types)) { + return 'text/plain'; + } + return $this->data['filetype']; + } + + + function getSize() { + return $this->data['filesize']; + } + + + function getLastModified() { + return datetime_convert('UTC','UTC',$this->data['edited'],'U'); + } + + + function delete() { + if((! $this->auth->owner_id) || (! perm_is_allowed($this->auth->owner_id,$this->auth->observer,'write_storage'))) { + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + if($this->auth->owner_id !== $this->auth->channel_id) { + if(($this->auth->observer !== $this->data['creator']) || ($this->data['flags'] & ATTACH_FLAG_DIR)) { + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + } + + attach_delete($this->auth->owner_id,$this->data['hash']); + } + +} + +function RedChannelList(&$auth) { + + $ret = array(); + + $r = q("select channel_id, channel_address from channel where not (channel_pageflags & %d) and not (channel_pageflags & %d) ", + intval(PAGE_REMOVED), + intval(PAGE_HIDDEN) + ); + + if($r) { + foreach($r as $rr) { + if(perm_is_allowed($rr['channel_id'],$auth->observer,'view_storage')) { + logger('RedChannelList: ' . '/cloud/' . $rr['channel_address'], LOGGER_DATA); + $ret[] = new RedDirectory('/cloud/' . $rr['channel_address'],$auth); + } + } + } + return $ret; + +} + + +function RedCollectionData($file,&$auth) { + + $ret = array(); + + $x = strpos($file,'/cloud'); + if($x === 0) { + $file = substr($file,6); + } + + if((! $file) || ($file === '/')) { + return RedChannelList($auth); + } + + $file = trim($file,'/'); + $path_arr = explode('/', $file); + + if(! $path_arr) + return null; + + $channel_name = $path_arr[0]; + + $r = q("select channel_id from channel where channel_address = '%s' limit 1", + dbesc($channel_name) + ); + + if(! $r) + return null; + + $channel_id = $r[0]['channel_id']; + $perms = permissions_sql($channel_id); + + $auth->owner_id = $channel_id; + + $path = '/' . $channel_name; + + $folder = ''; + $errors = false; + $permission_error = false; + + for($x = 1; $x < count($path_arr); $x ++) { + + $r = q("select id, hash, filename, flags from attach where folder = '%s' and filename = '%s' and uid = %d and (flags & %d) $perms limit 1", + dbesc($folder), + dbesc($path_arr[$x]), + intval($channel_id), + intval(ATTACH_FLAG_DIR) + ); + if(! $r) { + // path wasn't found. Try without permissions to see if it was the result of permissions. + $errors = true; + $r = q("select id, hash, filename, flags from attach where folder = '%s' and filename = '%s' and uid = %d and (flags & %d) limit 1", + dbesc($folder), + basename($path_arr[$x]), + intval($channel_id), + intval(ATTACH_FLAG_DIR) + ); + if($r) { + $permission_error = true; + } + break; + } + + if($r && ( $r[0]['flags'] & ATTACH_FLAG_DIR)) { + $folder = $r[0]['hash']; + $path = $path . '/' . $r[0]['filename']; + } + } + + if($errors) { + if($permission_error) { + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + else { + throw new DAV\Exception\NotFound('A component of the request file path could not be found'); + return; + } + } + + // This should no longer be needed since we just returned errors for paths not found + + if($path !== '/' . $file) { + logger("RedCollectionData: Path mismatch: $path !== /$file"); + return NULL; + } + + $ret = array(); + + $r = q("select id, uid, hash, filename, filetype, filesize, revision, folder, flags, created, edited from attach where folder = '%s' and uid = %d $perms group by filename", + dbesc($folder), + intval($channel_id) + ); + + foreach($r as $rr) { + logger('RedCollectionData: filename: ' . $rr['filename'], LOGGER_DATA); + + if($rr['flags'] & ATTACH_FLAG_DIR) + $ret[] = new RedDirectory('/cloud' . $path . '/' . $rr['filename'],$auth); + else + $ret[] = new RedFile('/cloud' . $path . '/' . $rr['filename'],$rr,$auth); + } + + return $ret; + +} + +function RedFileData($file, &$auth,$test = false) { + + logger('RedFileData:' . $file . (($test) ? ' (test mode) ' : ''), LOGGER_DEBUG); + + + $x = strpos($file,'/cloud'); + if($x === 0) { + $file = substr($file,6); + } + + if((! $file) || ($file === '/')) { + return new RedDirectory('/',$auth); + + } + + $file = trim($file,'/'); + + $path_arr = explode('/', $file); + + if(! $path_arr) + return null; + + + $channel_name = $path_arr[0]; + + + $r = q("select channel_id from channel where channel_address = '%s' limit 1", + dbesc($channel_name) + ); + + if(! $r) + return null; + + $channel_id = $r[0]['channel_id']; + + $path = '/' . $channel_name; + + $auth->owner_id = $channel_id; + + $permission_error = false; + + $folder = ''; + + require_once('include/security.php'); + $perms = permissions_sql($channel_id); + + $errors = false; + + for($x = 1; $x < count($path_arr); $x ++) { + $r = q("select id, hash, filename, flags from attach where folder = '%s' and filename = '%s' and uid = %d and (flags & %d) $perms", + dbesc($folder), + dbesc($path_arr[$x]), + intval($channel_id), + intval(ATTACH_FLAG_DIR) + ); + + if($r && ( $r[0]['flags'] & ATTACH_FLAG_DIR)) { + $folder = $r[0]['hash']; + $path = $path . '/' . $r[0]['filename']; + } + if(! $r) { + $r = q("select id, uid, hash, filename, filetype, filesize, revision, folder, flags, created, edited from attach + where folder = '%s' and filename = '%s' and uid = %d $perms group by filename limit 1", + dbesc($folder), + dbesc(basename($file)), + intval($channel_id) + + ); + } + if(! $r) { + + $errors = true; + $r = q("select id, uid, hash, filename, filetype, filesize, revision, folder, flags, created, edited from attach + where folder = '%s' and filename = '%s' and uid = %d group by filename limit 1", + dbesc($folder), + dbesc(basename($file)), + intval($channel_id) + ); + if($r) + $permission_error = true; + + } + + } + + if($path === '/' . $file) { + if($test) + return true; + // final component was a directory. + return new RedDirectory('/cloud/' . $file,$auth); + } + + if($errors) { + logger('RedFileData: not found'); + if($test) + return false; + if($permission_error) { + logger('RedFileData: permission error'); + throw new DAV\Exception\Forbidden('Permission denied.'); + } + return; + } + + if($r) { + if($test) + return true; + + if($r[0]['flags'] & ATTACH_FLAG_DIR) + return new RedDirectory('/cloud' . $path . '/' . $r[0]['filename'],$auth); + else + return new RedFile('/cloud' . $path . '/' . $r[0]['filename'],$r[0],$auth); + } + return false; +} + + +class RedBasicAuth extends Sabre\DAV\Auth\Backend\AbstractBasic { + + public $channel_name = ''; + public $channel_id = 0; + public $channel_hash = ''; + public $observer = ''; + public $browser; + public $owner_id; + public $owner_nick = ''; + public $timezone; + + protected function validateUserPass($username, $password) { + + + if(trim($password) === '+++') { + logger('reddav: validateUserPass: guest ' . $username); + return true; + } + + require_once('include/auth.php'); + $record = account_verify_password($username,$password); + if($record && $record['account_default_channel']) { + $r = q("select * from channel where channel_account_id = %d and channel_id = %d limit 1", + intval($record['account_id']), + intval($record['account_default_channel']) + ); + if($r) { + $this->currentUser = $r[0]['channel_address']; + $this->channel_name = $r[0]['channel_address']; + $this->channel_id = $r[0]['channel_id']; + $this->channel_hash = $this->observer = $r[0]['channel_hash']; + $_SESSION['uid'] = $r[0]['channel_id']; + $_SESSION['account_id'] = $r[0]['channel_account_id']; + $_SESSION['authenticated'] = true; + return true; + } + } + $r = q("select * from channel where channel_address = '%s' limit 1", + dbesc($username) + ); + if($r) { + $x = q("select * from account where account_id = %d limit 1", + intval($r[0]['channel_account_id']) + ); + if($x) { + foreach($x as $record) { + if(($record['account_flags'] == ACCOUNT_OK) || ($record['account_flags'] == ACCOUNT_UNVERIFIED) + && (hash('whirlpool',$record['account_salt'] . $password) === $record['account_password'])) { + logger('(DAV) RedBasicAuth: password verified for ' . $username); + $this->currentUser = $r[0]['channel_address']; + $this->channel_name = $r[0]['channel_address']; + $this->channel_id = $r[0]['channel_id']; + $this->channel_hash = $this->observer = $r[0]['channel_hash']; + $_SESSION['uid'] = $r[0]['channel_id']; + $_SESSION['account_id'] = $r[0]['channel_account_id']; + $_SESSION['authenticated'] = true; + return true; + } + } + } + } + logger('(DAV) RedBasicAuth: password failed for ' . $username); + return false; + } + + function setCurrentUser($name) { + $this->currentUser = $name; + } + + function setBrowserPlugin($browser) { + $this->browser = $browser; + } + + + function log() { + logger('dav: auth: channel_name ' . $this->channel_name, LOGGER_DATA); + logger('dav: auth: channel_id ' . $this->channel_id, LOGGER_DATA); + logger('dav: auth: channel_hash ' . $this->channel_hash, LOGGER_DATA); + logger('dav: auth: observer ' . $this->observer, LOGGER_DATA); + logger('dav: auth: owner_id ' . $this->owner_id, LOGGER_DATA); + logger('dav: auth: owner_nick ' . $this->owner_nick, LOGGER_DATA); + } + + +} + + +class RedBrowser extends DAV\Browser\Plugin { + + private $auth; + + function __construct(&$auth) { + + $this->auth = $auth; + $this->enableAssets = false; + + } + + // The DAV browser is instantiated after the auth module and directory classes but before we know the current + // directory and who the owner and observer are. So we add a pointer to the browser into the auth module and vice + // versa. Then when we've figured out what directory is actually being accessed, we call the following function + // to decide whether or not to show web elements which include writeable objects. + + + function set_writeable() { + + if(! $this->auth->owner_id) + $this->enablePost = false; + + if(! perm_is_allowed($this->auth->owner_id, get_observer_hash(), 'write_storage')) + $this->enablePost = false; + else + $this->enablePost = true; + + } + + public function generateDirectoryIndex($path) { + + $is_owner = ((local_user() && $this->auth->owner_id == local_user()) ? true : false); + + if($this->auth->timezone) + date_default_timezone_set($this->auth->timezone); + + $version = ''; + require_once('include/conversation.php'); + + if($this->auth->channel_name) + $html = profile_tabs(get_app(),(($is_owner) ? true : false),$this->auth->owner_nick); + + $html .= " + <body> + <h1>".t('Files').": ".$this->escapeHTML($path) . "/</h1> + <table id=\"cloud-index\"> + <tr> + <th></th> + <th>".t('Name')."</th> + <th></th><th></th><th></th> + <th>Type</th> + <th>Size</th> + <th>Last modified</th> + </tr> + <tr><td colspan=\"8\"><hr /></td></tr>"; + + $files = $this->server->getPropertiesForPath($path,array( + '{DAV:}displayname', + '{DAV:}resourcetype', + '{DAV:}getcontenttype', + '{DAV:}getcontentlength', + '{DAV:}getlastmodified', + ),1); + + $parent = $this->server->tree->getNodeForPath($path); + + + if ($path) { + + list($parentUri) = DAV\URLUtil::splitPath($path); + $fullPath = DAV\URLUtil::encodePath($this->server->getBaseUri() . $parentUri); + + $icon = $this->enableAssets?'<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl('icons/parent' . $this->iconExtension) . '" width="24" alt="Parent" /></a>':''; + $html.= " + <tr> + <td>$icon</td> + <td><a href=\"{$fullPath}\">..</a></td> + <td></td><td></td><th></td> + <td>[parent]</td> + <td></td> + <td></td> + </tr>"; + + } + + foreach($files as $file) { + + // This is the current directory, we can skip it + if (rtrim($file['href'],'/')==$path) continue; + + list(, $name) = DAV\URLUtil::splitPath($file['href']); + + $type = null; + + + if (isset($file[200]['{DAV:}resourcetype'])) { + $type = $file[200]['{DAV:}resourcetype']->getValue(); + + // resourcetype can have multiple values + if (!is_array($type)) $type = array($type); + + foreach($type as $k=>$v) { + + // Some name mapping is preferred + switch($v) { + case '{DAV:}collection' : + $type[$k] = 'Collection'; + break; + case '{DAV:}principal' : + $type[$k] = 'Principal'; + break; + case '{urn:ietf:params:xml:ns:carddav}addressbook' : + $type[$k] = 'Addressbook'; + break; + case '{urn:ietf:params:xml:ns:caldav}calendar' : + $type[$k] = 'Calendar'; + break; + case '{urn:ietf:params:xml:ns:caldav}schedule-inbox' : + $type[$k] = 'Schedule Inbox'; + break; + case '{urn:ietf:params:xml:ns:caldav}schedule-outbox' : + $type[$k] = 'Schedule Outbox'; + break; + case '{http://calendarserver.org/ns/}calendar-proxy-read' : + $type[$k] = 'Proxy-Read'; + break; + case '{http://calendarserver.org/ns/}calendar-proxy-write' : + $type[$k] = 'Proxy-Write'; + break; + } + + } + $type = implode(', ', $type); + } + + // If no resourcetype was found, we attempt to use + // the contenttype property + if (!$type && isset($file[200]['{DAV:}getcontenttype'])) { + $type = $file[200]['{DAV:}getcontenttype']; + } + if (!$type) $type = 'Unknown'; + + $size = isset($file[200]['{DAV:}getcontentlength'])?(int)$file[200]['{DAV:}getcontentlength']:''; + $lastmodified = ((isset($file[200]['{DAV:}getlastmodified']))? $file[200]['{DAV:}getlastmodified']->getTime()->format('Y-m-d H:i:s') :''); + + $fullPath = DAV\URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path?$path . '/':'') . $name,'/')); + + $displayName = isset($file[200]['{DAV:}displayname'])?$file[200]['{DAV:}displayname']:$name; + + $displayName = $this->escapeHTML($displayName); + $type = $this->escapeHTML($type); + + $icon = ''; + + if ($this->enableAssets) { + $node = $this->server->tree->getNodeForPath(($path?$path.'/':'') . $name); + foreach(array_reverse($this->iconMap) as $class=>$iconName) { + + if ($node instanceof $class) { + $icon = '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl($iconName . $this->iconExtension) . '" alt="" width="24" /></a>'; + break; + } + + + } + + } + + $parentHash=""; + $owner=$this->auth->owner_id; + $splitPath = split("/",$fullPath); + if (count($splitPath) > 3) { + for ($i=3; $i<count($splitPath); $i++) { + $attachName = urldecode($splitPath[$i]); + $attachHash = $this->findAttachHash($owner,$parentHash,$attachName); + $parentHash = $attachHash; + } + } + $attachId = $this->findAttachIdByHash($attachHash); + $fileStorageUrl = substr($fullPath, 0, strpos($fullPath,"cloud/")) . "filestorage/".$this->auth->channel_name; + $attachIcon = ""; // "<a href=\"attach/".$attachHash."\" title=\"".$displayName."\"><i class=\"icon-download\"></i></a>"; + $html.= "<tr> + <td>$icon</td> + <td style=\"min-width: 15em\"><a href=\"{$fullPath}\">{$displayName}</a></td>"; + + if($is_owner) { + $html .= "<td>" . (($size) ? $attachIcon : '') . "</td> + <td><a href=\"".$fileStorageUrl."/".$attachId."/edit\" title=\"".t('Edit')."\"><i class=\"icon-pencil btn btn-default\"></i></a></td> + <td><a href=\"".$fileStorageUrl."/".$attachId."/delete\" title=\"".t('Delete')."\" onclick=\"return confirm('Are you sure you want to delete this item?');\"><i class=\"icon-remove btn btn-default drop-icons\"></i></a></td>"; + } + else { + $html .= "<td></td><td></td><td></td>"; + } + $html .= + "<td>{$type}</td> + <td>". $size ."</td> + <td>" . (($lastmodified) ? datetime_convert('UTC', date_default_timezone_get(),$lastmodified) : '') . "</td> + </tr>"; + + } + + $html.= "<tr><td colspan=\"8\"><hr /></td></tr>"; + + $output = ''; + + if ($this->enablePost) { + $this->server->broadcastEvent('onHTMLActionsPanel',array($parent, &$output)); + } + + $html.=$output; + + $html.= "</table>"; + + get_app()->page['content'] = $html; + construct_page(get_app()); + +// return $html; + + } + + + public function htmlActionsPanel(DAV\INode $node, &$output) { + + + //Removed link to filestorage page + //if($this->auth->owner_id && $this->auth->owner_id == $this->auth->channel_id) { + // $channel = get_app()->get_channel(); + // if($channel) { + // $output .= '<tr><td colspan="2"><a href="filestorage/' . $channel['channel_address'] . '" >' . t('Edit File properties') . '</a></td></tr><tr><td> </td></tr>'; + // } + //} + + if (!$node instanceof DAV\ICollection) + return; + + // We also know fairly certain that if an object is a non-extended + // SimpleCollection, we won't need to show the panel either. + + if (get_class($node)==='Sabre\\DAV\\SimpleCollection') + return; + + $output.= '<table> + <tr> + <td><strong>Create new folder</strong> </td> + <td><form method="post" action=""> + <input type="text" name="name" /> + <input type="submit" value="create" /> + <input type="hidden" name="sabreAction" value="mkcol" /> + </form></td> + </tr><tr> + <td><strong>Upload file</strong> </td> + <td><form method="post" action="" enctype="multipart/form-data"> + <input type="file" name="file" style="display: inline;"/> + <input type="submit" value="upload" /> + <input type="hidden" name="sabreAction" value="put" /> + <!-- Name (optional): <input type="text" name="name" /> we should rather provide a rename action in edit form--> + </form></td> + </tr> + </table>'; + + } + + /** + * This method takes a path/name of an asset and turns it into url + * suiteable for http access. + * + * @param string $assetName + * @return string + */ + protected function getAssetUrl($assetName) { + return z_root() .'/cloud/?sabreAction=asset&assetName=' . urlencode($assetName); + } + + protected function findAttachHash($owner, $parentHash, $attachName) { + $r = q("select * from attach where uid = %d and folder = '%s' and filename = '%s' order by edited desc limit 1", + intval($owner), dbesc($parentHash), dbesc($attachName) + ); + $hash = ""; + if($r) { + foreach($r as $rr) { + $hash = $rr['hash']; + } + } + return $hash; + } + + protected function findAttachIdByHash($attachHash) { + $r = q("select * from attach where hash = '%s' order by edited desc limit 1", + dbesc($attachHash) + ); + $id = ""; + if($r) { + foreach($r as $rr) { + $id = $rr['id']; + } + } + return $id; + } +} diff --git a/include/security.php b/include/security.php index 276c8f97c..aaf4eb050 100644 --- a/include/security.php +++ b/include/security.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ function authenticate_success($user_record, $login_initial = false, $interactive = false,$return = false,$update_lastlog = false) { @@ -6,11 +6,7 @@ function authenticate_success($user_record, $login_initial = false, $interactive $_SESSION['addr'] = $_SERVER['REMOTE_ADDR']; -// logger('authenticate_success: ' . print_r($user_record,true)); -// logger('authenticate_success: ' . print_r($_SESSION,true)); - if(x($user_record,'account_id')) { -// logger('authenticate_success: Red-style'); $a->account = $user_record; $_SESSION['account_id'] = $user_record['account_id']; $_SESSION['authenticated'] = 1; @@ -35,91 +31,13 @@ function authenticate_success($user_record, $login_initial = false, $interactive } } - else { - $_SESSION['uid'] = $user_record['uid']; - $_SESSION['theme'] = $user_record['theme']; - $_SESSION['authenticated'] = 1; - $_SESSION['page_flags'] = $user_record['page-flags']; - $_SESSION['my_url'] = $a->get_baseurl() . '/channel/' . $user_record['nickname']; - $_SESSION['my_address'] = $user_record['nickname'] . '@' . substr($a->get_baseurl(),strpos($a->get_baseurl(),'://')+3); - - $a->user = $user_record; - - if($interactive) { - if($a->user['login_date'] === '0000-00-00 00:00:00') { - $_SESSION['return_url'] = 'profile_photo/new'; - $a->module = 'profile_photo'; - info( t("Welcome ") . $a->user['username'] . EOL); - info( t('Please upload a profile photo.') . EOL); - } - else - info( t("Welcome back ") . $a->user['username'] . EOL); - } - - $member_since = strtotime($a->user['register_date']); - if(time() < ($member_since + ( 60 * 60 * 24 * 14))) - $_SESSION['new_member'] = true; - else - $_SESSION['new_member'] = false; - if(strlen($a->user['timezone'])) { - date_default_timezone_set($a->user['timezone']); - $a->timezone = $a->user['timezone']; - } - - $master_record = $a->user; - - if((x($_SESSION,'submanage')) && intval($_SESSION['submanage'])) { - $r = q("select * from user where uid = %d limit 1", - intval($_SESSION['submanage']) - ); - if(count($r)) - $master_record = $r[0]; - } - - $r = q("SELECT `uid`,`username`,`nickname` FROM `user` WHERE `password` = '%s' AND `email` = '%s'", - dbesc($master_record['password']), - dbesc($master_record['email']) - ); - if($r && count($r)) - $a->identities = $r; - else - $a->identities = array(); - - $r = q("select `user`.`uid`, `user`.`username`, `user`.`nickname` - from manage left join user on manage.mid = user.uid - where `manage`.`uid` = %d", - intval($master_record['uid']) - ); - if($r && count($r)) - $a->identities = array_merge($a->identities,$r); - - if($login_initial) - logger('auth_identities: ' . print_r($a->identities,true), LOGGER_DEBUG); - - $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 1 LIMIT 1", - intval($_SESSION['uid'])); - if(count($r)) { - $a->contact = $r[0]; - $a->cid = $r[0]['id']; - $_SESSION['cid'] = $a->cid; - } - header('X-Account-Management-Status: active; name="' . $a->user['username'] . '"; id="' . $a->user['nickname'] .'"'); - - if($login_initial) { - $l = get_browser_language(); - - q("UPDATE `user` SET `login_date` = '%s', `language` = '%s' WHERE `uid` = %d LIMIT 1", - dbesc(datetime_convert()), - dbesc($l), - intval($_SESSION['uid']) - ); + if($login_initial) { - call_hooks('logged_in', $a->user); + call_hooks('logged_in', $user_record); - } + // might want to log success here } - if($return || x($_SESSION,'workflow')) { unset($_SESSION['workflow']); @@ -132,6 +50,18 @@ function authenticate_success($user_record, $login_initial = false, $interactive goaway($a->get_baseurl() . '/' . $return_url); } + /* This account has never created a channel. Send them to new_channel by default */ + + if($a->module === 'login') { + $r = q("select count(channel_id) as total from channel where channel_account_id = %d and not ( channel_pageflags & %d)", + intval($a->account['account_id']), + intval(PAGE_REMOVED) + ); + if(($r) && (! $r[0]['total'])) + goaway(z_root() . '/new_channel'); + } + + /* else just return */ } @@ -140,15 +70,18 @@ function change_channel($change_channel) { $ret = false; if($change_channel) { - $r = q("select channel.*, xchan.* from channel left join xchan on channel.channel_hash = xchan.xchan_hash where channel_id = %d and channel_account_id = %d limit 1", + $r = q("select channel.*, xchan.* from channel left join xchan on channel.channel_hash = xchan.xchan_hash where channel_id = %d and channel_account_id = %d and not ( channel_pageflags & %d) limit 1", intval($change_channel), - intval(get_account_id()) + intval(get_account_id()), + intval(PAGE_REMOVED) ); + if($r) { $hash = $r[0]['channel_hash']; $_SESSION['uid'] = intval($r[0]['channel_id']); get_app()->set_channel($r[0]); $_SESSION['theme'] = $r[0]['channel_theme']; + $_SESSION['mobile_theme'] = get_pconfig(local_user(),'system', 'mobile_theme'); date_default_timezone_set($r[0]['channel_timezone']); $ret = $r[0]; } @@ -157,19 +90,27 @@ function change_channel($change_channel) { ); if($x) { $_SESSION['my_url'] = $x[0]['xchan_url']; - $_SESSION['my_address'] = $x[0]['xchan_addr']; + $_SESSION['my_address'] = $r[0]['channel_address'] . '@' . substr(get_app()->get_baseurl(),strpos(get_app()->get_baseurl(),'://')+3); get_app()->set_observer($x[0]); get_app()->set_perms(get_all_perms(local_user(),$hash)); } + if(! is_dir('store/' . $r[0]['channel_address'])) + @mkdir('store/' . $r[0]['channel_address'], STORAGE_DEFAULT_PERMISSIONS,true); + } return $ret; } + + function permissions_sql($owner_id,$remote_verified = false,$groups = null) { + if(defined('STATUSNET_PRIVACY_COMPATIBILITY')) + return ''; + $local_user = local_user(); $remote_user = remote_user(); @@ -204,31 +145,37 @@ function permissions_sql($owner_id,$remote_verified = false,$groups = null) { else { - $observer = get_app()->get_observer(); - $groups = init_groups_visitor($remote_user); - - $gs = '<<>>'; // should be impossible to match - - if(is_array($groups) && count($groups)) { - foreach($groups as $g) - $gs .= '|<' . $g . '>'; - } - $sql = sprintf( - " AND ( NOT (deny_cid like '<%s>' OR deny_gid REGEXP '%s') - AND ( allow_cid like '<%s>' OR allow_gid REGEXP '%s' OR ( allow_cid = '' AND allow_gid = '') ) - ) - ", - dbesc(protect_sprintf( '%' . $remote_user . '%')), - dbesc($gs), - dbesc(protect_sprintf( '%' . $remote_user . '%')), - dbesc($gs) - ); + $observer = get_observer_hash(); + if($observer) { + $groups = init_groups_visitor($observer); + + $gs = '<<>>'; // should be impossible to match + + if(is_array($groups) && count($groups)) { + foreach($groups as $g) + $gs .= '|<' . $g . '>'; + } + $sql = sprintf( + " AND ( NOT (deny_cid like '%s' OR deny_gid REGEXP '%s') + AND ( allow_cid like '%s' OR allow_gid REGEXP '%s' OR ( allow_cid = '' AND allow_gid = '') ) + ) + ", + dbesc(protect_sprintf( '%<' . $observer . '>%')), + dbesc($gs), + dbesc(protect_sprintf( '%<' . $observer . '>%')), + dbesc($gs) + ); + } } + return $sql; } function item_permissions_sql($owner_id,$remote_verified = false,$groups = null) { + if(defined('STATUSNET_PRIVACY_COMPATIBILITY')) + return ''; + $local_user = local_user(); $remote_user = remote_user(); @@ -238,7 +185,7 @@ function item_permissions_sql($owner_id,$remote_verified = false,$groups = null) * default permissions - anonymous user */ - $sql = " AND not (item_flags & " . ITEM_PRIVATE . ") "; + $sql = " AND not item_private "; /** @@ -259,31 +206,61 @@ function item_permissions_sql($owner_id,$remote_verified = false,$groups = null) else { - $observer = get_app()->get_observer(); - $groups = init_groups_visitor($remote_user); + $observer = get_observer_hash(); + + if($observer) { + $groups = init_groups_visitor($observer); + + $gs = '<<>>'; // should be impossible to match + + if(is_array($groups) && count($groups)) { + foreach($groups as $g) + $gs .= '|<' . $g . '>'; + } + $sql = sprintf( + " AND ( NOT (deny_cid like '%s' OR deny_gid REGEXP '%s') + AND ( allow_cid like '%s' OR allow_gid REGEXP '%s' OR ( allow_cid = '' AND allow_gid = '') ) + ) + ", + dbesc(protect_sprintf( '%<' . $observer . '>%')), + dbesc($gs), + dbesc(protect_sprintf( '%<' . $observer . '>%')), + dbesc($gs) + ); + } + } + return $sql; +} + +function public_permissions_sql($observer_hash) { - $gs = '<<>>'; // should be impossible to match + $observer = get_app()->get_observer(); + $groups = init_groups_visitor($observer_hash); - if(is_array($groups) && count($groups)) { - foreach($groups as $g) - $gs .= '|<' . $g . '>'; - } + $gs = '<<>>'; // should be impossible to match + + if(is_array($groups) && count($groups)) { + foreach($groups as $g) + $gs .= '|<' . $g . '>'; + } + $sql = ''; + if($observer_hash) { $sql = sprintf( - " AND ( NOT (deny_cid like '<%s>' OR deny_gid REGEXP '%s') - AND ( allow_cid like '<%s>' OR allow_gid REGEXP '%s' OR ( allow_cid = '' AND allow_gid = '') ) - ) + " OR (( NOT (deny_cid like '%s' OR deny_gid REGEXP '%s') + AND ( allow_cid like '%s' OR allow_gid REGEXP '%s' OR ( allow_cid = '' AND allow_gid = '') ) + )) ", - dbesc(protect_sprintf( '%' . $remote_user . '%')), + dbesc(protect_sprintf( '%<' . $observer_hash . '>%')), dbesc($gs), - dbesc(protect_sprintf( '%' . $remote_user . '%')), + dbesc(protect_sprintf( '%<' . $observer_hash . '>%')), dbesc($gs) ); } + return $sql; } - /* * Functions used to protect against Cross-Site Request Forgery * The security token has to base on at least one value that an attacker can't know - here it's the session ID and the private key. @@ -300,7 +277,7 @@ function get_form_security_token($typename = '') { $timestamp = time(); $sec_hash = hash('whirlpool', $a->user['guid'] . $a->user['prvkey'] . session_id() . $timestamp . $typename); - + return $timestamp . '.' . $sec_hash; } @@ -334,7 +311,7 @@ function check_form_security_token_redirectOnErr($err_redirect, $typename = '', } function check_form_security_token_ForbiddenOnErr($typename = '', $formname = 'form_security_token') { if (!check_form_security_token($typename, $formname)) { - $a = get_app(); + $a = get_app(); logger('check_form_security_token failed: user ' . $a->user['guid'] . ' - form element ' . $typename); logger('check_form_security_token failed: _REQUEST data: ' . print_r($_REQUEST, true), LOGGER_DATA); header('HTTP/1.1 403 Forbidden'); @@ -350,14 +327,79 @@ function check_form_security_token_ForbiddenOnErr($typename = '', $formname = 'f if(! function_exists('init_groups_visitor')) { function init_groups_visitor($contact_id) { $groups = array(); - $r = q("SELECT gid FROM group_member WHERE xchan = '%s' ", - intval($contact_id) + $r = q("SELECT hash FROM `groups` left join group_member on groups.id = group_member.gid WHERE xchan = '%s' ", + dbesc($contact_id) ); if(count($r)) { foreach($r as $rr) - $groups[] = $rr['gid']; + $groups[] = $rr['hash']; } return $groups; }} + + + +// This is used to determine which uid have posts which are visible to the logged in user (from the API) for the +// public_timeline, and we can use this in a community page by making +// $perms = (PERMS_NETWORK|PERMS_PUBLIC) unless logged in. +// Collect uids of everybody on this site who has opened their posts to everybody on this site (or greater visibility) +// We always include yourself if logged in because you can always see your own posts +// resolving granular permissions for the observer against every person and every post on the site +// will likely be too expensive. +// Returns a string list of comma separated channel_ids suitable for direct inclusion in a SQL query + +function stream_perms_api_uids($perms = NULL ) { + $perms = is_null($perms) ? (PERMS_SITE|PERMS_NETWORK|PERMS_PUBLIC) : $perms; + + $ret = array(); + if(local_user()) + $ret[] = local_user(); + $r = q("select channel_id from channel where channel_r_stream > 0 and (channel_r_stream & %d) and not (channel_pageflags & %d)", + intval($perms), + intval(PAGE_CENSORED|PAGE_SYSTEM|PAGE_REMOVED) + ); + if($r) + foreach($r as $rr) + if(! in_array($rr['channel_id'],$ret)) + $ret[] = $rr['channel_id']; + + $str = ''; + if($ret) + foreach($ret as $rr) { + if($str) + $str .= ','; + $str .= intval($rr); + } + logger('stream_perms_api_uids: ' . $str, LOGGER_DEBUG); + return $str; +} + +function stream_perms_xchans($perms = NULL ) { + $perms = is_null($perms) ? (PERMS_SITE|PERMS_NETWORK|PERMS_PUBLIC) : $perms; + + $ret = array(); + if(local_user()) + $ret[] = get_observer_hash(); + + $r = q("select channel_hash from channel where channel_r_stream > 0 and (channel_r_stream & %d) and not (channel_pageflags & %d)", + intval($perms), + intval(PAGE_CENSORED|PAGE_SYETEM|PAGE_REMOVED) + ); + if($r) + foreach($r as $rr) + if(! in_array($rr['channel_hash'],$ret)) + $ret[] = $rr['channel_hash']; + + $str = ''; + if($ret) + foreach($ret as $rr) { + if($str) + $str .= ','; + $str .= "'" . dbesc($rr) . "'"; + } + logger('stream_perms_xchans: ' . $str, LOGGER_DEBUG); + return $str; +} + diff --git a/include/session.php b/include/session.php index 6c32e299f..b531688e2 100644 --- a/include/session.php +++ b/include/session.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ // Session management functions. These provide database storage of PHP // session info. @@ -6,12 +6,36 @@ $session_exists = 0; $session_expire = 180000; -if(! function_exists('ref_session_open')) { + + + +function new_cookie($time) { + $old_sid = session_id(); + +// ??? This shouldn't have any effect if called after session_start() +// We probably need to set the session expiration and change the PHPSESSID cookie. + + session_set_cookie_params($time); + session_regenerate_id(false); + + q("UPDATE session SET sid = '%s' WHERE sid = '%s'", dbesc(session_id()), dbesc($old_sid)); + + if (x($_COOKIE, 'jsAvailable')) { + if ($time) { + $expires = time() + $time; + } else { + $expires = 0; + } + setcookie('jsAvailable', $_COOKIE['jsAvailable'], $expires); + } +} + + function ref_session_open ($s,$n) { return true; -}} +} + -if(! function_exists('ref_session_read')) { function ref_session_read ($id) { global $session_exists; if(x($id)) @@ -21,9 +45,9 @@ function ref_session_read ($id) { return $r[0]['data']; } return ''; -}} +} + -if(! function_exists('ref_session_write')) { function ref_session_write ($id,$data) { global $session_exists, $session_expire; if(! $id || ! $data) { @@ -44,25 +68,25 @@ function ref_session_write ($id,$data) { dbesc($id), dbesc($default_expire), dbesc($data)); return true; -}} +} + -if(! function_exists('ref_session_close')) { function ref_session_close() { return true; -}} +} + -if(! function_exists('ref_session_destroy')) { function ref_session_destroy ($id) { q("DELETE FROM `session` WHERE `sid` = '%s'", dbesc($id)); return true; -}} +} + -if(! function_exists('ref_session_gc')) { function ref_session_gc($expire) { - q("DELETE FROM `session` WHERE `expire` < %d", dbesc(time())); - q("OPTIMIZE TABLE `sess_data`"); + q("DELETE FROM session WHERE expire < %d", dbesc(time())); + q("OPTIMIZE TABLE session"); return true; -}} +} $gc_probability = 50; @@ -71,6 +95,4 @@ ini_set('session.use_only_cookies', 1); ini_set('session.cookie_httponly', 1); -session_set_save_handler ('ref_session_open', 'ref_session_close', - 'ref_session_read', 'ref_session_write', - 'ref_session_destroy', 'ref_session_gc'); +session_set_save_handler ('ref_session_open', 'ref_session_close', 'ref_session_read', 'ref_session_write', 'ref_session_destroy', 'ref_session_gc'); diff --git a/include/socgraph.php b/include/socgraph.php index aa5a24e89..e12da5862 100644 --- a/include/socgraph.php +++ b/include/socgraph.php @@ -1,10 +1,17 @@ -<?php +<?php /** @file */ +require_once('include/dir_fns.php'); require_once('include/zot.php'); /* * poco_load * + * xchan is your connection + * We will load their friend list, and store in xlink_xchan your connection hash and xlink_link the hash for each connection + * If xchan isn't provided we will load the list of people from url who have indicated they are willing to be friends with + * new folks and add them to xlink with no xlink_xchan. + * + * Old behaviour: (documentation only): * Given a contact-id (minimum), load the PortableContacts friend list for that contact, * and add the entries to the gcontact (Global Contact) table, or update existing entries * if anything (name or photo) has changed. @@ -19,53 +26,96 @@ require_once('include/zot.php'); -function poco_load($xchan = null,$url = null) { +function poco_load($xchan = '',$url = null) { $a = get_app(); if($xchan && ! $url) { - $r = q("select xchan_connurl from xchan where xchan_hash = %d limit 1", - intval($xchan) + $r = q("select xchan_connurl from xchan where xchan_hash = '%s' limit 1", + dbesc($xchan) ); if($r) { $url = $r[0]['xchan_connurl']; - $uid = $r[0]['abook_channel']; } } - if(! $url) + if(! $url) { + logger('poco_load: no url'); return; + } - $url = $url . '?fields=displayName,hash,urls,photos' ; + $url = $url . '?f=&fields=displayName,hash,urls,photos,rating' ; logger('poco_load: ' . $url, LOGGER_DEBUG); $s = z_fetch_url($url); - logger('poco_load: returns ' . print_r($s,true), LOGGER_DATA); - - if(! $s['success']) + if(! $s['success']) { + if($s['return_code'] == 401) + logger('poco_load: protected'); + elseif($s['return_code'] == 404) + logger('poco_load: nothing found'); + else + logger('poco_load: returns ' . print_r($s,true)); return; + } $j = json_decode($s['body'],true); + if(! $j) { + logger('poco_load: unable to json_decode returned data.'); + return; + } + logger('poco_load: ' . print_r($j,true),LOGGER_DATA); - if(! x($j,'entry') && is_array($j['entry'])) + if($xchan) { + if(array_key_exists('chatrooms',$j) && is_array($j['chatrooms'])) { + foreach($j['chatrooms'] as $room) { + $r = q("select * from xchat where xchat_url = '%s' and xchat_xchan = '%s' limit 1", + dbesc($room['url']), + dbesc($xchan) + ); + if($r) { + q("update xchat set xchat_edited = '%s' where xchat_id = %d limit 1", + dbesc(datetime_convert()), + intval($r[0]['xchat_id']) + ); + } + else { + $x = q("insert into xchat ( xchat_url, xchat_desc, xchat_xchan, xchat_edited ) + values ( '%s', '%s', '%s', '%s' ) ", + dbesc(escape_tags($room['url'])), + dbesc(escape_tags($room['desc'])), + dbesc($xchan), + dbesc(datetime_convert()) + ); + } + } + } + q("delete from xchat where xchat_edited < UTC_TIMESTAMP() - INTERVAL 7 DAY and xchat_xchan = '%s' ", + dbesc($xchan) + ); + } + + if(! ((x($j,'entry')) && (is_array($j['entry'])))) { + logger('poco_load: no entries'); return; + } $total = 0; foreach($j['entry'] as $entry) { - $total ++; $profile_url = ''; $profile_photo = ''; $address = ''; $name = ''; $hash = ''; + $rating = 0; - $name = $entry['displayName']; - $hash = $entry['hash']; + $name = $entry['displayName']; + $hash = $entry['hash']; + $rating = ((array_key_exists('rating',$entry)) ? intval($entry['rating']) : 0); if(x($entry,'urls') && is_array($entry['urls'])) { foreach($entry['urls'] as $url) { @@ -88,8 +138,10 @@ function poco_load($xchan = null,$url = null) { } } - if((! $name) || (! $profile_url) || (! $profile_photo) || (! $hash) || (! $address)) + if((! $name) || (! $profile_url) || (! $profile_photo) || (! $hash) || (! $address)) { + logger('poco_load: missing data'); continue; + } $x = q("select xchan_hash from xchan where xchan_hash = '%s' limit 1", dbesc($hash) @@ -105,73 +157,77 @@ function poco_load($xchan = null,$url = null) { if($j) import_xchan($j); } + $x = q("select xchan_hash from xchan where xchan_hash = '%s' limit 1", + dbesc($hash) + ); + if(! $x) { + continue; + } + } + else { + continue; } } + $total ++; + $r = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' limit 1", dbesc($xchan), dbesc($hash) ); + if(! $r) { - q("insert into xlink ( xlink_xchan, xlink_link, xlink_updated ) values ( '%s', '%s', '%s' ) ", + q("insert into xlink ( xlink_xchan, xlink_link, xlink_rating, xlink_updated ) values ( '%s', '%s', %d, '%s' ) ", dbesc($xchan), dbesc($hash), + intval($rating), dbesc(datetime_convert()) ); } else { - q("update xlink set xlink_updated = '%s' where xlink_id = %d limit 1", + q("update xlink set xlink_updated = '%s', xlink_rating = %d where xlink_id = %d limit 1", dbesc(datetime_convert()), + intval($rating), intval($r[0]['xlink_id']) ); } - } logger("poco_load: loaded $total entries",LOGGER_DEBUG); - q("delete from xlink where xlink_xchan = '%s' and xlink_updated` < UTC_TIMESTAMP - INTERVAL 2 DAY", + q("delete from xlink where xlink_xchan = '%s' and xlink_updated < UTC_TIMESTAMP() - INTERVAL 2 DAY", dbesc($xchan) ); } -function count_common_friends($uid,$cid) { +function count_common_friends($uid,$xchan) { - $r = q("SELECT count(*) as `total` - FROM `glink` left join `gcontact` on `glink`.`gcid` = `gcontact`.`id` - where `glink`.`cid` = %d and `glink`.`uid` = %d - and `gcontact`.`nurl` in (select nurl from contact where uid = %d and self = 0 and blocked = 0 and hidden = 0 and id != %d ) ", - intval($cid), - intval($uid), - intval($uid), - intval($cid) + $r = q("SELECT count(xlink_id) as total from xlink where xlink_xchan = '%s' and xlink_link in + (select abook_xchan from abook where abook_xchan != '%s' and abook_channel = %d and abook_flags = 0 )", + dbesc($xchan), + dbesc($xchan), + intval($uid) ); -// logger("count_common_friends: $uid $cid {$r[0]['total']}"); - if(count($r)) + if($r) return $r[0]['total']; return 0; - } -function common_friends($uid,$cid,$start = 0,$limit=9999,$shuffle = false) { +function common_friends($uid,$xchan,$start = 0,$limit=100000000,$shuffle = false) { if($shuffle) $sql_extra = " order by rand() "; else - $sql_extra = " order by `gcontact`.`name` asc "; + $sql_extra = " order by xchan_name asc "; - $r = q("SELECT `gcontact`.* - FROM `glink` left join `gcontact` on `glink`.`gcid` = `gcontact`.`id` - where `glink`.`cid` = %d and `glink`.`uid` = %d - and `gcontact`.`nurl` in (select nurl from contact where uid = %d and self = 0 and blocked = 0 and hidden = 0 and id != %d ) - $sql_extra limit %d, %d", - intval($cid), + $r = q("SELECT * from xchan left join xlink on xlink_link = xchan_hash where xlink_xchan = '%s' and xlink_link in + (select abook_xchan from abook where abook_xchan != '%s' and abook_channel = %d and abook_flags = 0 ) $sql_extra limit %d, %d", + dbesc($xchan), + dbesc($xchan), intval($uid), - intval($uid), - intval($cid), intval($start), intval($limit) ); @@ -253,79 +309,90 @@ function all_friends($uid,$cid,$start = 0, $limit = 80) { -function suggestion_query($uid, $start = 0, $limit = 80) { +function suggestion_query($uid, $myxchan, $start = 0, $limit = 80) { - if(! $uid) + if((! $uid) || (! $myxchan)) return array(); - $r = q("SELECT count(glink.gcid) as `total`, gcontact.* from gcontact - left join glink on glink.gcid = gcontact.id - where uid = %d and not gcontact.nurl in ( select nurl from contact where uid = %d ) - and not gcontact.name in ( select name from contact where uid = %d ) - and not gcontact.id in ( select gcid from gcign where uid = %d ) - group by glink.gcid order by total desc limit %d, %d ", - intval($uid), + $r = q("SELECT count(xlink_xchan) as `total`, xchan.* from xchan + left join xlink on xlink_link = xchan_hash + where xlink_xchan in ( select abook_xchan from abook where abook_channel = %d ) + and not xlink_link in ( select abook_xchan from abook where abook_channel = %d ) + and not xlink_link in ( select xchan from xign where uid = %d ) + and xlink_xchan != '' + and not ( xchan_flags & %d ) + and not ( xchan_flags & %d ) + group by xchan_hash order by total desc limit %d, %d ", intval($uid), intval($uid), intval($uid), + intval(XCHAN_FLAGS_HIDDEN), + intval(XCHAN_FLAGS_DELETED), intval($start), intval($limit) ); - if(count($r) && count($r) >= ($limit -1)) + if($r && count($r) >= ($limit -1)) return $r; - $r2 = q("SELECT gcontact.* from gcontact - left join glink on glink.gcid = gcontact.id - where glink.uid = 0 and glink.cid = 0 and glink.zcid = 0 and not gcontact.nurl in ( select nurl from contact where uid = %d ) - and not gcontact.name in ( select name from contact where uid = %d ) - and not gcontact.id in ( select gcid from gcign where uid = %d ) - order by rand() limit %d, %d ", - intval($uid), + $r2 = q("SELECT count(xlink_link) as `total`, xchan.* from xchan + left join xlink on xlink_link = xchan_hash + where xlink_xchan = '' + and not xlink_link in ( select abook_xchan from abook where abook_channel = %d ) + and not xlink_link in ( select xchan from xign where uid = %d ) + and not ( xchan_flags & %d ) + and not ( xchan_flags & %d ) + group by xchan_hash order by total desc limit %d, %d ", intval($uid), intval($uid), + intval(XCHAN_FLAGS_HIDDEN), + intval(XCHAN_FLAGS_DELETED), intval($start), intval($limit) ); + if(is_array($r) && is_array($r2)) + return array_merge($r,$r2); - return array_merge($r,$r2); - + return array(); } function update_suggestions() { $a = get_app(); - $done = array(); + $dirmode = get_config('system','directory_mode'); + if($dirmode === false) + $dirmode = DIRECTORY_MODE_NORMAL; - poco_load(0,0,0,$a->get_baseurl() . '/poco'); + if(($dirmode == DIRECTORY_MODE_PRIMARY) || ($dirmode == DIRECTORY_MODE_STANDALONE)) { + $url = z_root() . '/sitelist'; + } + else { + $directory = find_upstream_directory($dirmode); + $url = $directory['url'] . '/sitelist'; + } + if(! $url) + return; - $done[] = $a->get_baseurl() . '/poco'; - if(strlen(get_config('system','directory_submit_url'))) { - $x = fetch_url('http://dir.friendica.com/pubsites'); - if($x) { - $j = json_decode($x); - if($j->entries) { - foreach($j->entries as $entry) { - $url = $entry->url . '/poco'; - if(! in_array($url,$done)) - poco_load(0,0,0,$entry->url . '/poco'); - } - } - } - } - $r = q("select distinct(poco) as poco from contact where network = '%s'", - dbesc(NETWORK_DFRN) - ); + $ret = z_fetch_url($url); + + if($ret['success']) { + + // We will grab fresh data once a day via the poller. Remove anything over a week old because + // the targets may have changed their preferences and don't want to be suggested - and they + // may have simply gone away. - if(count($r)) { - foreach($r as $rr) { - $base = substr($rr['poco'],0,strrpos($rr['poco'],'/')); - if(! in_array($base,$done)) - poco_load(0,0,0,$base); + $r = q("delete from xlink where xlink_xchan = '' and xlink_updated < UTC_TIMESTAMP() - INTERVAL 7 DAY"); + + + $j = json_decode($ret['body'],true); + if($j && $j['success']) { + foreach($j['entries'] as $host) { + poco_load('',$host['url'] . '/poco'); + } } } } diff --git a/include/spam.php b/include/spam.php new file mode 100644 index 000000000..8b158b7ae --- /dev/null +++ b/include/spam.php @@ -0,0 +1,35 @@ +<?php /** @file */ + + +function string_splitter($s) { + + if(! $s) + return array(); + + $s = preg_replace('/\pP+/','',$s); + + $x = mb_split("\[|\]|\s",$s); + + $ret = array(); + if($x) { + foreach($x as $y) { + if(mb_strlen($y) > 2) + $ret[] = substr($y,0,64); + } + } + return $ret; +} + + + +function get_words($uid,$list) { + + stringify($list,true); + + $r = q("select * from spam where term in ( " . $list . ") and uid = %d", + intval($uid) + ); + + return $r; +} + diff --git a/include/system_unavailable.php b/include/system_unavailable.php index bd7196cdf..dfe7c5e6b 100644 --- a/include/system_unavailable.php +++ b/include/system_unavailable.php @@ -1,4 +1,4 @@ -<?php +<?php /** @file */ function system_down() { echo <<< EOT diff --git a/include/taxonomy.php b/include/taxonomy.php new file mode 100644 index 000000000..92003328f --- /dev/null +++ b/include/taxonomy.php @@ -0,0 +1,353 @@ +<?php /** @file */ + +// post categories and "save to file" use the same item.file table for storage. +// We will differentiate the different uses by wrapping categories in angle brackets +// and save to file categories in square brackets. +// To do this we need to escape these characters if they appear in our tag. + +function file_tag_encode($s) { + return str_replace(array('<','>','[',']'),array('%3c','%3e','%5b','%5d'),$s); +} + +function file_tag_decode($s) { + return str_replace(array('%3c','%3e','%5b','%5d'),array('<','>','[',']'),$s); +} + +function file_tag_file_query($table,$s,$type = 'file') { + + if($type == 'file') + $termtype = TERM_FILE; + else + $termtype = TERM_CATEGORY; + + return sprintf(" AND " . (($table) ? dbesc($table) . '.' : '') . "id in (select term.oid from term where term.type = %d and term.term = '%s' and term.uid = " . (($table) ? dbesc($table) . '.' : '') . "uid ) ", + intval($termtype), + protect_sprintf(dbesc($s)) + ); +} + +function term_query($table,$s,$type = TERM_UNKNOWN) { + + return sprintf(" AND " . (($table) ? dbesc($table) . '.' : '') . "id in (select term.oid from term where term.type = %d and term.term = '%s' and term.uid = " . (($table) ? dbesc($table) . '.' : '') . "uid ) ", + intval($type), + protect_sprintf(dbesc($s)) + ); +} + + +function store_item_tag($uid,$iid,$otype,$type,$term,$url = '') { + if(! $term) + return false; + $r = q("select * from term + where uid = %d and oid = %d and otype = %d and type = %d + and term = '%s' and url = '%s' ", + intval($uid), + intval($iid), + intval($otype), + intval($type), + dbesc($term), + dbesc($url) + ); + if($r) + return false; + $r = q("insert into term (uid, oid, otype, type, term, url) + values( %d, %d, %d, %d, '%s', '%s') ", + intval($uid), + intval($iid), + intval($otype), + intval($type), + dbesc($term), + dbesc($url) + ); + return $r; +} + +function get_terms_oftype($arr,$type) { + $ret = array(); + if(! (is_array($arr) && count($arr))) + return $ret; + + if(! is_array($type)) + $type = array($type); + + foreach($type as $t) + foreach($arr as $x) + if($x['type'] == $t) + $ret[] = $x; + return $ret; +} + +function format_term_for_display($term) { + $s = ''; + if($term['type'] == TERM_HASHTAG) + $s .= '#'; + elseif($term['type'] == TERM_MENTION) + $s .= '@'; + else + return $s; + + if($term['url']) + $s .= '<a href="' . $term['url'] . '">' . htmlspecialchars($term['term'], ENT_COMPAT,'UTF-8') . '</a>'; + else + $s .= htmlspecialchars($term['term'], ENT_COMPAT,'UTF-8'); + return $s; +} + +// Tag cloud functions - need to be adpated to this database format + + +function tagadelic($uid, $count = 0, $authors = '', $flags = 0, $restrict = 0, $type = TERM_HASHTAG) { + + $sql_options = ''; + $count = intval($count); + + if($flags) + $sql_options .= " and ((item_flags & " . intval($flags) . ") = " . intval($flags) . ") "; + + if($authors) { + if(! is_array($authors)) + $authors = array($authors); + stringify_array_elms($authors,true); + $sql_options .= " and author_xchan in (" . implode(',',$authors) . ") "; + } + + // Fetch tags + $r = q("select term, count(term) as total from term left join item on term.oid = item.id + where term.uid = %d and term.type = %d + and otype = %d and item_restrict = %d and item_private = 0 + $sql_options + group by term order by total desc %s", + intval($uid), + intval($type), + intval(TERM_OBJ_POST), + intval($restrict), + ((intval($count)) ? "limit $count" : '') + ); + + if(! $r) + return array(); + + // Find minimum and maximum log-count. + $tags = array(); + $min = 1e9; + $max = -1e9; + + $x = 0; + foreach($r as $rr) { + $tags[$x][0] = $rr['term']; + $tags[$x][1] = log($rr['total']); + $tags[$x][2] = 0; + $min = min($min,$tags[$x][1]); + $max = max($max,$tags[$x][1]); + $x ++; + } + + usort($tags,'tags_sort'); + + $range = max(.01, $max - $min) * 1.0001; + + for($x = 0; $x < count($tags); $x ++) { + $tags[$x][2] = 1 + floor(5 * ($tags[$x][1] - $min) / $range); + } + + return $tags; +} + +function tags_sort($a,$b) { + if($a[0] == $b[0]) + return 0; + return((strtolower($a[0]) < strtolower($b[0])) ? -1 : 1); +} + + +function dir_tagadelic($count = 0) { + + $sql_options = ''; + $count = intval($count); + + // Fetch tags + $r = q("select xtag_term, count(xtag_term) as total from xtag + group by xtag_term order by total desc %s", + ((intval($count)) ? "limit $count" : '') + ); + + if(! $r) + return array(); + + // Find minimum and maximum log-count. + $tags = array(); + $min = 1e9; + $max = -1e9; + + $x = 0; + foreach($r as $rr) { + $tags[$x][0] = $rr['xtag_term']; + $tags[$x][1] = log($rr['total']); + $tags[$x][2] = 0; + $min = min($min,$tags[$x][1]); + $max = max($max,$tags[$x][1]); + $x ++; + } + + usort($tags,'tags_sort'); + + $range = max(.01, $max - $min) * 1.0001; + + for($x = 0; $x < count($tags); $x ++) { + $tags[$x][2] = 1 + floor(5 * ($tags[$x][1] - $min) / $range); + } + + return $tags; +} + + +function tagblock($link,$uid,$count = 0,$authors = '',$flags = 0,$restrict = 0,$type = TERM_HASHTAG) { + $o = ''; + $tab = 0; + $r = tagadelic($uid,$count,$authors,$flags,$restrict,$type); + + if($r) { + $o = '<div class="tagblock widget"><h3>' . t('Tags') . '</h3><div class="tags" align="center">'; + foreach($r as $rr) { + $o .= '<a href="'.$link .'/' . '?f=&tag=' . urlencode($rr[0]).'" class="tag'.$rr[2].'">'.$rr[0].'</a> ' . "\r\n"; + } + $o .= '</div></div>'; + } + return $o; +} + +function dir_tagblock($link,$r) { + $o = ''; + $tab = 0; + + if(! $r) + $r = get_app()->data['directory_keywords']; + + if($r) { + $o = '<div class="dirtagblock widget"><h3>' . t('Keywords') . '</h3><div class="tags" align="center">'; + foreach($r as $rr) { + $o .= '<a href="'.$link .'/' . '?f=&keywords=' . urlencode($rr['term']).'" class="tag'.$rr['normalise'].'" rel="nofollow" >'.$rr['term'].'</a> ' . "\r\n"; + } + $o .= '</div></div>'; + } + return $o; +} + + + + + + + /** + * verbs: [0] = first person singular, e.g. "I want", [1] = 3rd person singular, e.g. "Bill wants" + * We use the first person form when creating an activity, but the third person for use in activities + * FIXME: There is no accounting for verb gender for languages where this is significant. We may eventually + * require obj_verbs() to provide full conjugations and specify which form to use in the $_REQUEST params to this module. + */ + + + +function obj_verbs() { + $verbs = array( + 'has' => array( t('have'), t('has')), + 'wants' => array( t('want'), t('wants')), + 'likes' => array( t('like'), t('likes')), + 'dislikes' => array( t('dislike'), t('dislikes')), + ); + + $arr = array('verbs' => $verbs); + call_hooks('obj_verbs', $arr); + return $arr['verbs']; +} + + +function obj_verb_selector($current = '') { + $verbs = obj_verbs(); + $o .= '<select class="obj-verb-selector" name="verb" >'; + foreach($verbs as $k => $v) { + $selected = (($k == $current) ? ' selected="selected" ' : ''); + $o .= '<option value="' . urlencode($k) . '"' . $selected . '>' . $v[1] . '</option>'; + } + $o .= '</select>'; + return $o; + +} + +function get_things($profile_hash,$uid) { + + $sql_extra = (($profile_hash) ? " and obj_page = '" . $profile_hash . "' " : ''); + + $r = q("select * from obj left join term on obj_obj = term_hash where term_hash != '' and uid = %d and obj_type = %d $sql_extra order by obj_verb, term", + intval($uid), + intval(TERM_OBJ_THING) + ); + + $things = $sorted_things = null; + + $profile_hashes = array(); + + if($r) { + + // if no profile_hash was specified (display on profile page mode), match each of the things to a profile name + // (list all my things mode). This is harder than it sounds. + + foreach($r as $rr) { + $rr['profile_name'] = ''; + if(! in_array($rr['term_hash'],$profile_hashes)) + $profile_hashes[] = $rr['term_hash']; + } + stringify_array_elms($profile_hashes); + if(! $profile_hash) { + $exp = explode(',',$profile_hashes); + $p = q("select profile_guid as hash, profile_name as name from profile where profile_guid in ( $exp ) "); + if($p) { + foreach($r as $rr) { + foreach($p as $pp) { + if($rr['obj_page'] == $pp['hash']) { + $rr['profile_name'] == $pp['name']; + } + } + } + } + } + + $things = array(); + + // Use the system obj_verbs array as a sort key, since we don't really + // want an alphabetic sort. To change the order, use a plugin to + // alter the obj_verbs() array or alter it in code. Unknown verbs come + // after the known ones - in no particular order. + + $v = obj_verbs(); + foreach($v as $k => $foo) + $things[$k] = null; + foreach($r as $rr) { + + $l = q("select xchan_name, xchan_url from likes left join xchan on likee = xchan_hash where + target_type = '%s' and target_id = '%s' and channel_id = %d", + dbesc(ACTIVITY_OBJ_THING), + dbesc($rr['term_hash']), + intval($uid) + ); + + for($x = 0; $x < count($l); $x ++) + $l[$x]['xchan_url'] = zid($l[$x]['xchan_url']); + + if(! $things[$rr['obj_verb']]) + $things[$rr['obj_verb']] = array(); + $things[$rr['obj_verb']][] = array('term' => $rr['term'],'url' => $rr['url'],'img' => $rr['imgurl'], 'profile' => $rr['profile_name'],'term_hash' => $rr['term_hash'], 'likes' => $l,'like_count' => count($l),'like_label' => tt('Like','Likes',count($l),'noun')); + + } + $sorted_things = array(); + if($things) { + foreach($things as $k => $v) { + if(is_array($things[$k])) { + $sorted_things[$k] = $v; + } + } + } + } +//logger('things: ' . print_r($sorted_things,true)); + return $sorted_things; + +}
\ No newline at end of file diff --git a/include/template_processor.php b/include/template_processor.php index 61526e570..74acc9c67 100644..100755 --- a/include/template_processor.php +++ b/include/template_processor.php @@ -1,7 +1,11 @@ <?php + require_once 'include/ITemplateEngine.php'; + define ("KEY_NOT_EXISTS", '^R_key_not_Exists^'); - class Template { + class Template implements ITemplateEngine { + static $name ="internal"; + var $r; var $search; var $replace; @@ -244,9 +248,13 @@ return $s; } - - public function replace($s, $r) { - $t1 = dba_timer(); + + private function replace($s,$r) { + $this->replace_macros($s, $r); + } + + // TemplateEngine interface + public function replace_macros($s, $r) { $this->r = $r; $s = $this->_build_nodes($s); @@ -265,14 +273,18 @@ $os=$s; $count++; $s = $this->var_replace($s); } - $t3 = dba_timer(); -// logger('macro timer: ' . sprintf('%01.4f %01.4f',$t3 - $t2, $t2 - $t1)); - return $s; } + + public function get_markup_template($file, $root='') { + $template_file = theme_include($file, $root); + if ($template_file) { + $content = file_get_contents($template_file); + } + return $content; + } } - $t = new Template; diff --git a/include/text.php b/include/text.php index 5438aae73..b5b8ec41a 100644..100755 --- a/include/text.php +++ b/include/text.php @@ -1,28 +1,28 @@ -<?php +<?php /** @file */ -// This is our template processor. -// $s is the string requiring macro substitution. -// $r is an array of key value pairs (search => replace) -// returns substituted string. -// WARNING: this is pretty basic, and doesn't properly handle search strings that are substrings of each other. -// For instance if 'test' => "foo" and 'testing' => "bar", testing could become either bar or fooing, -// depending on the order in which they were declared in the array. require_once("include/template_processor.php"); +require_once("include/friendica_smarty.php"); -if(! function_exists('replace_macros')) { +/** + * This is our template processor + * + * @param string|FriendicaSmarty $s the string requiring macro substitution, + * or an instance of FriendicaSmarty + * @param array $r key value pairs (search => replace) + * @return string substituted string + */ function replace_macros($s,$r) { - global $t; + $a = get_app(); + + $arr = array('template' => $s, 'params' => $r); + call_hooks('replace_macros', $arr); - //$ts = microtime(); - $r = $t->replace($s,$r); - //$tt = microtime() - $ts; + $t = $a->template_engine(); + $output = $t->replace_macros($arr['template'],$arr['params']); - //$a = get_app(); - //$a->page['debug'] .= "$tt <br>\n"; - return template_unescape($r); - -}} + return $output; +} // random string, there are 86 characters max in text mode, 128 for hex @@ -31,13 +31,13 @@ function replace_macros($s,$r) { define('RANDOM_STRING_HEX', 0x00 ); define('RANDOM_STRING_TEXT', 0x01 ); -if(! function_exists('random_string')) { + function random_string($size = 64,$type = RANDOM_STRING_HEX) { // generate a bit of entropy and run it through the whirlpool $s = hash('whirlpool', (string) rand() . uniqid(rand(),true) . (string) rand(),(($type == RANDOM_STRING_TEXT) ? true : false)); $s = (($type == RANDOM_STRING_TEXT) ? str_replace("\n","",base64url_encode($s,true)) : $s); return(substr($s,0,$size)); -}} +} /** * This is our primary input filter. @@ -53,32 +53,94 @@ function random_string($size = 64,$type = RANDOM_STRING_HEX) { * They will be replaced with safer brackets. This may be filtered further * if these are not allowed either. * + * @param string $string Input string + * @return string Filtered string */ -if(! function_exists('notags')) { + function notags($string) { return(str_replace(array("<",">"), array('[',']'), $string)); // High-bit filter no longer used // return(str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string)); -}} +} // use this on "body" or "content" input where angle chars shouldn't be removed, // and allow them to be safely displayed. -if(! function_exists('escape_tags')) { + + +/** + * use this on "body" or "content" input where angle chars shouldn't be removed, + * and allow them to be safely displayed. + * @param string $string + * @return string + */ function escape_tags($string) { return(htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false)); -}} +} + + +function z_input_filter($channel_id,$s,$type = 'text/bbcode') { + + if($type === 'text/bbcode') + return escape_tags($s); + if($type === 'text/markdown') + return escape_tags($s); + if($type == 'text/plain') + return escape_tags($s); + $r = q("select account_id, account_roles from account left join channel on channel_account_id = account_id where channel_id = %d limit 1", + intval($channel_id) + ); + if($r && ($r[0]['account_roles'] & ACCOUNT_ROLE_ALLOWCODE)) { + if(local_user() && (get_account_id() == $r[0]['account_id'])) { + return $s; + } + } + + if($type === 'text/html') + return purify_html($s); + + return escape_tags($s); + +} + + + + + +function purify_html($s) { + require_once('library/HTMLPurifier.auto.php'); + require_once('include/html2bbcode.php'); + +// FIXME this function has html output, not bbcode - so safely purify these +// $s = html2bb_video($s); +// $s = oembed_html2bbcode($s); + + $config = HTMLPurifier_Config::createDefault(); + $config->set('Cache.DefinitionImpl', null); + + $purifier = new HTMLPurifier($config); + return $purifier->purify($s); +} + + + // generate a string that's random, but usually pronounceable. // used to generate initial passwords -if(! function_exists('autoname')) { + +/** + * generate a string that's random, but usually pronounceable. + * used to generate initial passwords + * @param int $len + * @return string + */ function autoname($len) { if($len <= 0) @@ -147,18 +209,24 @@ function autoname($len) { if(substr($word,-1) == 'q') $word = substr($word,0,-1); return $word; -}} +} // escape text ($str) for XML transport // returns escaped text. -if(! function_exists('xmlify')) { + +/** + * escape text ($str) for XML transport + * @param string $str + * @return string Escaped text. + */ function xmlify($str) { $buffer = ''; - for($x = 0; $x < mb_strlen($str); $x ++) { - $char = $str[$x]; + $len = mb_strlen($str); + for($x = 0; $x < $len; $x ++) { + $char = mb_substr($str,$x,1); switch( $char ) { @@ -189,25 +257,32 @@ function xmlify($str) { } $buffer = trim($buffer); return($buffer); -}} +} // undo an xmlify // pass xml escaped text ($s), returns unescaped text -if(! function_exists('unxmlify')) { + function unxmlify($s) { $ret = str_replace('&','&', $s); $ret = str_replace(array('<','>','"','''),array('<','>','"',"'"),$ret); return $ret; -}} +} // convenience wrapper, reverse the operation "bin2hex" +// This is a built-in function in php >= 5.4 + if(! function_exists('hex2bin')) { function hex2bin($s) { if(! (is_string($s) && strlen($s))) return ''; + if(strlen($s) & 1) { + logger('hex2bin: illegal hex string: ' . $s); + return $s; + } + if(! ctype_xdigit($s)) { return($s); } @@ -215,6 +290,7 @@ function hex2bin($s) { return(pack("H*",$s)); }} + // Automatic pagination. // To use, get the count of total items. // Then call $a->set_pager_total($number_items); @@ -225,7 +301,7 @@ function hex2bin($s) { // will limit the results to the correct items for the current page. // The actual page handling is then accomplished at the application layer. -if(! function_exists('paginate')) { + function paginate(&$a) { $o = ''; $stripped = preg_replace('/(&page=[0-9]*)/','',$a->query_string); @@ -279,36 +355,39 @@ function paginate(&$a) { $o .= '</div>'."\r\n"; } return $o; -}} +} + + +function alt_pager(&$a, $i, $more = '', $less = '') { + + $o = ''; + + if(! $more) + $more = t('older'); + if(! $less) + $less = t('newer'); -if(! function_exists('alt_pager')) { -function alt_pager(&$a, $i) { - $o = ''; $stripped = preg_replace('/(&page=[0-9]*)/','',$a->query_string); $stripped = str_replace('q=','',$stripped); $stripped = trim($stripped,'/'); $pagenum = $a->pager['page']; - $url = $a->get_baseurl() . '/' . $stripped; - - $o .= '<div class="pager">'; - - if($a->pager['page']>1) - $o .= "<a href=\"$url"."&page=".($a->pager['page'] - 1).'">' . t('newer') . '</a>'; - if($i>0) { - if($a->pager['page']>1) - $o .= " - "; - $o .= "<a href=\"$url"."&page=".($a->pager['page'] + 1).'">' . t('older') . '</a>'; - } - + $url = $a->get_baseurl() . '/' . $stripped; - $o .= '</div>'."\r\n"; + return replace_macros(get_markup_template('alt_pager.tpl'),array( + '$has_less' => (($a->pager['page'] > 1) ? true : false), + '$has_more' => (($i > 0 && $i >= $a->pager['itemspage']) ? true : false), + '$less' => $less, + '$more' => $more, + '$url' => $url, + '$prevpage' => $a->pager['page'] - 1, + '$nextpage' => $a->pager['page'] + 1, + )); - return $o; -}} +} // Turn user/group ACLs stored as angle bracketed text into arrays -if(! function_exists('expand_acl')) { + function expand_acl($s) { // turn string array of angle-bracketed elements into string array @@ -325,22 +404,22 @@ function expand_acl($s) { } } return $ret; -}} +} // Used to wrap ACL elements in angle brackets for storage -if(! function_exists('sanitise_acl')) { + function sanitise_acl(&$item) { if(strlen($item)) $item = '<' . notags(trim($item)) . '>'; else unset($item); -}} +} // Convert an ACL array to a storable string -if(! function_exists('perms2str')) { + function perms2str($p) { $ret = ''; @@ -354,45 +433,45 @@ function perms2str($p) { $ret = implode('',$tmp); } return $ret; -}} +} // generate a guaranteed unique (for this domain) item ID for ATOM // safe from birthday paradox -if(! function_exists('item_message_id')) { + function item_message_id() { do { $dups = false; $hash = random_string(); - $uri = $hash . '@' . get_app()->get_hostname(); + $mid = $hash . '@' . get_app()->get_hostname(); - $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' LIMIT 1", - dbesc($uri)); + $r = q("SELECT id FROM item WHERE mid = '%s' LIMIT 1", + dbesc($mid)); if(count($r)) $dups = true; } while($dups == true); - return $uri; -}} + return $mid; +} // Generate a guaranteed unique photo ID. // safe from birthday paradox -if(! function_exists('photo_new_resource')) { + function photo_new_resource() { do { $found = false; $resource = hash('md5',uniqid(mt_rand(),true)); - $r = q("SELECT `id` FROM `photo` WHERE `resource_id` = '%s' LIMIT 1", + $r = q("SELECT id FROM photo WHERE resource_id = '%s' LIMIT 1", dbesc($resource) ); if(count($r)) $found = true; } while($found == true); return $resource; -}} +} @@ -407,15 +486,14 @@ function photo_new_resource() { // pass the attribute string as $attr and the attribute you // are looking for as $s - returns true if found, otherwise false -if(! function_exists('attribute_contains')) { function attribute_contains($attr,$s) { $a = explode(' ', $attr); if(count($a) && in_array($s,$a)) return true; return false; -}} +} + -if(! function_exists('logger')) { function logger($msg,$level = 0) { // turn off logger in install mode global $a; @@ -432,15 +510,48 @@ function logger($msg,$level = 0) { @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND); return; -}} +} + + +// This is a special logging facility for developers. It allows one to target specific things to trace/debug +// and is identical to logger() with the exception of the log filename. This allows one to isolate specific +// calls while allowing logger() to paint a bigger picture of overall activity and capture more detail. +// If you find dlogger() calls in checked in code, you are free to remove them - so as to provide a noise-free +// development environment which responds to events you are targetting personally. + + +function dlogger($msg,$level = 0) { + // turn off logger in install mode + global $a; + global $db; + + if(($a->module == 'install') || (! ($db && $db->connected))) return; + + $debugging = get_config('system','debugging'); + $loglevel = intval(get_config('system','loglevel')); + $logfile = get_config('system','dlogfile'); + + if((! $debugging) || (! $logfile) || ($level > $loglevel)) + return; + + @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $msg . "\n", FILE_APPEND); + return; +} + + +function profiler($t1,$t2,$label) { + if(file_exists('profiler.out') && $t1 && t2) + @file_put_contents('profiler.out', sprintf('%01.4f %s',$t2 - $t1,$label) . "\n", FILE_APPEND); +} + -if(! function_exists('activity_match')) { function activity_match($haystack,$needle) { if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA))) return true; + return false; -}} +} // Pull out all #hashtags and @person tags from $s; @@ -451,7 +562,7 @@ function activity_match($haystack,$needle) { // Returns array of tags found, or empty array. -if(! function_exists('get_tags')) { + function get_tags($s) { $ret = array(); @@ -459,9 +570,24 @@ function get_tags($s) { $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s); + // ignore anything in [style= ] + + $s = preg_replace('/\[style=(.*?)\]/sm','',$s); + + // match any double quoted tags + + if(preg_match_all('/([@#]\"\;.*?\"\;)/',$s,$match)) { + foreach($match[1] as $mtch) { + $ret[] = $mtch; + } + } + // Match full names against @tags including the space between first and last // We will look these up afterward to see if they are full names or not recognisable. + + + if(preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A@,:?]+)([ \x0D\x0A@,:?]|$)/',$s,$match)) { foreach($match[1] as $mtch) { if(strstr($mtch,"]")) { @@ -487,28 +613,65 @@ function get_tags($s) { if(substr($mtch,-1,1) === '.') $mtch = substr($mtch,0,-1); // ignore strictly numeric tags like #1 - if((strpos($mtch,'#') === 0) && ctype_digit(substr($mtch,1))) + if((strpos($mtch,'#') === 0) && ( ctype_digit(substr($mtch,1)) || substr($mtch,1,1) === '^')) continue; // try not to catch url fragments if(strpos($s,$mtch) && preg_match('/[a-zA-z0-9\/]/',substr($s,strpos($s,$mtch)-1,1))) continue; + // or quote remnants from the quoted strings we already picked out earlier + if(strpos($mtch,'"')) + continue; + $ret[] = $mtch; } } + + // bookmarks + + if(preg_match_all('/#\^\[(url|zrl)(.*?)\](.*?)\[\/(url|zrl)\]/',$s,$match,PREG_SET_ORDER)) { + foreach($match as $mtch) { + $ret[] = $mtch[0]; + } + } + + // make sure the longer tags are returned first so that if two or more have common substrings + // we'll replace the longest ones first. Otherwise the common substring would be found in + // both strings and the string replacement would link both to the shorter strings and + // fail to link the longer string. RedMatrix github issue #378 + + usort($ret,'tag_sort_length'); + + +// logger('get_tags: ' . print_r($ret,true)); + return $ret; -}} +} + +function tag_sort_length($a,$b) { + if(mb_strlen($a) == mb_strlen($b)) + return 0; + return((mb_strlen($b) < mb_strlen($a)) ? (-1) : 1); +} + + + + + +function strip_zids($s) { + return preg_replace('/[\?&]zid=(.*?)(&|$)/ism','$2',$s); +} // quick and dirty quoted_printable encoding -if(! function_exists('qp')) { + function qp($s) { return str_replace ("%","=",rawurlencode($s)); -}} +} + -if(! function_exists('get_mentions')) { function get_mentions($item,$tags) { $o = ''; @@ -522,13 +685,19 @@ function get_mentions($item,$tags) { } } return $o; -}} +} + -if(! function_exists('contact_block')) { function contact_block() { $o = ''; $a = get_app(); + if(! $a->profile['uid']) + return; + + if(! perm_is_allowed($a->profile['uid'],get_observer_hash(),'view_contacts')) + return; + $shown = get_pconfig($a->profile['uid'],'system','display_friend_count'); if($shown === false) @@ -536,22 +705,36 @@ function contact_block() { if($shown == 0) return; + + $is_owner = ((local_user() && local_user() == $a->profile['uid']) ? true : false); + + $abook_flags = ABOOK_FLAG_PENDING|ABOOK_FLAG_SELF; + $xchan_flags = XCHAN_FLAGS_ORPHAN|XCHAN_FLAGS_DELETED; + if(! $is_owner) { + $abook_flags = $abook_flags | ABOOK_FLAGS_HIDDEN; + $xchan_flags = $xchan_flags | XCHAN_FLAGS_HIDDEN; + } + if((! is_array($a->profile)) || ($a->profile['hide_friends'])) return $o; - $r = q("SELECT COUNT(*) AS total FROM abook WHERE abook_channel = %d and abook_flags = 0", - intval($a->profile['uid']) + $r = q("SELECT COUNT(abook_id) AS total FROM abook left join xchan on abook_xchan = xchan_hash WHERE abook_channel = %d and not ( abook_flags & %d ) and not (xchan_flags & %d)", + intval($a->profile['uid']), + intval($abook_flags), + intval($xchan_flags) ); if(count($r)) { $total = intval($r[0]['total']); } if(! $total) { - $contacts = t('No connnections'); + $contacts = t('No connections'); $micropro = Null; } else { - $r = q("SELECT abook.*, xchan.* FROM abook left join xchan on abook.abook_xchan = xchan.xchan_hash WHERE abook_channel = %d AND abook_flags = 0 ORDER BY RAND() LIMIT %d", + $r = q("SELECT abook.*, xchan.* FROM abook left join xchan on abook.abook_xchan = xchan.xchan_hash WHERE abook_channel = %d AND not ( abook_flags & %d) and not (xchan_flags & %d ) ORDER BY RAND() LIMIT %d", intval($a->profile['uid']), + intval($abook_flags), + intval($xchan_flags), intval($shown) ); @@ -567,8 +750,8 @@ function contact_block() { $tpl = get_markup_template('contact_block.tpl'); $o = replace_macros($tpl, array( '$contacts' => $contacts, - '$nickname' => $a->profile['nickname'], - '$viewcontacts' => t('View Connnections'), + '$nickname' => $a->profile['channel_address'], + '$viewconnections' => t('View Connections'), '$micropro' => $micropro, )); @@ -577,7 +760,7 @@ function contact_block() { call_hooks('contact_block_end', $arr); return $o; -}} +} function chanlink_hash($s) { @@ -593,9 +776,15 @@ function chanlink_cid($d) { return z_root() . '/chanview?f=&cid=' . intval($d); } +function magiclink_url($observer,$myaddr,$url) { + return (($observer) + ? z_root() . '/magic?f=&dest=' . $url . '&addr=' . $myaddr + : $url + ); +} + -if(! function_exists('micropro')) { function micropro($contact, $redirect = false, $class = '', $textmode = false) { if($contact['click']) @@ -611,24 +800,38 @@ function micropro($contact, $redirect = false, $class = '', $textmode = false) { '$name' => $contact['xchan_name'], '$title' => $contact['xchan_name'] . ' [' . $contact['xchan_addr'] . ']', )); -}} +} + -if(! function_exists('search')) { function search($s,$id='search-box',$url='/search',$save = false) { $a = get_app(); $o = '<div id="' . $id . '">'; $o .= '<form action="' . $a->get_baseurl((stristr($url,'network')) ? true : false) . $url . '" method="get" >'; - $o .= '<input type="text" name="search" id="search-text" placeholder="' . t('Search') . '" value="' . $s .'" />'; - $o .= '<input type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; - if($save) - $o .= '<input type="submit" name="save" id="search-save" value="' . t('Save') . '" />'; + $o .= '<input type="text" class="icon-search" name="search" id="search-text" placeholder="" value="' . $s .'" onclick="this.submit();" />'; + $o .= '<input class="search-submit btn btn-default" type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; + if(feature_enabled(local_user(),'savedsearch')) + $o .= '<input class="search-save btn btn-default" type="submit" name="save" id="search-save" value="' . t('Save') . '" />'; $o .= '</form></div>'; return $o; -}} +} + + +function searchbox($s,$id='search-box',$url='/search',$save = false) { + $a = get_app(); + $o = '<div id="' . $id . '">'; + $o .= '<form action="' . z_root() . '/' . $url . '" method="get" >'; + $o .= '<input type="hidden" name="f" value="" />'; + $o .= '<input type="text" class="icon-search" name="search" id="search-text" placeholder="" value="' . $s .'" onclick="this.submit();" />'; + $o .= '<input type="submit" name="submit" class="btn btn-default" id="search-submit" value="' . t('Search') . '" />'; + if(feature_enabled(local_user(),'savedsearch')) + $o .= '<input type="submit" name="searchsave" class="btn btn-default" id="search-save" value="' . t('Save') . '" />'; + $o .= '</form></div>'; + return $o; +} + -if(! function_exists('valid_email')) { function valid_email($x){ if(get_config('system','disable_email_validation')) @@ -637,7 +840,7 @@ function valid_email($x){ if(preg_match('/^[_a-zA-Z0-9\-\+]+(\.[_a-zA-Z0-9\-\+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/',$x)) return true; return false; -}} +} /** @@ -648,12 +851,40 @@ function valid_email($x){ * */ -if(! function_exists('linkify')) { + function linkify($s) { - $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" >$1</a>', $s); + $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\@\~\#\'\%\$\!\+]*)/", ' <a href="$1" >$1</a>', $s); $s = preg_replace("/\<(.*?)(src|href)=(.*?)\&\;(.*?)\>/ism",'<$1$2=$3&$4>',$s); return($s); -}} +} + +/** + * @function sslify($s) + * Replace media element using http url with https to a local redirector if using https locally + * @param string $s + * + * Looks for HTML tags containing src elements that are http when we're viewing an https page + * Typically this throws an insecure content violation in the browser. So we redirect them + * to a local redirector which uses https and which redirects to the selected content + * + * @returns string + */ + + +function sslify($s) { + if(strpos(z_root(),'https:') === false) + return $s; + $matches = null; + $cnt = preg_match_all("/\<(.*?)src=\"(http\:.*?)\"(.*?)\>/",$s,$matches,PREG_SET_ORDER); + if($cnt) { + foreach($matches as $match) { + $s = str_replace($match[2],z_root() . '/sslify?f=&url=' . urlencode($match[2]),$s); + } + } + return $s; +} + + function get_poke_verbs() { @@ -674,9 +905,6 @@ function get_poke_verbs() { function get_mood_verbs() { - // index is present tense verb - // value is array containing past tense verb, translation of present, translation of past - $arr = array( 'happy' => t('happy'), 'sad' => t('sad'), @@ -695,6 +923,7 @@ function get_mood_verbs() { 'cranky' => t('cranky'), 'disturbed' => t('disturbed'), 'frustrated' => t('frustrated'), + 'depressed' => t('depressed'), 'motivated' => t('motivated'), 'relaxed' => t('relaxed'), 'surprised' => t('surprised'), @@ -717,8 +946,8 @@ function get_mood_verbs() { * Returns string * * It is expected that this function will be called using HTML text. - * We will escape text between HTML pre and code blocks from being - * processed. + * We will escape text between HTML pre and code blocks, and HTML attributes + * (such as urls) from being processed. * * At a higher level, the bbcode [nosmile] tag can be used to prevent this * function from being executed by the prepare_text() routine when preparing @@ -726,7 +955,7 @@ function get_mood_verbs() { * */ -if(! function_exists('smilies')) { + function smilies($s, $sample = false) { $a = get_app(); @@ -735,8 +964,8 @@ function smilies($s, $sample = false) { || (local_user() && intval(get_pconfig(local_user(),'system','no_smilies')))) return $s; - $s = preg_replace_callback('/<pre>(.*?)<\/pre>/ism','smile_encode',$s); - $s = preg_replace_callback('/<code>(.*?)<\/code>/ism','smile_encode',$s); + $s = preg_replace_callback('{<(pre|code)>.*?</\1>}ism','smile_shield',$s); + $s = preg_replace_callback('/<[a-z]+ .*?>/ism','smile_shield',$s); $texts = array( '<3', @@ -771,9 +1000,8 @@ function smilies($s, $sample = false) { ':facepalm', ':like', ':dislike', - '~friendika', - '~friendica' - + 'red#', + 'r#' ); $icons = array( @@ -809,8 +1037,9 @@ function smilies($s, $sample = false) { '<img class="smiley" src="' . $a->get_baseurl() . '/images/smiley-facepalm.gif" alt=":facepalm" />', '<img class="smiley" src="' . $a->get_baseurl() . '/images/like.gif" alt=":like" />', '<img class="smiley" src="' . $a->get_baseurl() . '/images/dislike.gif" alt=":dislike" />', - '<a href="http://project.friendika.com">~friendika <img class="smiley" src="' . $a->get_baseurl() . '/images/friendika-16.png" alt="~friendika" /></a>', - '<a href="http://friendica.com">~friendica <img class="smiley" src="' . $a->get_baseurl() . '/images/friendica-16.png" alt="~friendica" /></a>' + '<a href="http://getzot.com"><strong>red<img class="smiley" src="' . $a->get_baseurl() . '/images/rm-16.png" alt="red#" />matrix</strong></a>', + '<a href="http://getzot.com"><strong>red<img class="smiley" src="' . $a->get_baseurl() . '/images/rm-16.png" alt="r#" />matrix</strong></a>' + ); $params = array('texts' => $texts, 'icons' => $icons, 'string' => $s); @@ -827,19 +1056,18 @@ function smilies($s, $sample = false) { $s = str_replace($params['texts'],$params['icons'],$params['string']); } - $s = preg_replace_callback('/<pre>(.*?)<\/pre>/ism','smile_decode',$s); - $s = preg_replace_callback('/<code>(.*?)<\/code>/ism','smile_decode',$s); + $s = preg_replace_callback('/<!--base64:(.*?)-->/ism', 'smile_unshield', $s); return $s; -}} +} -function smile_encode($m) { - return(str_replace($m[1],base64url_encode($m[1]),$m[0])); +function smile_shield($m) { + return '<!--base64:' . base64url_encode($m[0]) . '-->'; } -function smile_decode($m) { - return(str_replace($m[1],base64url_decode($m[1]),$m[0])); +function smile_unshield($m) { + return base64url_decode($m[1]); } // expand <3333 to the correct number of hearts @@ -856,7 +1084,7 @@ function preg_heart($x) { } -if(! function_exists('day_translate')) { + function day_translate($s) { $ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'), array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')), @@ -867,14 +1095,14 @@ function day_translate($s) { $ret); return $ret; -}} +} + -if(! function_exists('normalise_link')) { function normalise_link($url) { $ret = str_replace(array('https:','//www.'), array('http:','//'), $url); return(rtrim($ret,'/')); -}} +} /** * @@ -887,95 +1115,192 @@ function normalise_link($url) { * */ -if(! function_exists('link_compare')) { + function link_compare($a,$b) { if(strcasecmp(normalise_link($a),normalise_link($b)) === 0) return true; return false; -}} +} // Given an item array, convert the body element from bbcode to html and add smilie icons. // If attach is true, also add icons for item attachments -if(! function_exists('prepare_body')) { -function prepare_body($item,$attach = false) { - - $a = get_app(); - call_hooks('prepare_body_init', $item); - - $s = prepare_text($item['body']); +function unobscure(&$item) { + if(array_key_exists('item_flags',$item) && ($item['item_flags'] & ITEM_OBSCURED)) { + $key = get_config('system','prvkey'); + if($item['title']) + $item['title'] = crypto_unencapsulate(json_decode_plus($item['title']),$key); + if($item['body']) + $item['body'] = crypto_unencapsulate(json_decode_plus($item['body']),$key); + } - $prep_arr = array('item' => $item, 'html' => $s); - call_hooks('prepare_body', $prep_arr); - $s = $prep_arr['html']; +} - if(! $attach) { - return $s; - } +function theme_attachments(&$item) { - $arr = explode(',',$item['attach']); - if(count($arr)) { - $s .= '<div class="body-attach">'; + $arr = json_decode_plus($item['attach']); + if(is_array($arr) && count($arr)) { + $attaches = array(); foreach($arr as $r) { - $matches = false; $icon = ''; - $cnt = preg_match_all('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"\[\/attach\]|',$r,$matches, PREG_SET_ORDER); - if($cnt) { - foreach($matches as $mtch) { - $icontype = strtolower(substr($mtch[3],0,strpos($mtch[3],'/'))); - switch($icontype) { - case 'video': - case 'audio': - case 'image': - case 'text': - $icon = '<div class="attachtype icon s22 type-' . $icontype . '"></div>'; - break; - default: - $icon = '<div class="attachtype icon s22 type-unkn"></div>'; - break; - } - $title = ((strlen(trim($mtch[4]))) ? escape_tags(trim($mtch[4])) : escape_tags($mtch[1])); - $title .= ' ' . $mtch[2] . ' ' . t('bytes'); - if((local_user() == $item['uid']) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) - $the_url = $a->get_baseurl() . '/redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1]; - else - $the_url = $mtch[1]; - - $s .= '<a href="' . strip_tags($the_url) . '" title="' . $title . '" class="attachlink" >' . $icon . '</a>'; - } + $icontype = substr($r['type'],0,strpos($r['type'],'/')); + + // FIXME This should probably be a giant "if" statement in the template so that we don't have icon names + // embedded in php code + + switch($icontype) { + case 'video': + $icon = 'icon-facetime-video'; + break; + case 'audio': + $icon = 'icon-volume-up'; + break; + case 'image': + $icon = 'icon-picture'; + break; + case 'text': + $icon = 'icon-align-justify'; + break; + default: + $icon = 'icon-question'; + break; } + + $title = htmlspecialchars($r['title'], ENT_COMPAT,'UTF-8'); + if(! $title) + $title = t('unknown.???'); + $title .= ' ' . $r['length'] . ' ' . t('bytes'); + + $url = z_root() . '/magic?f=&hash=' . $item['author_xchan'] . '&dest=' . $r['href'] . '/' . $r['revision']; + $s .= '<a href="' . $url . '" title="' . $title . '" class="attachlink" >' . $icon . '</a>'; + $attaches[] = array('title' => $title, 'url' => $url, 'icon' => $icon ); + } - $s .= '<div class="clear"></div></div>'; + + } - $x = ''; + $s = replace_macros(get_markup_template('item_attach.tpl'), array( + '$attaches' => $attaches + )); + + return $s; + +} + + +function format_categories(&$item,$writeable) { + + $s = ''; $terms = get_terms_oftype($item['term'],TERM_CATEGORY); if($terms) { + $categories = array(); foreach($terms as $t) { - if(strlen($x)) - $x .= ','; - $x .= htmlspecialchars($t['term'],ENT_COMPAT,'UTF-8') - . ((local_user() == $item['uid']) ? ' <a href="' . $a->get_baseurl() . '/filerm/' . $item['id'] . '?f=&cat=' . urlencode($t['term']) . '" title="' . t('remove') . '" >' . t('[remove]') . '</a>' : ''); + $term = htmlspecialchars($t['term'],ENT_COMPAT,'UTF-8',false) ; + if(! trim($term)) + continue; + $removelink = (($writeable) ? z_root() . '/filerm/' . $item['id'] . '?f=&cat=' . urlencode($t['term']) : ''); + $categories[] = array('term' => $term, 'writeable' => $writeable, 'removelink' => $removelink, 'url' => zid($t['url'])); } - if(strlen($x)) - $s .= '<div class="categorytags"><span>' . t('Categories:') . ' </span>' . $x . '</div>'; + } + $s = replace_macros(get_markup_template('item_categories.tpl'),array( + '$remove' => t('remove category'), + '$categories' => $categories + )); + return $s; +} + +// Add any hashtags which weren't mentioned in the message body, e.g. community tags + +function format_hashtags(&$item) { + + $s = ''; + $terms = get_terms_oftype($item['term'],TERM_HASHTAG); + if($terms) { + $categories = array(); + foreach($terms as $t) { + $term = htmlspecialchars($t['term'],ENT_COMPAT,'UTF-8',false) ; + if(! trim($term)) + continue; + if(strpos($item['body'], $t['url'])) + continue; + if($s) + $s .= ' '; + $s .= '#<a href="' . zid($t['url']) . '" >' . $term . '</a>'; + } } + return $s; +} + - $x = ''; + + + +function format_filer(&$item) { + + $s = ''; $terms = get_terms_oftype($item['term'],TERM_FILE); if($terms) { + $categories = array(); foreach($terms as $t) { - if(strlen($x)) - $x .= ' '; - $x .= htmlspecialchars($t['term'],ENT_COMPAT,'UTF-8') - . ' <a href="' . $a->get_baseurl() . '/filerm/' . $item['id'] . '?f=&term=' . urlencode($t['term']) . '" title="' . t('remove') . '" >' . t('[remove]') . '</a>'; + $term = htmlspecialchars($t['term'],ENT_COMPAT,'UTF-8',false) ; + if(! trim($term)) + continue; + $removelink = z_root() . '/filerm/' . $item['id'] . '?f=&term=' . urlencode($t['term']); + $categories[] = array('term' => $term, 'removelink' => $removelink); } - if(strlen($x) && (local_user() == $item['uid'])) - $s .= '<div class="filesavetags"><span>' . t('Filed under:') . ' </span>' . $x . '</div>'; } + $s = replace_macros(get_markup_template('item_filer.tpl'),array( + '$remove' => t('remove from file'), + '$categories' => $categories + )); + return $s; +} + + + + + +function prepare_body(&$item,$attach = false) { + + $a = get_app(); + + + + call_hooks('prepare_body_init', $item); + + unobscure($item); + + $s = prepare_text($item['body'],$item['mimetype']); + + $prep_arr = array('item' => $item, 'html' => $s); + call_hooks('prepare_body', $prep_arr); + $s = $prep_arr['html']; + + if(! $attach) { + return $s; + } + + + $s .= theme_attachments($item); + + + $writeable = ((get_observer_hash() == $item['owner_xchan']) ? true : false); + + + $s .= format_hashtags($item); + + $s .= format_categories($item,$writeable); + + if(local_user() == $item['uid']) + $s .= format_filer($item); + + + $s = sslify($s); + // Look for spoiler $spoilersearch = '<blockquote class="spoiler">'; @@ -1011,30 +1336,106 @@ function prepare_body($item,$attach = false) { call_hooks('prepare_body_final', $prep_arr); return $prep_arr['html']; -}} +} // Given a text string, convert from bbcode to html and add smilie icons. -if(! function_exists('prepare_text')) { -function prepare_text($text) { - require_once('include/bbcode.php'); +function prepare_text($text,$content_type = 'text/bbcode') { - if(stristr($text,'[nosmile]')) - $s = bbcode($text); - else - $s = smilies(bbcode($text)); + + + switch($content_type) { + + case 'text/plain': + $s = escape_tags($text); + break; + + case 'text/html': + $s = $text; + break; + + case 'text/markdown': + require_once('library/markdown.php'); + $s = Markdown($text); + break; + + // No security checking is done here at display time - so we need to verify + // that the author is allowed to use PHP before storing. We also cannot allow + // importation of PHP text bodies from other sites. Therefore this content + // type is only valid for web pages (and profile details). + + // It may be possible to provide a PHP message body which is evaluated on the + // sender's site before sending it elsewhere. In that case we will have a + // different content-type here. + + case 'application/x-php': + ob_start(); + eval($text); + $s = ob_get_contents(); + ob_end_clean(); + break; + + case 'text/bbcode': + case '': + default: + require_once('include/bbcode.php'); + + if(stristr($text,'[nosmile]')) + $s = bbcode($text); + else + $s = smilies(bbcode($text)); + $s = zidify_links($s); + break; + } + +//logger('prepare_text: ' . $s); return $s; -}} +} + + +/** + * zidify_callback() and zidify_links() work together to turn any HTML a tags with class="zrl" into zid links + * These will typically be generated by a bbcode '[zrl]' tag. This is done inside prepare_text() rather than bbcode() + * because the latter is used for general purpose conversions and the former is used only when preparing text for + * immediate display. + * + * Issues: Currently the order of HTML parameters in the text is somewhat rigid and inflexible. + * We assume it looks like <a class="zrl" href="xxxxxxxxxx"> and will not work if zrl and href appear in a different order. + */ + + +function zidify_callback($match) { + $is_zid = ((feature_enabled(local_user(),'sendzid')) || (strpos($match[1],'zrl')) ? true : false); + $replace = '<a' . $match[1] . ' href="' . (($is_zid) ? zid($match[2]) : $match[2]) . '"'; + $x = str_replace($match[0],$replace,$match[0]); + return $x; +} + +function zidify_img_callback($match) { + $is_zid = ((feature_enabled(local_user(),'sendzid')) || (strpos($match[1],'zrl')) ? true : false); + $replace = '<img' . $match[1] . ' src="' . (($is_zid) ? zid($match[2]) : $match[2]) . '"'; + + $x = str_replace($match[0],$replace,$match[0]); + return $x; +} + + +function zidify_links($s) { + $s = preg_replace_callback('/\<a(.*?)href\=\"(.*?)\"/ism','zidify_callback',$s); + $s = preg_replace_callback('/\<img(.*?)src\=\"(.*?)\"/ism','zidify_img_callback',$s); + return $s; +} + /** * return atom link elements for all of our hubs */ -if(! function_exists('feed_hublinks')) { + function feed_hublinks() { $hub = get_config('system','huburl'); @@ -1052,11 +1453,11 @@ function feed_hublinks() { } } return $hubxml; -}} +} /* return atom link elements for salmon endpoints */ -if(! function_exists('feed_salmonlinks')) { + function feed_salmonlinks($nick) { $a = get_app(); @@ -1068,31 +1469,91 @@ function feed_salmonlinks($nick) { $salmon .= ' <link rel="http://salmon-protocol.org/ns/salmon-replies" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; $salmon .= ' <link rel="http://salmon-protocol.org/ns/salmon-mention" href="' . xmlify($a->get_baseurl() . '/salmon/' . $nick) . '" />' . "\n" ; return $salmon; -}} +} + -if(! function_exists('get_plink')) { -function get_plink($item) { - $a = get_app(); - if (x($item,'plink') && ($item['private'] != 1)) { +function get_plink($item,$conversation_mode = true) { + $a = get_app(); + if($conversation_mode) + $key = 'plink'; + else + $key = 'llink'; + + if(x($item,$key)) { return array( - 'href' => $item['plink'], - 'title' => t('link to source'), + 'href' => zid($item[$key]), + 'title' => t('Link to Source'), ); } else { return false; } -}} +} + -if(! function_exists('unamp')) { function unamp($s) { return str_replace('&', '&', $s); -}} +} +function layout_select($channel_id, $current = '') { + $r = q("select mid,sid from item left join item_id on iid = item.id where service = 'PDL' and item.uid = item_id.uid and item_id.uid = %d and (item_restrict & %d)", + intval($channel_id), + intval(ITEM_PDL) + ); + if($r) { + $o = t('Select a page layout: '); + $o .= '<select name="layout_mid" id="select-layout_mid" >'; + $empty_selected = (($current === '') ? ' selected="selected" ' : ''); + $o .= '<option value="" ' . $empty_selected . '>' . t('default') . '</option>'; + foreach($r as $rr) { + $selected = (($rr['mid'] == $current) ? ' selected="selected" ' : ''); + $o .= '<option value="' . $rr['mid'] . '"' . $selected . '>' . $rr['sid'] . '</option>'; + } + $o .= '</select>'; + } + + return $o; +} + + + + + +function mimetype_select($channel_id, $current = 'text/bbcode') { + + $x = array( + 'text/bbcode', + 'text/html', + 'text/markdown', + 'text/plain' + ); + + $r = q("select account_id, account_roles from account left join channel on account_id = channel_account_id where + channel_id = %d limit 1", + intval($channel_id) + ); + + if($r) { + if($r[0]['account_roles'] & ACCOUNT_ROLE_ALLOWCODE) { + if(local_user() && get_account_id() == $r[0]['account_id']) + $x[] = 'application/x-php'; + } + } + + $o = t('Page content type: '); + $o .= '<select name="mimetype" id="mimetype-select">'; + foreach($x as $y) { + $select = (($y == $current) ? ' selected="selected" ' : ''); + $o .= '<option name="' . $y . '"' . $select . '>' . $y . '</option>'; + } + $o .= '</select>'; + + return $o; + +} -if(! function_exists('lang_selector')) { function lang_selector() { global $a; @@ -1125,10 +1586,10 @@ function lang_selector() { )); return $o; -}} +} + -if(! function_exists('return_bytes')) { function return_bytes ($size_str) { switch (substr ($size_str, -1)) { @@ -1137,23 +1598,9 @@ function return_bytes ($size_str) { case 'G': case 'g': return (int)$size_str * 1073741824; default: return $size_str; } -}} - -function generate_user_guid() { - $found = true; - do { - $guid = random_string(16); - $x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1", - dbesc($guid) - ); - if(! count($x)) - $found = false; - } while ($found == true ); - return $guid; } - function base64url_encode($s, $strip_padding = true) { $s = strtr(base64_encode($s),'+/','-_'); @@ -1170,84 +1617,10 @@ function base64url_decode($s) { logger('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true)); return $s; } - -/* - * // Placeholder for new rev of salmon which strips base64 padding. - * // PHP base64_decode handles the un-padded input without requiring this step - * // Uncomment if you find you need it. - * - * $l = strlen($s); - * if(! strpos($s,'=')) { - * $m = $l % 4; - * if($m == 2) - * $s .= '=='; - * if($m == 3) - * $s .= '='; - * } - * - */ - return base64_decode(strtr($s,'-_','+/')); } -if (!function_exists('str_getcsv')) { - function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') { - if (is_string($input) && !empty($input)) { - $output = array(); - $tmp = preg_split("/".$eol."/",$input); - if (is_array($tmp) && !empty($tmp)) { - while (list($line_num, $line) = each($tmp)) { - if (preg_match("/".$escape.$enclosure."/",$line)) { - while ($strlen = strlen($line)) { - $pos_delimiter = strpos($line,$delimiter); - $pos_enclosure_start = strpos($line,$enclosure); - if ( - is_int($pos_delimiter) && is_int($pos_enclosure_start) - && ($pos_enclosure_start < $pos_delimiter) - ) { - $enclosed_str = substr($line,1); - $pos_enclosure_end = strpos($enclosed_str,$enclosure); - $enclosed_str = substr($enclosed_str,0,$pos_enclosure_end); - $output[$line_num][] = $enclosed_str; - $offset = $pos_enclosure_end+3; - } else { - if (empty($pos_delimiter) && empty($pos_enclosure_start)) { - $output[$line_num][] = substr($line,0); - $offset = strlen($line); - } else { - $output[$line_num][] = substr($line,0,$pos_delimiter); - $offset = ( - !empty($pos_enclosure_start) - && ($pos_enclosure_start < $pos_delimiter) - ) - ?$pos_enclosure_start - :$pos_delimiter+1; - } - } - $line = substr($line,$offset); - } - } else { - $line = preg_split("/".$delimiter."/",$line); - - /* - * Validating against pesky extra line breaks creating false rows. - */ - if (is_array($line) && !empty($line[0])) { - $output[$line_num] = $line; - } - } - } - return $output; - } else { - return false; - } - } else { - return false; - } - } -} - function cleardiv() { return '<div class="clear"></div>'; } @@ -1295,7 +1668,7 @@ function array_xmlify($val){ function reltoabs($text, $base) { if (empty($base)) - return $text; + return $text; $base = rtrim($base,'/'); @@ -1325,290 +1698,35 @@ function reltoabs($text, $base) } function item_post_type($item) { - if(intval($item['event-id'])) - return t('event'); - if(strlen($item['resource_id'])) - return t('photo'); - if(strlen($item['verb']) && $item['verb'] !== ACTIVITY_POST) - return t('activity'); - if($item['id'] != $item['parent']) - return t('comment'); - return t('post'); -} - -// post categories and "save to file" use the same item.file table for storage. -// We will differentiate the different uses by wrapping categories in angle brackets -// and save to file categories in square brackets. -// To do this we need to escape these characters if they appear in our tag. - -function file_tag_encode($s) { - return str_replace(array('<','>','[',']'),array('%3c','%3e','%5b','%5d'),$s); -} - -function file_tag_decode($s) { - return str_replace(array('%3c','%3e','%5b','%5d'),array('<','>','[',']'),$s); -} - -function file_tag_file_query($table,$s,$type = 'file') { - - if($type == 'file') - $termtype = TERM_FILE; - else - $termtype = TERM_CATEGORY; - - return sprintf(" AND " . (($table) ? dbesc($table) . '.' : '') . "id in (select term.oid from term where term.type = %d and term.term = '%s' and term.uid = " . (($table) ? dbesc($table) . '.' : '') . "uid ) ", - intval($termtype), - protect_sprintf(dbesc($s)) - ); -} - -function term_query($table,$s,$type = TERM_UNKNOWN) { - - return sprintf(" AND " . (($table) ? dbesc($table) . '.' : '') . "id in (select term.oid from term where term.type = %d and term.term = '%s' and term.uid = " . (($table) ? dbesc($table) . '.' : '') . "uid ) ", - intval($type), - protect_sprintf(dbesc($s)) - ); -} - -// ex. given music,video return <music><video> or [music][video] -function file_tag_list_to_file($list,$type = 'file') { - $tag_list = ''; - if(strlen($list)) { - $list_array = explode(",",$list); - if($type == 'file') { - $lbracket = '['; - $rbracket = ']'; - } - else { - $lbracket = '<'; - $rbracket = '>'; - } - - foreach($list_array as $item) { - if(strlen($item)) { - $tag_list .= $lbracket . file_tag_encode(trim($item)) . $rbracket; - } - } - } - return $tag_list; -} - -// ex. given <music><video>[friends], return music,video or friends -function file_tag_file_to_list($file,$type = 'file') { - $matches = false; - $list = ''; - if($type == 'file') { - $cnt = preg_match_all('/\[(.*?)\]/',$file,$matches,PREG_SET_ORDER); - } - else { - $cnt = preg_match_all('/<(.*?)>/',$file,$matches,PREG_SET_ORDER); - } - if($cnt) { - foreach($matches as $mtch) { - if(strlen($list)) - $list .= ','; - $list .= file_tag_decode($mtch[1]); - } - } - - return $list; -} - -function file_tag_update_pconfig($uid,$file_old,$file_new,$type = 'file') { - // $file_old - categories previously associated with an item - // $file_new - new list of categories for an item - - if(! intval($uid)) - return false; - - if($file_old == $file_new) - return true; - - $saved = get_pconfig($uid,'system','filetags'); - if(strlen($saved)) { - if($type == 'file') { - $lbracket = '['; - $rbracket = ']'; - } - else { - $lbracket = '<'; - $rbracket = '>'; - } - - $filetags_updated = $saved; - - // check for new tags to be added as filetags in pconfig - $new_tags = array(); - $check_new_tags = explode(",",file_tag_file_to_list($file_new,$type)); - - foreach($check_new_tags as $tag) { - if(! stristr($saved,$lbracket . file_tag_encode($tag) . $rbracket)) - $new_tags[] = $tag; - } - - $filetags_updated .= file_tag_list_to_file(implode(",",$new_tags),$type); - - // check for deleted tags to be removed from filetags in pconfig - $deleted_tags = array(); - $check_deleted_tags = explode(",",file_tag_file_to_list($file_old,$type)); - - foreach($check_deleted_tags as $tag) { - if(! stristr($file_new,$lbracket . file_tag_encode($tag) . $rbracket)) - $deleted_tags[] = $tag; - } - - foreach($deleted_tags as $key => $tag) { - $r = q("select file from item where uid = %d " . file_tag_file_query('item',$tag,$type), - intval($uid) - ); - - if(count($r)) { - unset($deleted_tags[$key]); - } - else { - $filetags_updated = str_replace($lbracket . file_tag_encode($tag) . $rbracket,'',$filetags_updated); - } - } - - if($saved != $filetags_updated) { - set_pconfig($uid,'system','filetags', $filetags_updated); - } - return true; - } - else - if(strlen($file_new)) { - set_pconfig($uid,'system','filetags', $file_new); - } - return true; -} - -function store_item_tag($uid,$iid,$otype,$type,$term,$url = '') { - if(! $term) - return false; - $r = q("select * from term - where uid = %d and oid = %d and otype = %d and type = %d - and term = '%s' and url = '%s' ", - intval($uid), - intval($iid), - intval($otype), - intval($type), - dbesc($term), - dbesc($url) - ); - if(count($r)) - return false; - $r = q("insert into term (uid, oid, otype, type, term, url) - values( %d, %d, %d, %d, '%s', '%s') ", - intval($uid), - intval($iid), - intval($otype), - intval($type), - dbesc($term), - dbesc($url) - ); - return $r; -} - -function get_terms_oftype($arr,$type) { - $ret = array(); - if(! (is_array($arr) && count($arr))) - return $ret; - - if(! is_array($type)) - $type = array($type); - - foreach($type as $t) - foreach($arr as $x) - if($x['type'] == $t) - $ret[] = $x; - return $ret; -} - -function format_term_for_display($term) { - $s = ''; - if($term['type'] == TERM_HASHTAG) - $s .= '#'; - elseif($term['type'] == TERM_MENTION) - $s .= '@'; - - if($term['url']) $s .= '<a target="extlink" href="' . $term['url'] . '">' . htmlspecialchars($term['term']) . '</a>'; - else $s .= htmlspecialchars($term['term']); - return $s; -} - -function file_tag_save_file($uid,$item,$file) { - $result = false; - if(! intval($uid)) - return false; - - $r = q("select file from item where id = %d and uid = %d limit 1", - intval($item), - intval($uid) - ); - if(count($r)) { - if(! stristr($r[0]['file'],'[' . file_tag_encode($file) . ']')) - q("update item set file = '%s' where id = %d and uid = %d limit 1", - dbesc($r[0]['file'] . '[' . file_tag_encode($file) . ']'), - intval($item), - intval($uid) - ); - $saved = get_pconfig($uid,'system','filetags'); - if((! strlen($saved)) || (! stristr($saved,'[' . file_tag_encode($file) . ']'))) - set_pconfig($uid,'system','filetags',$saved . '[' . file_tag_encode($file) . ']'); - info( t('Item filed') ); - } - return true; -} - -function file_tag_unsave_file($uid,$item,$file,$cat = false) { - $result = false; - if(! intval($uid)) - return false; - - if($cat == true) - $pattern = '<' . file_tag_encode($file) . '>' ; - else - $pattern = '[' . file_tag_encode($file) . ']' ; - - - $r = q("select file from item where id = %d and uid = %d limit 1", - intval($item), - intval($uid) - ); - if(! count($r)) - return false; - - q("update item set file = '%s' where id = %d and uid = %d limit 1", - dbesc(str_replace($pattern,'',$r[0]['file'])), - intval($item), - intval($uid) - ); - - $r = q("select file from item where uid = %d and deleted = 0 " . file_tag_file_query('item',$file,(($cat) ? 'category' : 'file')), - intval($uid) - ); - - if(! count($r)) { - $saved = get_pconfig($uid,'system','filetags'); - set_pconfig($uid,'system','filetags',str_replace($pattern,'',$saved)); + switch($item['resource_type']) { + case 'photo': + $post_type = t('photo'); + break; + case 'event': + $post_type = t('event'); + break; + default: + $post_type = t('status'); + if($item['mid'] != $item['parent_mid']) + $post_type = t('comment'); + break; + } - } - return true; -} + if(strlen($item['verb']) && (! activity_match($item['verb'],ACTIVITY_POST))) + $post_type = t('activity'); -function normalise_openid($s) { - return trim(str_replace(array('http://','https://'),array('',''),$s),'/'); + return $post_type; } function undo_post_tagging($s) { $matches = null; - $cnt = preg_match_all('/([@#])\[url=(.*?)\](.*?)\[\/url\]/ism',$s,$matches,PREG_SET_ORDER); + $cnt = preg_match_all('/([@#])(\!*)\[zrl=(.*?)\](.*?)\[\/zrl\]/ism',$s,$matches,PREG_SET_ORDER); if($cnt) { foreach($matches as $mtch) { - $s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s); + $s = str_replace($mtch[0], $mtch[1] . $mtch[2] . str_replace(' ','_',$mtch[4]),$s); } } return $s; @@ -1666,6 +1784,7 @@ function check_webbie($arr) { $str .= "'" . dbesc($y) . "'"; } } + if(strlen($str)) { $r = q("select channel_address from channel where channel_address in ( $str ) "); if(count($r)) { @@ -1674,8 +1793,9 @@ function check_webbie($arr) { } } foreach($arr as $x) { - if(! in_array($x,$taken)) { - return $x; + $y = legal_webbie($x); + if(! in_array($y,$taken)) { + return $y; } } } @@ -1691,7 +1811,11 @@ function ids_to_querystr($arr,$idx = 'id') { return(implode(',', $t)); } -function xchan_query(&$items) { +// Fetches xchan and hubloc data for an array of items with only an +// author_xchan and owner_xchan. If $abook is true also include the abook info. +// This is needed in the API to save extra per item lookups there. + +function xchan_query(&$items,$abook = true) { $arr = array(); if($items && count($items)) { foreach($items as $item) { @@ -1702,8 +1826,16 @@ function xchan_query(&$items) { } } if(count($arr)) { - $chans = q("select xchan.*,hubloc.* from xchan left join hubloc on hubloc_hash = xchan_hash - where xchan_hash in (" . implode(',', $arr) . ") and ( hubloc_flags & " . intval(HUBLOC_FLAGS_PRIMARY) . " )"); + if($abook) { + $chans = q("select * from xchan left join hubloc on hubloc_hash = xchan_hash left join abook on abook_xchan = xchan_hash and abook_channel = %d + where xchan_hash in (" . implode(',', $arr) . ") and ( hubloc_flags & " . intval(HUBLOC_FLAGS_PRIMARY) . " )", + intval($item['uid']) + ); + } + else { + $chans = q("select xchan.*,hubloc.* from xchan left join hubloc on hubloc_hash = xchan_hash + where xchan_hash in (" . implode(',', $arr) . ") and ( hubloc_flags & " . intval(HUBLOC_FLAGS_PRIMARY) . " )"); + } } if($items && count($items) && $chans && count($chans)) { for($x = 0; $x < count($items); $x ++) { @@ -1761,9 +1893,11 @@ function magic_link($s) { return $s; } -function stringify_array_elms(&$arr) { +// if $escape is true, dbesc() each element before adding quotes + +function stringify_array_elms(&$arr,$escape = false) { for($x = 0; $x < count($arr); $x ++) - $arr[$x] = "'" . $arr[$x] . "'"; + $arr[$x] = "'" . (($escape) ? dbesc($arr[$x]) : $arr[$x]) . "'"; } /** @@ -1775,117 +1909,87 @@ function stringify_array_elms(&$arr) { */ function jindent($json) { - $result = ''; - $pos = 0; - $strLen = strlen($json); - $indentStr = ' '; - $newLine = "\n"; - $prevChar = ''; - $outOfQuotes = true; + $result = ''; + $pos = 0; + $strLen = strlen($json); + $indentStr = ' '; + $newLine = "\n"; + $prevChar = ''; + $outOfQuotes = true; - for ($i=0; $i<=$strLen; $i++) { + for ($i=0; $i<=$strLen; $i++) { - // Grab the next character in the string. - $char = substr($json, $i, 1); + // Grab the next character in the string. + $char = substr($json, $i, 1); - // Are we inside a quoted string? - if ($char == '"' && $prevChar != '\\') { - $outOfQuotes = !$outOfQuotes; - - // If this character is the end of an element, - // output a new line and indent the next line. - } else if(($char == '}' || $char == ']') && $outOfQuotes) { - $result .= $newLine; - $pos --; - for ($j=0; $j<$pos; $j++) { - $result .= $indentStr; - } - } - - // Add the character to the result string. - $result .= $char; - - // If the last character was the beginning of an element, - // output a new line and indent the next line. - if (($char == ',' || $char == '{' || $char == '[') && $outOfQuotes) { - $result .= $newLine; - if ($char == '{' || $char == '[') { - $pos ++; - } - - for ($j = 0; $j < $pos; $j++) { - $result .= $indentStr; - } - } - - $prevChar = $char; - } + // Are we inside a quoted string? + if ($char == '"' && $prevChar != '\\') { + $outOfQuotes = !$outOfQuotes; + + // If this character is the end of an element, + // output a new line and indent the next line. + } else if(($char == '}' || $char == ']') && $outOfQuotes) { + $result .= $newLine; + $pos --; + for ($j=0; $j<$pos; $j++) { + $result .= $indentStr; + } + } + + // Add the character to the result string. + $result .= $char; + + // If the last character was the beginning of an element, + // output a new line and indent the next line. + if (($char == ',' || $char == '{' || $char == '[') && $outOfQuotes) { + $result .= $newLine; + if ($char == '{' || $char == '[') { + $pos ++; + } + + for ($j = 0; $j < $pos; $j++) { + $result .= $indentStr; + } + } + + $prevChar = $char; + } - return $result; + return $result; } -// Tag cloud functions - need to be adpated to this database format +function json_decode_plus($s) { + $x = json_decode($s,true); + if(! $x) + $x = json_decode(str_replace(array('\\"','\\\\'),array('"','\\'),$s),true); + return $x; -function tagadelic($uid, $count = 0, $type = TERM_HASHTAG) { - - // Fetch tags - $r = q("select term, count(term) as total from term - where uid = %d and type = %d - and otype = %d - group by term order by total desc %s", - intval($uid), - intval($type), - intval(TERM_OBJ_POST), - ((intval($count)) ? "limit $count" : '') - ); +} - if(! $r) - return array(); - - // Find minimum and maximum log-count. - $tags = array(); - $min = 1e9; - $max = -1e9; - - $x = 0; - foreach($r as $rr) { - $tags[$x][0] = $rr['term']; - $tags[$x][1] = log($rr['total']); - $tags[$x][2] = 0; - $min = min($min,$tags[$x][1]); - $max = max($max,$tags[$x][1]); - $x ++; - } - usort($tags,'tags_sort'); +function design_tools() { + $channel = get_app()->get_channel(); + $who = $channel['channel_address']; - $range = max(.01, $max - $min) * 1.0001; + return replace_macros(get_markup_template('design_tools.tpl'), array( + '$title' => t('Design'), + '$who' => $who, + '$blocks' => t('Blocks'), + '$menus' => t('Menus'), + '$layout' => t('Layouts'), + '$pages' => t('Pages') + )); +} - for($x = 0; $x < count($tags); $x ++) { - $tags[$x][2] = 1 + floor(5 * ($tags[$x][1] - $min) / $range); - } +/* case insensitive in_array() */ - return $tags; +function in_arrayi($needle, $haystack) { + return in_array(strtolower($needle), array_map('strtolower', $haystack)); } -function tags_sort($a,$b) { - if($a[0] == $b[0]) - return 0; - return((strtolower($a[0]) < strtolower($b[0])) ? -1 : 1); +function normalise_openid($s) { + return trim(str_replace(array('http://','https://'),array('',''),$s),'/'); } - -function tagblock($link,$uid,$count = 0,$type = TERM_HASHTAG) { - $tab = 0; - $r = tagadelic($uid,$count,$type); - - if($r) { - echo '<div class="tags" align="center">'; - foreach($r as $rr) { - echo '<a href="'.$link .'/' . '?f=&tag=' . urlencode($rr[0]).'" class="tag'.$rr[2].'">'.$rr[0].'</a> '; - } - echo '</div>'; - } -} diff --git a/include/user.php b/include/user.php deleted file mode 100644 index d71f024d1..000000000 --- a/include/user.php +++ /dev/null @@ -1,320 +0,0 @@ -<?php - -require_once('include/config.php'); -require_once('include/network.php'); -require_once('include/plugin.php'); -require_once('include/text.php'); -require_once('include/language.php'); -require_once('include/datetime.php'); - -function create_user($arr) { - - // Required: { username, nickname, email } or { openid_url } - - $a = get_app(); - $result = array('success' => false, 'user' => null, 'password' => '', 'message' => ''); - - $using_invites = get_config('system','invitation_only'); - $num_invites = get_config('system','number_invites'); - - - $invite_id = ((x($arr,'invite_id')) ? notags(trim($arr['invite_id'])) : ''); - $username = ((x($arr,'username')) ? notags(trim($arr['username'])) : ''); - $nickname = ((x($arr,'nickname')) ? notags(trim($arr['nickname'])) : ''); - $email = ((x($arr,'email')) ? notags(trim($arr['email'])) : ''); - $openid_url = ((x($arr,'openid_url')) ? notags(trim($arr['openid_url'])) : ''); - $photo = ((x($arr,'photo')) ? notags(trim($arr['photo'])) : ''); - $password = ((x($arr,'password')) ? trim($arr['password']) : ''); - $blocked = ((x($arr,'blocked')) ? intval($arr['blocked']) : 0); - $verified = ((x($arr,'verified')) ? intval($arr['verified']) : 0); - - $publish = ((x($arr,'profile_publish_reg') && intval($arr['profile_publish_reg'])) ? 1 : 0); - $netpublish = ((strlen(get_config('system','directory_submit_url'))) ? $publish : 0); - - $tmp_str = $openid_url; - - if($using_invites) { - if(! $invite_id) { - $result['message'] .= t('An invitation is required.') . EOL; - return $result; - } - $r = q("select * from register where `hash` = '%s' limit 1", dbesc($invite_id)); - if(! results($r)) { - $result['message'] .= t('Invitation could not be verified.') . EOL; - return $result; - } - } - - if((! x($username)) || (! x($email)) || (! x($nickname))) { - if($openid_url) { - if(! validate_url($tmp_str)) { - $result['message'] .= t('Invalid OpenID url') . EOL; - return $result; - } - $_SESSION['register'] = 1; - $_SESSION['openid'] = $openid_url; - require_once('library/openid.php'); - $openid = new LightOpenID; - $openid->identity = $openid_url; - $openid->returnUrl = $a->get_baseurl() . '/openid'; - $openid->required = array('namePerson/friendly', 'contact/email', 'namePerson'); - $openid->optional = array('namePerson/first','media/image/aspect11','media/image/default'); - goaway($openid->authUrl()); - // NOTREACHED - } - - notice( t('Please enter the required information.') . EOL ); - return; - } - - if(! validate_url($tmp_str)) - $openid_url = ''; - - - $err = ''; - - // collapse multiple spaces in name - $username = preg_replace('/ +/',' ',$username); - - if(mb_strlen($username) > 48) - $result['message'] .= t('Please use a shorter name.') . EOL; - if(mb_strlen($username) < 3) - $result['message'] .= t('Name too short.') . EOL; - - // I don't really like having this rule, but it cuts down - // on the number of auto-registrations by Russian spammers - - // Using preg_match was completely unreliable, due to mixed UTF-8 regex support - // $no_utf = get_config('system','no_utf'); - // $pat = (($no_utf) ? '/^[a-zA-Z]* [a-zA-Z]*$/' : '/^\p{L}* \p{L}*$/u' ); - - // So now we are just looking for a space in the full name. - - $loose_reg = get_config('system','no_regfullname'); - if(! $loose_reg) { - $username = mb_convert_case($username,MB_CASE_TITLE,'UTF-8'); - if(! strpos($username,' ')) - $result['message'] .= t("That doesn't appear to be your full \x28First Last\x29 name.") . EOL; - } - - - if(! allowed_email($email)) - $result['message'] .= t('Your email domain is not among those allowed on this site.') . EOL; - - if((! valid_email($email)) || (! validate_email($email))) - $result['message'] .= t('Not a valid email address.') . EOL; - - // Disallow somebody creating an account using openid that uses the admin email address, - // since openid bypasses email verification. We'll allow it if there is not yet an admin account. - - if((x($a->config,'admin_email')) && (strcasecmp($email,$a->config['admin_email']) == 0) && strlen($openid_url)) { - $r = q("SELECT * FROM `user` WHERE `email` = '%s' LIMIT 1", - dbesc($email) - ); - if(count($r)) - $result['message'] .= t('Cannot use that email.') . EOL; - } - - $nickname = $arr['nickname'] = strtolower($nickname); - - if(! preg_match("/^[a-z][a-z0-9\-\_]*$/",$nickname)) - $result['message'] .= t('Your "nickname" can only contain "a-z", "0-9", "-", and "_", and must also begin with a letter.') . EOL; - $r = q("SELECT `uid` FROM `user` - WHERE `nickname` = '%s' LIMIT 1", - dbesc($nickname) - ); - if(count($r)) - $result['message'] .= t('Nickname is already registered. Please choose another.') . EOL; - - // Check deleted accounts that had this nickname. Doesn't matter to us, - // but could be a security issue for federated platforms. - - $r = q("SELECT * FROM `userd` - WHERE `username` = '%s' LIMIT 1", - dbesc($nickname) - ); - if(count($r)) - $result['message'] .= t('Nickname was once registered here and may not be re-used. Please choose another.') . EOL; - - if(strlen($result['message'])) { - return $result; - } - - $new_password = ((strlen($password)) ? $password : autoname(6) . mt_rand(100,9999)); - $new_password_encoded = hash('whirlpool',$new_password); - - $result['password'] = $new_password; - - require_once('include/crypto.php'); - - $keys = new_keypair(4096); - - if($keys === false) { - $result['message'] .= t('SERIOUS ERROR: Generation of security keys failed.') . EOL; - return $result; - } - - $default_service_class = get_config('system','default_service_class'); - if(! $default_service_class) - $default_service_class = ''; - - - $prvkey = $keys['prvkey']; - $pubkey = $keys['pubkey']; - - $r = q("INSERT INTO `user` ( `guid`, `username`, `password`, `email`, `openid`, `nickname`, - `pubkey`, `prvkey`, `register_date`, `verified`, `blocked`, `timezone`, `service_class` ) - VALUES ( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, 'UTC', '%s' )", - dbesc(generate_user_guid()), - dbesc($username), - dbesc($new_password_encoded), - dbesc($email), - dbesc($openid_url), - dbesc($nickname), - dbesc($pubkey), - dbesc($prvkey), - dbesc(datetime_convert()), - intval($verified), - intval($blocked), - dbesc($default_service_class) - ); - - if($r) { - $r = q("SELECT * FROM `user` - WHERE `username` = '%s' AND `password` = '%s' LIMIT 1", - dbesc($username), - dbesc($new_password_encoded) - ); - if($r !== false && count($r)) { - $u = $r[0]; - $newuid = intval($r[0]['uid']); - } - } - else { - $result['message'] .= t('An error occurred during registration. Please try again.') . EOL ; - return $result; - } - - /** - * if somebody clicked submit twice very quickly, they could end up with two accounts - * due to race condition. Remove this one. - */ - - $r = q("SELECT `uid` FROM `user` - WHERE `nickname` = '%s' ", - dbesc($nickname) - ); - if((count($r) > 1) && $newuid) { - $result['message'] .= t('Nickname is already registered. Please choose another.') . EOL; - q("DELETE FROM `user` WHERE `uid` = %d LIMIT 1", - intval($newuid) - ); - return $result; - } - - if(x($newuid) !== false) { - $r = q("INSERT INTO `profile` ( `uid`, `profile_name`, `is_default`, `name`, `photo`, `thumb`, `publish`, `net-publish` ) - VALUES ( %d, '%s', %d, '%s', '%s', '%s', %d, %d ) ", - intval($newuid), - t('default'), - 1, - dbesc($username), - dbesc($a->get_baseurl() . "/photo/profile/{$newuid}"), - dbesc($a->get_baseurl() . "/photo/avatar/{$newuid}"), - intval($publish), - intval($netpublish) - - ); - if($r === false) { - $result['message'] .= t('An error occurred creating your default profile. Please try again.') . EOL; - // Start fresh next time. - $r = q("DELETE FROM `user` WHERE `uid` = %d", - intval($newuid)); - return $result; - } - $r = q("INSERT INTO `contact` ( `uid`, `created`, `self`, `name`, `nick`, `photo`, `thumb`, `micro`, `blocked`, `pending`, `url`, `nurl`, - `request`, `notify`, `poll`, `confirm`, `poco`, `name_date`, `uri_date`, `avatar_date`, `closeness` ) - VALUES ( %d, '%s', 1, '%s', '%s', '%s', '%s', '%s', 0, 0, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', 0 ) ", - intval($newuid), - datetime_convert(), - dbesc($username), - dbesc($nickname), - dbesc($a->get_baseurl() . "/photo/profile/{$newuid}"), - dbesc($a->get_baseurl() . "/photo/avatar/{$newuid}"), - dbesc($a->get_baseurl() . "/photo/micro/{$newuid}"), - dbesc($a->get_baseurl() . "/channel/$nickname"), - dbesc(normalise_link($a->get_baseurl() . "/channel/$nickname")), - dbesc($a->get_baseurl() . "/dfrn_request/$nickname"), - dbesc($a->get_baseurl() . "/dfrn_notify/$nickname"), - dbesc($a->get_baseurl() . "/dfrn_poll/$nickname"), - dbesc($a->get_baseurl() . "/dfrn_confirm/$nickname"), - dbesc($a->get_baseurl() . "/poco/$nickname"), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc(datetime_convert()) - ); - - // Create a group with no members. 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')); - - } - - // if we have no OpenID photo try to look up an avatar - if(! strlen($photo)) - $photo = avatar_img($email); - - // unless there is no avatar-plugin loaded - if(strlen($photo)) { - require_once('include/Photo.php'); - $photo_failure = false; - - $filename = basename($photo); - $img_str = fetch_url($photo,true); - // guess mimetype from headers or filename - $type = guess_image_type($photo,true); - - - $img = new Photo($img_str, $type); - if($img->is_valid()) { - - $img->scaleImageSquare(175); - - $hash = photo_new_resource(); - - $r = $img->store($newuid, 0, $hash, $filename, t('Profile Photos'), 4 ); - - if($r === false) - $photo_failure = true; - - $img->scaleImage(80); - - $r = $img->store($newuid, 0, $hash, $filename, t('Profile Photos'), 5 ); - - if($r === false) - $photo_failure = true; - - $img->scaleImage(48); - - $r = $img->store($newuid, 0, $hash, $filename, t('Profile Photos'), 6 ); - - if($r === false) - $photo_failure = true; - - if(! $photo_failure) { - q("UPDATE `photo` SET `profile` = 1 WHERE `resource_id` = '%s' ", - dbesc($hash) - ); - } - } - } - - call_hooks('register_account', $newuid); - - $result['success'] = true; - $result['user'] = $u; - return $result; - -} diff --git a/include/widgets.php b/include/widgets.php new file mode 100644 index 000000000..96bced87f --- /dev/null +++ b/include/widgets.php @@ -0,0 +1,782 @@ +<?php /** @file */ + +require_once('include/dir_fns.php'); +require_once('include/contact_widgets.php'); + + +function widget_profile($args) { + $a = get_app(); + $block = (((get_config('system','block_public')) && (! local_user()) && (! remote_user())) ? true : false); + return profile_sidebar($a->profile, $block, true); +} + +// FIXME The problem with the next widget is that we don't have a search function for webpages that we can send the links to. +// Then we should also provide an option to search webpages and conversations. + +function widget_tagcloud($args) { + + $o = ''; + $tab = 0; + $a = get_app(); + $uid = $a->profile_uid; + $count = ((x($args,'count')) ? intval($args['count']) : 24); + $flags = 0; + $type = TERM_CATEGORY; + + $r = tagadelic($uid,$count,$authors,$flags,ITEM_WEBPAGE,$type); + + if($r) { + $o = '<div class="tagblock widget"><h3>' . t('Categories') . '</h3><div class="tags" align="center">'; + foreach($r as $rr) { + $o .= '<span class="tag'.$rr[2].'">'.$rr[0].'</span> ' . "\r\n"; + } + $o .= '</div></div>'; + } + return $o; +} + +function widget_collections($args) { + require_once('include/group.php'); + + $mode = ((array_key_exists('mode',$args)) ? $args['mode'] : 'conversation'); + switch($mode) { + case 'conversation': + $every = argv(0); + $each = argv(0); + $edit = true; + $current = $_REQUEST['gid']; + $abook_id = 0; + $wmode = 0; + break; + case 'groups': + $every = 'connections'; + $each = argv(0); + $edit = false; + $current = intval(argv(1)); + $abook_id = 0; + $wmode = 1; + break; + case 'abook': + $every = 'connections'; + $each = 'group'; + $edit = false; + $current = 0; + $abook_id = get_app()->poi['abook_xchan']; + $wmode = 1; + break; + default: + return ''; + break; + } + + return group_side($every, $each, $edit, $current, $abook_id, $wmode); + +} + + +function widget_appselect($arr) { + return replace_macros(get_markup_template('app_select.tpl'),array( + '$title' => t('Apps'), + '$system' => t('System'), + '$authed' => ((local_user()) ? true : false), + '$personal' => t('Personal'), + '$new' => t('Create Personal App'), + '$edit' => t('Edit Personal App') + )); +} + + + +function widget_suggestions($arr) { + + if((! local_user()) || (! feature_enabled(local_user(),'suggest'))) + return ''; + + require_once('include/socgraph.php'); + + $r = suggestion_query(local_user(),get_observer_hash(),0,20); + + if(! $r) { + return; + } + + $arr = array(); + + // Get two random entries from the top 20 returned. + // We'll grab the first one and the one immediately following. + // This will throw some entropy intot he situation so you won't + // be looking at the same two mug shots every time the widget runs + + + $index = ((count($r) > 2) ? mt_rand(0,count($r) - 2) : 0); + + + for($x = $index; $x <= ($index+1); $x ++) { + + $rr = $r[$x]; + if(! $rr['xchan_url']) + break; + + $connlnk = z_root() . '/follow/?url=' . $rr['xchan_addr']; + + $arr[] = array( + 'url' => chanlink_url($rr['xchan_url']), + 'profile' => $rr['xchan_url'], + 'name' => $rr['xchan_name'], + 'photo' => $rr['xchan_photo_m'], + 'ignlnk' => z_root() . '/suggest?ignore=' . $rr['xchan_hash'], + 'conntxt' => t('Connect'), + 'connlnk' => $connlnk, + 'ignore' => t('Ignore/Hide') + ); + } + + + $o = replace_macros(get_markup_template('suggest_widget.tpl'),array( + '$title' => t('Suggestions'), + '$more' => t('See more...'), + '$entries' => $arr + )); + + return $o; + +} + + +function widget_follow($args) { + if(! local_user()) + return ''; + $a = get_app(); + $uid =$a->channel['channel_id']; + $r = q("select count(*) as total from abook where abook_channel = %d and not (abook_flags & %d) ", + intval($uid), + intval(ABOOK_FLAG_SELF) + ); + if($r) + $total_channels = $r[0]['total']; + $limit = service_class_fetch($uid,'total_channels'); + if($limit !== false) { + $abook_usage_message = sprintf( t("You have %1$.0f of %2$.0f allowed connections."), $total_channels, $limit); + } + else { + $abook_usage_message = ''; + } + return replace_macros(get_markup_template('follow.tpl'),array( + '$connect' => t('Add New Connection'), + '$desc' => t('Enter the channel address'), + '$hint' => t('Example: bob@example.com, http://example.com/barbara'), + '$follow' => t('Connect'), + '$abook_usage_message' => $abook_usage_message + )); + +} + + +function widget_notes($arr) { + if(! local_user()) + return ''; + if(! feature_enabled(local_user(),'private_notes')) + return ''; + + $text = get_pconfig(local_user(),'notes','text'); + + $o = replace_macros(get_markup_template('notes.tpl'), array( + '$banner' => t('Notes'), + '$text' => $text, + '$save' => t('Save'), + )); + return $o; +} + + +function widget_savedsearch($arr) { + if((! local_user()) || (! feature_enabled(local_user(),'savedsearch'))) + return ''; + + $a = get_app(); + + $search = ((x($_GET,'search')) ? $_GET['search'] : ''); + + if(x($_GET,'searchsave') && $search) { + $r = q("select * from `term` where `uid` = %d and `type` = %d and `term` = '%s' limit 1", + intval(local_user()), + intval(TERM_SAVEDSEARCH), + dbesc($search) + ); + if(! $r) { + q("insert into `term` ( `uid`,`type`,`term` ) values ( %d, %d, '%s') ", + intval(local_user()), + intval(TERM_SAVEDSEARCH), + dbesc($search) + ); + } + } + + if(x($_GET,'searchremove') && $search) { + q("delete from `term` where `uid` = %d and `type` = %d and `term` = '%s' limit 1", + intval(local_user()), + intval(TERM_SAVEDSEARCH), + dbesc($search) + ); + $search = ''; + } + + + + $srchurl = $a->query_string; + + $srchurl = rtrim(preg_replace('/searchsave\=[^\&].*?(\&|$)/is','',$srchurl),'&'); + $hasq = ((strpos($srchurl,'?') !== false) ? true : false); + $srchurl = rtrim(preg_replace('/searchremove\=[^\&].*?(\&|$)/is','',$srchurl),'&'); + $hasq = ((strpos($srchurl,'?') !== false) ? true : false); + + $srchurl = rtrim(preg_replace('/search\=[^\&].*?(\&|$)/is','',$srchurl),'&'); + $srchurl = str_replace(array('?f=','&f='),array('',''),$srchurl); + $hasq = ((strpos($srchurl,'?') !== false) ? true : false); + + $o = ''; + + $r = q("select `tid`,`term` from `term` WHERE `uid` = %d and `type` = %d ", + intval(local_user()), + intval(TERM_SAVEDSEARCH) + ); + + $saved = array(); + + if(count($r)) { + foreach($r as $rr) { + + $saved[] = array( + 'id' => $rr['tid'], + 'term' => $rr['term'], + 'dellink' => z_root() . '/' . $srchurl . (($hasq) ? '' : '?f=') . '&searchremove=1&search=' . urlencode($rr['term']), + 'srchlink' => z_root() . '/' . $srchurl . (($hasq) ? '' : '?f=') . '&search=' . urlencode($rr['term']), + 'displayterm' => htmlspecialchars($rr['term'], ENT_COMPAT,'UTF-8'), + 'encodedterm' => urlencode($rr['term']), + 'delete' => t('Remove term'), + 'selected' => ($search==$rr['term']), + ); + } + } + + + $tpl = get_markup_template("saved_searches.tpl"); + $o = replace_macros($tpl, array( + '$title' => t('Saved Searches'), + '$add' => t('add'), + '$searchbox' => searchbox($search,'netsearch-box',$srchurl . (($hasq) ? '' : '?f='),true), + '$saved' => $saved, + )); + + return $o; + +} + + +function widget_filer($arr) { + if(! local_user()) + return ''; + + $a = get_app(); + + $selected = ((x($_REQUEST,'file')) ? $_REQUEST['file'] : ''); + + $terms = array(); + $r = q("select distinct(term) from term where uid = %d and type = %d order by term asc", + intval(local_user()), + intval(TERM_FILE) + ); + if(! $r) + return; + + foreach($r as $rr) + $terms[] = array('name' => $rr['term'], 'selected' => (($selected == $rr['term']) ? 'selected' : '')); + + return replace_macros(get_markup_template('fileas_widget.tpl'),array( + '$title' => t('Saved Folders'), + '$desc' => '', + '$sel_all' => (($selected == '') ? 'selected' : ''), + '$all' => t('Everything'), + '$terms' => $terms, + '$base' => z_root() . '/' . $a->cmd + + )); +} + +function widget_archive($arr) { + + $o = ''; + $a = get_app(); + + if(! $a->profile_uid) { + return ''; + } + + $uid = $a->profile_uid; + + if(! feature_enabled($uid,'archives')) + return ''; + + if(! perm_is_allowed($uid,get_observer_hash(),'view_stream')) + return ''; + + + $wall = ((array_key_exists('wall', $arr)) ? intval($arr['wall']) : 0); + $style = ((array_key_exists('style', $arr)) ? $arr['style'] : 'select'); + $url = z_root() . '/' . $a->cmd; + + + $ret = list_post_dates($uid,$wall); + + if(! count($ret)) + return ''; + + $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array( + '$title' => t('Archives'), + '$size' => ((count($ret) > 6) ? 6 : count($ret)), + '$url' => $url, + '$style' => $style, + '$dates' => $ret + )); + return $o; +} + + +function widget_fullprofile($arr) { + $a = get_app(); + if(! $a->profile['profile_uid']) + return; + + $block = (((get_config('system','block_public')) && (! local_user()) && (! remote_user())) ? true : false); + + return profile_sidebar($a->profile, $block); +} + +function widget_categories($arr) { + $a = get_app(); + + + if($a->profile['profile_uid'] && (! perm_is_allowed($a->profile['profile_uid'],get_observer_hash(),'view_stream'))) + return ''; + + + $cat = ((x($_REQUEST,'cat')) ? htmlspecialchars($_REQUEST['cat'],ENT_COMPAT,'UTF-8') : ''); + $srchurl = $a->query_string; + $srchurl = rtrim(preg_replace('/cat\=[^\&].*?(\&|$)/is','',$srchurl),'&'); + $srchurl = str_replace(array('?f=','&f='),array('',''),$srchurl); + return categories_widget($srchurl,$cat); + +} + +function widget_tagcloud_wall($arr) { + $a = get_app(); + if((! $a->profile['profile_uid']) || (! $a->profile['channel_hash'])) + return ''; + if(! perm_is_allowed($a->profile['profile_uid'],get_observer_hash(),'view_stream')) + return ''; + + $limit = ((array_key_exists('limit',$arr)) ? intval($arr['limit']) : 50); + if(feature_enabled($a->profile['profile_uid'],'tagadelic')) + return tagblock('search',$a->profile['profile_uid'],$limit,$a->profile['channel_hash'],ITEM_WALL); + return ''; +} + + +function widget_affinity($arr) { + + if(! local_user()) + return ''; + + $cmin = ((x($_REQUEST,'cmin')) ? intval($_REQUEST['cmin']) : 0); + $cmax = ((x($_REQUEST,'cmax')) ? intval($_REQUEST['cmax']) : 99); + + if(feature_enabled(local_user(),'affinity')) { + $tpl = get_markup_template('main_slider.tpl'); + $x = replace_macros($tpl,array( + '$val' => $cmin . ';' . $cmax, + '$refresh' => t('Refresh'), + '$me' => t('Me'), + '$intimate' => t('Best Friends'), + '$friends' => t('Friends'), + '$coworkers' => t('Co-workers'), + '$oldfriends' => t('Former Friends'), + '$acquaintances' => t('Acquaintances'), + '$world' => t('Everybody') + )); + $arr = array('html' => $x); + call_hooks('main_slider',$arr); + return $arr['html']; + } + return ''; +} + + +function widget_settings_menu($arr) { + + if(! local_user()) + return; + + $a = get_app(); + $channel = $a->get_channel(); + + $abook_self_id = 0; + + // Retrieve the 'self' address book entry for use in the auto-permissions link + + $abk = q("select abook_id from abook where abook_channel = %d and ( abook_flags & %d ) limit 1", + intval(local_user()), + intval(ABOOK_FLAG_SELF) + ); + if($abk) + $abook_self_id = $abk[0]['abook_id']; + + + $tabs = array( + array( + 'label' => t('Account settings'), + 'url' => $a->get_baseurl(true).'/settings/account', + 'selected' => ((argv(1) === 'account') ? 'active' : ''), + ), + + array( + 'label' => t('Channel settings'), + 'url' => $a->get_baseurl(true).'/settings/channel', + 'selected' => ((argv(1) === 'channel') ? 'active' : ''), + ), + + array( + 'label' => t('Additional features'), + 'url' => $a->get_baseurl(true).'/settings/features', + 'selected' => ((argv(1) === 'features') ? 'active' : ''), + ), + + array( + 'label' => t('Feature settings'), + 'url' => $a->get_baseurl(true).'/settings/featured', + 'selected' => ((argv(1) === 'featured') ? 'active' : ''), + ), + + array( + 'label' => t('Display settings'), + 'url' => $a->get_baseurl(true).'/settings/display', + 'selected' => ((argv(1) === 'display') ? 'active' : ''), + ), + + array( + 'label' => t('Connected apps'), + 'url' => $a->get_baseurl(true) . '/settings/oauth', + 'selected' => ((argv(1) === 'oauth') ? 'active' : ''), + ), + + array( + 'label' => t('Export channel'), + 'url' => $a->get_baseurl(true) . '/uexport/basic', + 'selected' => '' + ), + +// array( +// 'label' => t('Export account'), +// 'url' => $a->get_baseurl(true) . '/uexport/complete', +// 'selected' => '' +// ), + + array( + 'label' => t('Automatic Permissions (Advanced)'), + 'url' => $a->get_baseurl(true) . '/connedit/' . $abook_self_id, + 'selected' => '' + ), + + + ); + + if(feature_enabled(local_user(),'premium_channel')) { + $tabs[] = array( + 'label' => t('Premium Channel Settings'), + 'url' => $a->get_baseurl(true) . '/connect/' . $channel['channel_address'], + 'selected' => '' + ); + + } + + if(feature_enabled(local_user(),'channel_sources')) { + $tabs[] = array( + 'label' => t('Channel Sources'), + 'url' => $a->get_baseurl(true) . '/sources', + 'selected' => '' + ); + + } + + + + $tabtpl = get_markup_template("generic_links_widget.tpl"); + return replace_macros($tabtpl, array( + '$title' => t('Settings'), + '$class' => 'settings-widget', + '$items' => $tabs, + )); + +} + + +function widget_mailmenu($arr) { + if (! local_user()) + return; + + $a = get_app(); + return replace_macros(get_markup_template('message_side.tpl'), array( + '$tabs'=> array(), + + '$check'=>array( + 'label' => t('Check Mail'), + 'url' => $a->get_baseurl(true) . '/message', + 'sel' => (argv(1) == ''), + ), + '$new'=>array( + 'label' => t('New Message'), + 'url' => $a->get_baseurl(true) . '/mail/new', + 'sel'=> (argv(1) == 'new'), + ) + + )); + +} + +function widget_design_tools($arr) { + $a = get_app(); + + // mod menu doesn't load a profile. For any modules which load a profile, check it. + // otherwise local_user() is sufficient for permissions. + + if($a->profile['profile_uid']) + if($a->profile['profile_uid'] != local_user()) + return ''; + + if(! local_user()) + return ''; + + return design_tools(); +} + +function widget_findpeople($arr) { + return findpeople_widget(); +} + + +function widget_photo_albums($arr) { + $a = get_app(); + if(! $a->profile['profile_uid']) + return ''; + $channelx = channelx_by_n($a->profile['profile_uid']); + if((! $channelx) || (! perm_is_allowed($a->profile['profile_uid'],get_observer_hash(),'view_photos'))) + return ''; + return photos_album_widget($channelx,$a->get_observer()); + +} + + +function widget_vcard($arr) { + require_once ('include/Contact.php'); + return vcard_from_xchan('',get_app()->get_observer()); +} + + +/** + * The following directory widgets are only useful on the directory page + */ + +function widget_dirsafemode($arr) { + return dir_safe_mode(); +} + +function widget_dirsort($arr) { + return dir_sort_links(); +} + +function widget_dirtags($arr) { + return dir_tagblock(z_root() . '/directory',null); +} + +function widget_menu_preview($arr) { + if(! get_app()->data['menu_item']) + return; + require_once('include/menu.php'); + return menu_render(get_app()->data['menu_item']); +} + +function widget_chatroom_list($arr) { + $a = get_app(); + require_once("include/chat.php"); + $r = chatroom_list($a->profile['profile_uid']); + return replace_macros(get_markup_template('chatroomlist.tpl'),array( + '$header' => t('Chat Rooms'), + '$baseurl' => z_root(), + '$nickname' => $a->profile['channel_address'], + '$items' => $r, + )); +} + +function widget_bookmarkedchats($arr) { + $h = get_observer_hash(); + if(! $h) + return; + $r = q("select * from xchat where xchat_xchan = '%s' group by xchat_url order by xchat_desc", + dbesc($h) + ); + + for($x = 0; $x < count($r); $x ++) + $r[$x]['xchat_url'] = zid($r[$x]['xchat_url']); + return replace_macros(get_markup_template('bookmarkedchats.tpl'),array( + '$header' => t('Bookmarked Chatrooms'), + '$rooms' => $r + )); +} + +function widget_suggestedchats($arr) { + + // probably should restrict this to your friends, but then the widget will only work + // if you are logged in locally. + + $h = get_observer_hash(); + if(! $h) + return; + $r = q("select *, count(xchat_url) as total from xchat group by xchat_url order by total desc, xchat_desc limit 24"); + + for($x = 0; $x < count($r); $x ++) + $r[$x]['xchat_url'] = zid($r[$x]['xchat_url']); + return replace_macros(get_markup_template('bookmarkedchats.tpl'),array( + '$header' => t('Suggested Chatrooms'), + '$rooms' => $r + )); +} + +function widget_item($arr) { + $uid = $a->profile['profile_uid']; + if((! $uid) || (! $arr['mid'])) + return ''; + + if(! perm_is_allowed($uid,get_observer_hash(),'view_pages')) + return ''; + + require_once('include/security.php'); + $sql_extra = item_permissions_sql($uid); + + + $r = q("select * from item where mid = '%s' and uid = %d and item_restrict = " . intval(ITEM_WEBPAGE) . " $sql_extra limit 1", + dbesc($arr['mid']), + intval($uid) + ); + + if(! $r) + return ''; + + xchan_query($r); + $r = fetch_post_tags($r,true); + + $o .= prepare_page($r[0]); + return $o; + +} + +function widget_clock($arr) { + + $miltime = 0; + if(isset($arr['military']) && $arr['military']) + $miltime = 1; + +$o = <<< EOT +<div class="widget"> +<h3 class="clockface"></h3> +<script> + +var timerID = null +var timerRunning = false + +function stopclock(){ + if(timerRunning) + clearTimeout(timerID) + timerRunning = false +} + +function startclock(){ + stopclock() + showtime() +} + +function showtime(){ + var now = new Date() + var hours = now.getHours() + var minutes = now.getMinutes() + var seconds = now.getSeconds() + var military = $miltime + var timeValue = "" + if(military) + timeValue = hours + else + timeValue = ((hours > 12) ? hours - 12 : hours) + timeValue += ((minutes < 10) ? ":0" : ":") + minutes +// timeValue += ((seconds < 10) ? ":0" : ":") + seconds + if(! military) + timeValue += (hours >= 12) ? " P.M." : " A.M." + $('.clockface').html(timeValue) + timerID = setTimeout("showtime()",1000) + timerRunning = true +} + +$(document).ready(function() { + startclock(); +}); + +</script> +</div> +EOT; +return $o; + +} + + +/** + * @function widget_photo($arr) + * widget to display a single photo. + * @param array $arr; + * 'src' => URL of photo + * 'zrl' => true or false, use zid in url + * 'style' => CSS string + * URL must be an http or https URL + */ + + +function widget_photo($arr) { + + $style = $zrl = false; + $params = ''; + if(array_key_exists('src',$arr) && isset($arr['src'])) + $url = $arr['src']; + + if(strpos($url,'http') !== 0) + return ''; + + if(array_key_exists('style',$arr) && isset($arr['style'])) + $style = $arr['style']; + + // ensure they can't sneak in an eval(js) function + + if(strpos($style,'(') !== false) + return ''; + + if(array_key_exists('zrl',$arr) && isset($arr['zrl'])) + $zrl = (($arr['zrl']) ? true : false); + + if($zrl) + $url = zid($url); + + $o = '<div class="widget">'; + + $o .= '<img ' . (($zrl) ? ' class="zrl" ' : '') + . (($style) ? ' style="' . $style . '"' : '') + . ' src="' . $url . '" />'; + + $o .= '</div>'; + + return $o; +} diff --git a/include/zot.php b/include/zot.php index e134ac428..4f42ea2b4 100644 --- a/include/zot.php +++ b/include/zot.php @@ -1,12 +1,29 @@ -<?php +<?php /** @file */ require_once('include/crypto.php'); require_once('include/items.php'); /** + * Red implementation of zot protocol. + * + * https://github.com/friendica/red/wiki/zot + * https://github.com/friendica/red/wiki/Zot---A-High-Level-Overview + * + */ + + +/** * * @function zot_new_uid($channel_nick) - * @channel_id = unique nickname of controlling entity + * + * Generates a unique string for use as a zot guid using our DNS-based url, the channel nickname and some entropy. + * The entropy ensures uniqueness against re-installs where the same URL and nickname are chosen. + * NOTE: zot doesn't require this to be unique. Internally we use a whirlpool hash of this guid and the signature + * of this guid signed with the channel private key. This can be verified and should make the probability of + * collision of the verified result negligible within the constraints of our immediate universe. + * + * @param string channel_nickname = unique nickname of controlling entity + * * @returns string * */ @@ -18,73 +35,58 @@ function zot_new_uid($channel_nick) { /** - * - * Given an array of zot_uid(s), return all distinct hubs - * If primary is true, return only primary hubs - * Result is ordered by url to assist in batching. - * Return only the first primary hub as there should only be one. + * @function zot_get_hublocs($hash) + * Given a zot hash, return all distinct hubs. + * This function is used in building the zot discovery packet + * and therefore should only be used by channels which are defined + * on this hub + * @param string $hash - xchan_hash + * @retuns array of hubloc (hub location structures) + * hubloc_id int + * hubloc_guid char(255) + * hubloc_guid_sig text + * hubloc_hash char(255) + * hubloc_addr char(255) + * hubloc_flags int + * hubloc_status int + * hubloc_url char(255) + * hubloc_url_sig text + * hubloc_host char(255) + * hubloc_callback char(255) + * hubloc_connect char(255) + * hubloc_sitekey text + * hubloc_updated datetime + * hubloc_connected datetime * */ -function zot_get_hubloc($arr,$primary = false) { - - $tmp = ''; - - if(is_array($arr)) { - foreach($arr as $e) { - if(strlen($tmp)) - $tmp .= ','; - $tmp .= "'" . dbesc($e) . "'" ; - } - } - - if(! strlen($tmp)) - return array(); +function zot_get_hublocs($hash) { - $sql_extra = (($primary) ? " and hubloc_flags & " . intval(HUBLOC_FLAGS_PRIMARY) : "" ); - $limit = (($primary) ? " limit 1 " : ""); - return q("select * from hubloc where hubloc_hash in ( $tmp ) $sql_extra order by hubloc_url $limit"); + /** Only search for active hublocs - e.g. those that haven't been marked deleted */ -} - -function zot_notify($channel,$url,$type = 'notify',$recipients = null, $remote_key = null) { - - -// FIXME json encode all params -// build the packet externally so that here we really are doing just a zot of the packet. - - $params = array( - 'type' => $type, - 'sender' => json_encode(array( - 'guid' => $channel['channel_guid'], - 'guid_sig' => base64url_encode(rsa_sign($channel['channel_guid'],$channel['channel_prvkey'])), - 'url' => z_root(), - 'url_sig' => base64url_encode(rsa_sign(z_root(),$channel['channel_prvkey'])) - )), - 'callback' => '/post', - 'version' => ZOT_REVISION + $ret = q("select * from hubloc where hubloc_hash = '%s' and not ( hubloc_flags & %d ) group by hubloc_url ", + dbesc($hash), + intval(HUBLOC_FLAGS_DELETED) ); - - - if($recipients) - $params['recipients'] = json_encode($recipients); - - // Hush-hush ultra top-secret mode - - if($remote_key) { - $params = aes_encapsulate($params,$remote_key); - } - - $x = z_post_url($url,$params); - return($x); + return $ret; } - -/* + +/** + * + * @function zot_build_packet($channel,$type = 'notify',$recipients = null, $remote_key = null, $secret = null) + * builds a zot notification packet that you can either + * store in the queue with a message array or call zot_zot to immediately + * zot it to the other side * - * zot_build_packet builds a notification packet that you can either - * store in the queue with a message array or call zot_zot to immediately - * zot it to the other side + * @param array $channel => sender channel structure + * @param string $type => packet type: one of 'ping', 'pickup', 'purge', 'refresh', 'force_refresh', 'notify', 'auth_check' + * @param array $recipients => envelope information, array ( 'guid' => string, 'guid_sig' => string ); empty for public posts + * @param string $remote_key => optional public site key of target hub used to encrypt entire packet + * NOTE: remote_key and encrypted packets are required for 'auth_check' packets, optional for all others + * @param string $secret => random string, required for packets which require verification/callback + * e.g. 'pickup', 'purge', 'notify', 'auth_check'. Packet types 'ping', 'force_refresh', and 'refresh' do not require verification * + * @returns string json encoded zot packet */ function zot_build_packet($channel,$type = 'notify',$recipients = null, $remote_key = null, $secret = null) { @@ -101,30 +103,56 @@ function zot_build_packet($channel,$type = 'notify',$recipients = null, $remote_ 'version' => ZOT_REVISION ); - if($recipients) $data['recipients'] = $recipients; - if($secret) + if($secret) { $data['secret'] = $secret; + $data['secret_sig'] = base64url_encode(rsa_sign($secret,$channel['channel_prvkey'])); + } - logger('zot_build_packet: ' . print_r($data,true)); + logger('zot_build_packet: ' . print_r($data,true), LOGGER_DATA); // Hush-hush ultra top-secret mode if($remote_key) { - $data = aes_encapsulate(json_encode($data),$remote_key); + $data = crypto_encapsulate(json_encode($data),$remote_key); } return json_encode($data); } +/** + * @function: zot_zot + * @param: string $url + * @param: array $data + * + * @returns: array => see z_post_url for returned data format + */ + + + function zot_zot($url,$data) { return z_post_url($url,array('data' => $data)); } -function zot_finger($webbie,$channel) { +/** + * @function: zot_finger + * + * Look up information about channel + * @param: string $webbie + * does not have to be host qualified e.g. 'foo' is treated as 'foo@thishub' + * @param: array $channel + * (optional), if supplied permissions will be enumerated specifically for $channel + * @param: boolean $autofallback + * fallback/failover to http if https connection cannot be established. Default is true. + * + * @returns: array => see z_post_url and mod/zfinger.php + */ + + +function zot_finger($webbie,$channel,$autofallback = true) { if(strpos($webbie,'@') === false) { @@ -138,10 +166,19 @@ function zot_finger($webbie,$channel) { $xchan_addr = $address . '@' . $host; + if((! $address) || (! $xchan_addr)) { + logger('zot_finger: no address :' . $webbie); + return array('success' => false); + } + + // potential issue here; the xchan_addr points to the primary hub. + // The webbie we were called with may not, so it might not be found + // unless we query for hubloc_addr instead of xchan_addr + $r = q("select xchan.*, hubloc.* from xchan left join hubloc on xchan_hash = hubloc_hash where xchan_addr = '%s' and (hubloc_flags & %d) limit 1", - dbesc($xchan_address), + dbesc($xchan_addr), intval(HUBLOC_FLAGS_PRIMARY) ); @@ -153,6 +190,9 @@ function zot_finger($webbie,$channel) { } $rhs = '/.well-known/zot-info'; + $https = ((strpos($url,'https://') === 0) ? true : false); + + logger('zot_finger: ' . $address . ' at ' . $url, LOGGER_DEBUG); if($channel) { $postvars = array( @@ -161,23 +201,61 @@ function zot_finger($webbie,$channel) { 'target_sig' => $channel['channel_guid_sig'], 'key' => $channel['channel_pubkey'] ); + $result = z_post_url($url . $rhs,$postvars); - if(! $result['success']) - $result = z_post_url('http://' . $host . $rhs,$postvars); + + + if((! $result['success']) && ($autofallback)) { + if($https) { + logger('zot_finger: https failed. falling back to http'); + $result = z_post_url('http://' . $host . $rhs,$postvars); + } + } } else { - $rhs .= 'address=' . urlencode($address); + $rhs .= '?f=&address=' . urlencode($address); $result = z_fetch_url($url . $rhs); - if(! $result['success']) - $result = z_fetch_url('http://' . $host . $rhs); + if((! $result['success']) && ($autofallback)) { + if($https) { + logger('zot_finger: https failed. falling back to http'); + $result = z_fetch_url('http://' . $host . $rhs); + } + } } + if(! $result['success']) + logger('zot_finger: no results'); + return $result; } -function zot_refresh($them,$channel = null) { +/** + * @function: zot_refresh($them, $channel = null, $force = false) + * + * zot_refresh is typically invoked when somebody has changed permissions of a channel and they are notified + * to fetch new permissions via a finger/discovery operation. This may result in a new connection + * (abook entry) being added to a local channel and it may result in auto-permissions being granted. + * + * Friending in zot is accomplished by sending a refresh packet to a specific channel which indicates a + * permission change has been made by the sender which affects the target channel. The hub controlling + * the target channel does targetted discovery (a zot-finger request requesting permissions for the local + * channel). These are decoded here, and if necessary and abook structure (addressbook) is created to store + * the permissions assigned to this channel. + * + * Initially these abook structures are created with a 'pending' flag, so that no reverse permissions are + * implied until this is approved by the owner channel. A channel can also auto-populate permissions in + * return and send back a refresh packet of its own. This is used by forum and group communication channels + * so that friending and membership in the channel's "club" is automatic. + * + * @param array $them => xchan structure of sender + * @param array $channel => local channel structure of target recipient, required for "friending" operations + * + * @returns boolean true if successful, else false + */ + +function zot_refresh($them,$channel = null, $force = false) { logger('zot_refresh: them: ' . print_r($them,true), LOGGER_DATA); if($channel) @@ -220,26 +298,28 @@ function zot_refresh($them,$channel = null) { $result = z_post_url($url . $rhs,$postvars); - logger('zot_refresh: zot-info: ' . print_r($result,true)); + logger('zot_refresh: zot-info: ' . print_r($result,true), LOGGER_DATA); if($result['success']) { $j = json_decode($result['body'],true); - $x = import_xchan($j); + if(! (($j) && ($j['success']))) { + logger('zot_refresh: result not decodable'); + return false; + } - if(! $x['success']) - return $x; + $x = import_xchan($j,(($force) ? UPDATE_FLAGS_FORCED : UPDATE_FLAGS_UPDATED)); - $xchan_hash = $x['hash']; + if(! $x['success']) + return false; $their_perms = 0; - if($channel) { $global_perms = get_perms(); if($j['permissions']['data']) { - $permissions = aes_unencapsulate(array( + $permissions = crypto_unencapsulate(array( 'data' => $j['permissions']['data'], 'key' => $j['permissions']['key'], 'iv' => $j['permissions']['iv']), @@ -251,8 +331,15 @@ function zot_refresh($them,$channel = null) { else $permissions = $j['permissions']; + $connected_set = false; + if($permissions && is_array($permissions)) { foreach($permissions as $k => $v) { + // The connected permission means you are in their address book + if($k === 'connected') { + $connected_set = intval($v); + continue; + } if($v) { $their_perms = $their_perms | intval($global_perms[$k][1]); } @@ -264,17 +351,57 @@ function zot_refresh($them,$channel = null) { intval($channel['channel_id']), intval(ABOOK_FLAG_SELF) ); - if($r) { - $y = q("update abook set abook_their_perms = %d + + if(array_key_exists('profile',$j) && array_key_exists('next_birthday',$j['profile'])) { + $next_birthday = datetime_convert('UTC','UTC',$j['profile']['next_birthday']); + } + else { + $next_birthday = '0000-00-00 00:00:00'; + } + + if($r) { + + // if the dob is the same as what we have stored (disregarding the year), keep the one + // we have as we may have updated the year after sending a notification; and resetting + // to the one we just received would cause us to create duplicated events. + + if(substr($r[0]['abook_dob'],5) == substr($next_birthday,5)) + $next_birthday = $r[0]['abook_dob']; + + $current_abook_connected = (($r[0]['abook_flags'] & ABOOK_FLAG_UNCONNECTED) ? 0 : 1); + + $y = q("update abook set abook_their_perms = %d, abook_dob = '%s' where abook_xchan = '%s' and abook_channel = %d and not (abook_flags & %d) limit 1", intval($their_perms), + dbesc($next_birthday), dbesc($x['hash']), intval($channel['channel_id']), intval(ABOOK_FLAG_SELF) ); + +// if(($connected_set === 0 || $connected_set === 1) && ($connected_set !== $current_abook_unconnected)) { + + // if they are in your address book but you aren't in theirs, and/or this does not + // match your current connected state setting, toggle it. + +// $y1 = q("update abook set abook_flags = (abook_flags ^ %d) +// where abook_xchan = '%s' and abook_channel = %d +// and not (abook_flags & %d) limit 1", +// intval(ABOOK_FLAG_UNCONNECTED), +// dbesc($x['hash']), +// intval($channel['channel_id']), +// intval(ABOOK_FLAG_SELF) +// ); +// } + if(! $y) logger('abook update failed'); + else { + // if we were just granted read stream permission and didn't have it before, try to pull in some posts + if((! ($r[0]['abook_their_perms'] & PERMS_R_STREAM)) && ($their_perms & PERMS_R_STREAM)) + proc_run('php','include/onepoll.php',$r[0]['abook_id']); + } } else { $default_perms = 0; @@ -283,10 +410,14 @@ function zot_refresh($them,$channel = null) { intval($channel['channel_id']), intval(ABOOK_FLAG_SELF) ); + if($z) - $default_perms = intval($z[0]['my_perms']); + $default_perms = intval($z[0]['abook_my_perms']); + + // Keep original perms to check if we need to notify them + $previous_perms = get_all_perms($channel['channel_id'],$x['hash']); - $y = q("insert into abook ( abook_account, abook_channel, abook_xchan, abook_their_perms, abook_my_perms, abook_created, abook_updated, abook_flags ) values ( %d, %d, '%s', %d, %d, '%s', '%s', %d )", + $y = q("insert into abook ( abook_account, abook_channel, abook_xchan, abook_their_perms, abook_my_perms, abook_created, abook_updated, abook_dob, abook_flags ) values ( %d, %d, '%s', %d, %d, '%s', '%s', '%s', %d )", intval($channel['channel_account_id']), intval($channel['channel_id']), dbesc($x['hash']), @@ -294,41 +425,43 @@ function zot_refresh($them,$channel = null) { intval($default_perms), dbesc(datetime_convert()), dbesc(datetime_convert()), + dbesc($next_birthday), intval(($default_perms) ? 0 : ABOOK_FLAG_PENDING) ); + if($y) { logger("New introduction received for {$channel['channel_name']}"); - if($default_perms) { - // send back a permissions update for auto-friend/auto-permissions + $new_perms = get_all_perms($channel['channel_id'],$x['hash']); + if($new_perms != $previous_perms) { + // Send back a permissions update if permissions have changed $z = q("select * from abook where abook_xchan = '%s' and abook_channel = %d and not (abook_flags & %d) limit 1", dbesc($x['hash']), intval($channel['channel_id']), intval(ABOOK_FLAG_SELF) ); if($z) - proc_run('php','include/notifier.php','permissions_update',$z[0]['abook_id']); + proc_run('php','include/notifier.php','permission_update',$z[0]['abook_id']); } - } - } - } - else { - - logger('zot_refresh: importing profile if available'); - - // Are we a directory server of some kind? - $dirmode = intval(get_config('system','directory_mode')); - if($dirmode != DIRECTORY_MODE_NORMAL) { - if(array_key_exists('profile',$x) && is_array($x['profile'])) { - import_directory_profile($x['hash'],$x['profile']); - } - else { - // they may have made it private - $r = q("delete from xprof where xprof_hash = '%s' limit 1", - dbesc($x['hash']) - ); - $r = q("delete from xtag where xtag_hash = '%s' limit 1", + $new_connection = q("select abook_id, abook_flags from abook where abook_channel = %d and abook_xchan = '%s' order by abook_created desc limit 1", + intval($channel['channel_id']), dbesc($x['hash']) ); + if($new_connection) { + require_once('include/enotify.php'); + notification(array( + 'type' => NOTIFY_INTRO, + 'from_xchan' => $x['hash'], + 'to_xchan' => $channel['channel_hash'], + 'link' => z_root() . '/connedit/' . $new_connection[0]['abook_id'], + )); + } + + if($new_connection && ($their_perms & PERMS_R_STREAM)) { + if(($channel['channel_w_stream'] & PERMS_PENDING) + || (! ($new_connection[0]['abook_flags'] & ABOOK_FLAG_PENDING)) ) + proc_run('php','include/onepoll.php',$new_connection[0]['abook_id']); + } + } } } @@ -337,6 +470,25 @@ function zot_refresh($them,$channel = null) { return false; } +/** + * @function: zot_gethub + * + * A guid and a url, both signed by the sender, distinguish a known sender at a known location + * This function looks these up to see if the channel is known and therefore previously verified. + * If not, we will need to verify it. + * + * @param array $arr + * $arr must contain: + * string $arr['guid'] => guid of conversant + * string $arr['guid_sig'] => guid signed with conversant's private key + * string $arr['url'] => URL of the origination hub of this communication + * string $arr['url_sig'] => URL signed with conversant's private key + * + * + * @returns: array => hubloc record + */ + + function zot_gethub($arr) { @@ -355,10 +507,31 @@ function zot_gethub($arr) { return $r[0]; } } - logger('zot_gethub: not found', LOGGER_DEBUG); + logger('zot_gethub: not found: ' . print_r($arr,true), LOGGER_DEBUG); return null; } +/** + * @function zot_register_hub($arr) + * + * A communication has been received which has an unknown (to us) sender. + * Perform discovery based on our calculated hash of the sender at the origination address. + * This will fetch the discovery packet of the sender, which contains the public key we + * need to verify our guid and url signatures. + * + * @param array $arr + * $arr must contain: + * string $arr['guid'] => guid of conversant + * string $arr['guid_sig'] => guid signed with conversant's private key + * string $arr['url'] => URL of the origination hub of this communication + * string $arr['url_sig'] => URL signed with conversant's private key + * + * + * @returns array => 'success' (boolean true or false) + * 'message' (optional error string only if success is false) + */ + + function zot_register_hub($arr) { $result = array('success' => false); @@ -369,151 +542,72 @@ function zot_register_hub($arr) { $url = $arr['url'] . '/.well-known/zot-info/?f=&guid_hash=' . $guid_hash; - logger('zot_register_hub: ' . $url); + logger('zot_register_hub: ' . $url, LOGGER_DEBUG); $x = z_fetch_url($url); - logger('zot_register_hub: ' . print_r($x,true)); + logger('zot_register_hub: ' . print_r($x,true), LOGGER_DATA); if($x['success']) { $record = json_decode($x['body'],true); - $c = import_xchan($record); - if($c['success']) - $result['success'] = true; - } - } - return $result; -} - - -// Takes a json array from zot_finger and imports the xchan and hublocs -// If the xchan already exists, update the name and photo if these have changed. -// + /* + * We now have a key - only continue registration if our signatures are valid + * AND the guid and guid sig in the returned packet match those provided in + * our current communication. + */ -function import_xchan_from_json($j) { + if((rsa_verify($arr['guid'],base64url_decode($arr['guid_sig']),$record['key'])) + && (rsa_verify($arr['url'],base64url_decode($arr['url_sig']),$record['key'])) + && ($arr['guid'] === $record['guid']) + && ($arr['guid_sig'] === $record['guid_sig'])) { - $ret = array('success' => false); - - $xchan_hash = base64url_encode(hash('whirlpool',$j->guid . $j->guid_sig, true)); - $import_photos = false; - - if(! rsa_verify($j->guid,base64url_decode($j->guid_sig),$j->key)) { - logger('import_xchan_from_json: Unable to verify channel signature for ' . $j->address); - $ret['message'] = t('Unable to verify channel signature'); - return $ret; - } - - $r = q("select * from xchan where xchan_hash = '%s' limit 1", - dbesc($xchan_hash) - ); - - if($r) { - if($r[0]['xchan_photo_date'] != $j->photo_updated) - $update_photos = true; - if(($r[0]['xchan_name_date'] != $j->name_updated) || ($r[0]['xchan_connurl'] != $j->connections_url)) { - $r = q("update xchan set xchan_name = '%s', xchan_name_date = '%s', xchan_connurl = '%s' where xchan_hash = '%s' limit 1", - dbesc($j->name), - dbesc($j->name_updated), - dbesc($j->connections_url), - dbesc($xchan_hash) - ); + $c = import_xchan($record); + if($c['success']) + $result['success'] = true; + } + else { + logger('zot_register_hub: failure to verify returned packet.'); + } } } - else { - $import_photos = true; - $x = q("insert into xchan ( xchan_hash, xchan_guid, xchan_guid_sig, xchan_pubkey, xchan_photo_mimetype, - xchan_photo_l, xchan_addr, xchan_url, xchan_connurl, xchan_name, xchan_network, xchan_photo_date, xchan_name_date) - values ( '%s', '%s', '%s', '%s' , '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ", - dbesc($xchan_hash), - dbesc($j->guid), - dbesc($j->guid_sig), - dbesc($j->key), - dbesc($j->photo_mimetype), - dbesc($j->photo), - dbesc($j->address), - dbesc($j->url), - dbesc($j->connections_url), - dbesc($j->name), - dbesc('zot'), - dbesc($j->photo_updated), - dbesc($j->name_updated) - ); + return $result; +} - } +/** + * @function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED) + * Takes an associative array of a fetched discovery packet and updates + * all internal data structures which need to be updated as a result. + * + * @param array $arr => json_decoded discovery packet + * @param int $ud_flags + * Determines whether to create a directory update record if any changes occur, default is UPDATE_FLAGS_UPDATED (true) + * $ud_flags = UPDATE_FLAGS_FORCED indicates a forced refresh where we unconditionally create a directory update record + * this typically occurs once a month for each channel as part of a scheduled ping to notify the directory + * that the channel still exists + * + * @returns array => 'success' (boolean true or false) + * 'message' (optional error string only if success is false) + */ - if($import_photos) { +function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED) { - require_once("Photo.php"); - $photos = import_profile_photo($j->photo,$xchan_hash); - $r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' - where xchan_hash = '%s' limit 1", - dbesc($j->photo_updated), - dbesc($photos[0]), - dbesc($photos[1]), - dbesc($photos[2]), - dbesc($photos[3]), - dbesc($xchan_hash) - ); - } + call_hooks('import_xchan', $arr); - if($j->locations) { - foreach($j->locations as $location) { - if(! rsa_verify($location->url,base64url_decode($location->url_sig),$j->key)) { - logger('import_xchan_from_json: Unable to verify site signature for ' . $location->url); - $ret['message'] .= sprintf( t('Unable to verify site signature for %s'), $location->url) . EOL; - continue; - } - - $r = q("select * from hubloc where hubloc_hash = '%s' and hubloc_url = '%s' limit 1", - dbesc($xchan_hash), - dbesc($location->url) - ); - if($r) { - if(($r[0]['hubloc_flags'] & HUBLOC_FLAGS_PRIMARY) && (! $location->primary)) { - $r = q("update hubloc set hubloc_flags = (hubloc_flags ^ %d) where hubloc_id = %d limit 1", - intval(HUBLOC_FLAGS_PRIMARY), - intval($r[0]['hubloc_id']) - ); - } - continue; - } - - $r = q("insert into hubloc ( hubloc_guid, hubloc_guid_sig, hubloc_hash, hubloc_addr, hubloc_flags, hubloc_url, hubloc_url_sig, hubloc_host, hubloc_callback, hubloc_sitekey) - values ( '%s','%s','%s','%s', %d ,'%s','%s','%s','%s','%s')", - dbesc($j->guid), - dbesc($j->guid_sig), - dbesc($xchan_hash), - dbesc($location->address), - intval((intval($location->primary)) ? HUBLOC_FLAGS_PRIMARY : 0), - dbesc($location->url), - dbesc($location->url_sig), - dbesc($location->host), - dbesc($location->callback), - dbesc($location->sitekey) - ); - - } + $ret = array('success' => false); + $dirmode = intval(get_config('system','directory_mode')); - } + $changed = false; + $what = ''; - if(! x($ret,'message')) { - $ret['success'] = true; - $ret['hash'] = $xchan_hash; + if(! (is_array($arr) && array_key_exists('success',$arr) && $arr['success'])) { + logger('import_xchan: invalid data packet: ' . print_r($arr,true)); + $ret['message'] = t('Invalid data packet'); + return $ret; } - return $ret; -} - -// Takes a json associative array from zot_finger and imports the xchan and hublocs -// If the xchan already exists, update the name and photo if these have changed. -// - -function import_xchan($arr) { - - $ret = array('success' => false); $xchan_hash = base64url_encode(hash('whirlpool',$arr['guid'] . $arr['guid_sig'], true)); $import_photos = false; @@ -524,27 +618,98 @@ function import_xchan($arr) { return $ret; } + logger('import_xchan: ' . $xchan_hash, LOGGER_DEBUG); + $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($xchan_hash) ); + if(! array_key_exists('connect_url', $arr)) + $arr['connect_url'] = ''; + + if(strpos($arr['address'],'/') !== false) + $arr['address'] = substr($arr['address'],0,strpos($arr['address'],'/')); + if($r) { if($r[0]['xchan_photo_date'] != $arr['photo_updated']) - $update_photos = true; - if(($r[0]['xchan_name_date'] != $arr['name_updated']) || ($r[0]['xchan_connurl'] != $arr['connections_url'])) { - $r = q("update xchan set xchan_name = '%s', xchan_name_date = '%s', xchan_connurl = '%s' where xchan_hash = '%s' limit 1", + $import_photos = true; + + // if we import an entry from a site that's not ours and either or both of us is off the grid - hide the entry. + // TODO: check if we're the same directory realm, which would mean we are allowed to see it + + $dirmode = get_config('system','directory_mode'); + + if((($arr['site']['directory_mode'] === 'standalone') || ($dirmode & DIRECTORY_MODE_STANDALONE)) +&& ($arr['site']['url'] != z_root())) + $arr['searchable'] = false; + + $hidden = (1 - intval($arr['searchable'])); + + // Be careful - XCHAN_FLAGS_HIDDEN should evaluate to 1 + if(($r[0]['xchan_flags'] & XCHAN_FLAGS_HIDDEN) != $hidden) + $new_flags = $r[0]['xchan_flags'] ^ XCHAN_FLAGS_HIDDEN; + else + $new_flags = $r[0]['xchan_flags']; + + $adult = (($r[0]['xchan_flags'] & XCHAN_FLAGS_SELFCENSORED) ? true : false); + $adult_changed = ((intval($adult) != intval($arr['adult_content'])) ? true : false); + if($adult_changed) + $new_flags = $new_flags ^ XCHAN_FLAGS_SELFCENSORED; + + $deleted = (($r[0]['xchan_flags'] & XCHAN_FLAGS_DELETED) ? true : false); + $deleted_changed = ((intval($deleted) != intval($arr['deleted'])) ? true : false); + if($deleted_changed) + $new_flags = $new_flags ^ XCHAN_FLAGS_DELETED; + + if(($r[0]['xchan_name_date'] != $arr['name_updated']) + || ($r[0]['xchan_connurl'] != $arr['connections_url']) + || ($r[0]['xchan_flags'] != $new_flags) + || ($r[0]['xchan_addr'] != $arr['address']) + || ($r[0]['xchan_follow'] != $arr['follow_url']) + || ($r[0]['xchan_connpage'] != $arr['connect_url']) + || ($r[0]['xchan_url'] != $arr['url'])) { + $r = q("update xchan set xchan_name = '%s', xchan_name_date = '%s', xchan_connurl = '%s', xchan_follow = '%s', + xchan_connpage = '%s', xchan_flags = %d, + xchan_addr = '%s', xchan_url = '%s' where xchan_hash = '%s' limit 1", dbesc($arr['name']), dbesc($arr['name_updated']), dbesc($arr['connections_url']), + dbesc($arr['follow_url']), + dbesc($arr['connect_url']), + intval($new_flags), + dbesc($arr['address']), + dbesc($arr['url']), dbesc($xchan_hash) ); + + logger('import_xchan: existing: ' . print_r($r[0],true), LOGGER_DATA); + logger('import_xchan: new: ' . print_r($arr,true), LOGGER_DATA); + $what .= 'xchan '; + $changed = true; } } else { $import_photos = true; + + + if((($arr['site']['directory_mode'] === 'standalone') || ($dirmode & DIRECTORY_MODE_STANDALONE)) +&& ($arr['site']['url'] != z_root())) + $arr['searchable'] = false; + + $hidden = (1 - intval($arr['searchable'])); + + if($hidden) + $new_flags = XCHAN_FLAGS_HIDDEN; + else + $new_flags = 0; + if($arr['adult_content']) + $new_flags |= XCHAN_FLAGS_SELFCENSORED; + if(array_key_exists('deleted',$arr) && $arr['deleted']) + $new_flags |= XCHAN_FLAGS_DELETED; + $x = q("insert into xchan ( xchan_hash, xchan_guid, xchan_guid_sig, xchan_pubkey, xchan_photo_mimetype, - xchan_photo_l, xchan_addr, xchan_url, xchan_connurl, xchan_name, xchan_network, xchan_photo_date, xchan_name_date) - values ( '%s', '%s', '%s', '%s' , '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ", + xchan_photo_l, xchan_addr, xchan_url, xchan_connurl, xchan_follow, xchan_connpage, xchan_name, xchan_network, xchan_photo_date, xchan_name_date, xchan_flags) + values ( '%s', '%s', '%s', '%s' , '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d) ", dbesc($xchan_hash), dbesc($arr['guid']), dbesc($arr['guid_sig']), @@ -554,32 +719,95 @@ function import_xchan($arr) { dbesc($arr['address']), dbesc($arr['url']), dbesc($arr['connections_url']), + dbesc($arr['follow_url']), + dbesc($arr['connect_url']), dbesc($arr['name']), dbesc('zot'), dbesc($arr['photo_updated']), - dbesc($arr['name_updated']) + dbesc($arr['name_updated']), + intval($new_flags) ); + $what .= 'new_xchan'; + $changed = true; + } if($import_photos) { - require_once("Photo.php"); + require_once('include/photo/photo_driver.php'); - $photos = import_profile_photo($arr['photo'],$xchan_hash); - $r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' - where xchan_hash = '%s' limit 1", - dbesc($arr['photo_updated']), - dbesc($photos[0]), - dbesc($photos[1]), - dbesc($photos[2]), - dbesc($photos[3]), - dbesc($xchan_hash) + // see if this is a channel clone that's hosted locally - which we treat different from other xchans/connections + + $local = q("select channel_account_id, channel_id from channel where channel_hash = '%s' limit 1", + dbesc($xchan_hash) ); + if($local) { + $ph = z_fetch_url($arr['photo'],true); + if($ph['success']) { + import_channel_photo($ph['body'], $arr['photo_mimetype'], $local[0]['channel_account_id'],$local[0]['channel_id']); + // reset the names in case they got messed up when we had a bug in this function + $photos = array( + z_root() . '/photo/profile/l/' . $local[0]['channel_id'], + z_root() . '/photo/profile/m/' . $local[0]['channel_id'], + z_root() . '/photo/profile/s/' . $local[0]['channel_id'], + $arr['photo_mimetype'], + false + ); + } + } + else { + $photos = import_profile_photo($arr['photo'],$xchan_hash); + } + if($photos) { + if($photos[4]) { + // importing the photo failed somehow. Leave the photo_date alone so we can try again at a later date. + // This often happens when somebody joins the matrix with a bad cert. + $r = q("update xchan set xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' + where xchan_hash = '%s' limit 1", + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc($photos[3]), + dbesc($xchan_hash) + ); + } + else { + $r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' + where xchan_hash = '%s' limit 1", + dbesc(datetime_convert('UTC','UTC',$arr['photo_updated'])), + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc($photos[3]), + dbesc($xchan_hash) + ); + } + $what .= 'photo '; + $changed = true; + } } + // what we are missing for true hub independence is for any changes in the primary hub to + // get reflected not only in the hublocs, but also to update the URLs and addr in the appropriate xchan + if($arr['locations']) { + + $xisting = q("select hubloc_id, hubloc_url, hubloc_sitekey from hubloc where hubloc_hash = '%s'", + dbesc($xchan_hash) + ); + + // See if a primary is specified + + $has_primary = false; + foreach($arr['locations'] as $location) { + if($location['primary']) { + $has_primary = true; + break; + } + } + foreach($arr['locations'] as $location) { if(! rsa_verify($location['url'],base64url_decode($location['url_sig']),$arr['key'])) { logger('import_xchan: Unable to verify site signature for ' . $location['url']); @@ -587,22 +815,115 @@ function import_xchan($arr) { continue; } - $r = q("select * from hubloc where hubloc_hash = '%s' and hubloc_url = '%s' limit 1", + // Ensure that they have one primary hub + + if(! $has_primary) + $location['primary'] = true; + + + for($x = 0; $x < count($xisting); $x ++) { + if(($xisting[$x]['hubloc_url'] === $location['url']) && ($xisting[$x]['hubloc_sitekey'] === $location['sitekey'])) { + $xisting[$x]['updated'] = true; + } + } + + if(! $location['sitekey']) { + logger('import_xchan: empty hubloc sitekey. ' . print_r($location,true)); + continue; + } + + // Catch some malformed entries from the past which still exist + + if(strpos($location['address'],'/') !== false) + $location['address'] = substr($location['address'],0,strpos($location['address'],'/')); + + // match as many fields as possible in case anything at all changed. + + $r = q("select * from hubloc where hubloc_hash = '%s' and hubloc_guid = '%s' and hubloc_guid_sig = '%s' and hubloc_url = '%s' and hubloc_url_sig = '%s' and hubloc_host = '%s' and hubloc_addr = '%s' and hubloc_callback = '%s' and hubloc_sitekey = '%s' ", dbesc($xchan_hash), - dbesc($location['url']) + dbesc($arr['guid']), + dbesc($arr['guid_sig']), + dbesc($location['url']), + dbesc($location['url_sig']), + dbesc($location['host']), + dbesc($location['address']), + dbesc($location['callback']), + dbesc($location['sitekey']) ); if($r) { - if(($r[0]['hubloc_flags'] & HUBLOC_FLAGS_PRIMARY) && (! $location['primary'])) { - $r = q("update hubloc set hubloc_flags = (hubloc_flags ^ %d) where hubloc_id = %d limit 1", + logger('import_xchan: hub exists: ' . $location['url']); + // update connection timestamp if this is the site we're talking to + if($location['url'] == $arr['site']['url']) { + q("update hubloc set hubloc_connected = '%s', hubloc_updated = '%s' where hubloc_id = %d limit 1", + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($r[0]['hubloc_id']) + ); + } + if($r[0]['hubloc_status'] & HUBLOC_OFFLINE) { + q("update hubloc set hubloc_status = (hubloc_status ^ %d) where hubloc_id = %d limit 1", + intval(HUBLOC_OFFLINE), + intval($r[0]['hubloc_id']) + ); + if($r[0]['hubloc_flags'] & HUBLOC_FLAGS_ORPHANCHECK) { + q("update hubloc set hubloc_flags = (hubloc_flags ^ %d) where hubloc_id = %d limit 1", + intval(HUBLOC_FLAGS_ORPHANCHECK), + intval($r[0]['hubloc_id']) + ); + } + q("update xchan set xchan_flags = (xchan_flags ^ %d) where (xchan_flags & %d) and xchan_hash = '%s' limit 1", + intval(XCHAN_FLAGS_ORPHAN), + intval(XCHAN_FLAGS_ORPHAN), + dbesc($xchan_hash) + ); + + } + + // Remove pure duplicates + if(count($r) > 1) { + for($h = 1; $h < count($r); $h ++) { + q("delete from hubloc where hubloc_id = %d limit 1", + intval($r[$h]['hubloc_id']) + ); + } + } + + if((($r[0]['hubloc_flags'] & HUBLOC_FLAGS_PRIMARY) && (! $location['primary'])) + || ((! ($r[0]['hubloc_flags'] & HUBLOC_FLAGS_PRIMARY)) && ($location['primary']))) { + $r = q("update hubloc set hubloc_flags = (hubloc_flags ^ %d), hubloc_updated = '%s' where hubloc_id = %d limit 1", intval(HUBLOC_FLAGS_PRIMARY), + dbesc(datetime_convert()), intval($r[0]['hubloc_id']) ); + $what = 'primary_hub '; + $changed = true; + } + if((($r[0]['hubloc_flags'] & HUBLOC_FLAGS_DELETED) && (! $location['deleted'])) + || ((! ($r[0]['hubloc_flags'] & HUBLOC_FLAGS_DELETED)) && ($location['deleted']))) { + $r = q("update hubloc set hubloc_flags = (hubloc_flags ^ %d), hubloc_updated = '%s' where hubloc_id = %d limit 1", + intval(HUBLOC_FLAGS_DELETED), + dbesc(datetime_convert()), + intval($r[0]['hubloc_id']) + ); + $what = 'delete_hub '; + $changed = true; } continue; } - $r = q("insert into hubloc ( hubloc_guid, hubloc_guid_sig, hubloc_hash, hubloc_addr, hubloc_flags, hubloc_url, hubloc_url_sig, hubloc_host, hubloc_callback, hubloc_sitekey) - values ( '%s','%s','%s','%s', %d ,'%s','%s','%s','%s','%s')", + // new hub claiming to be primary. Make it so. + + if(intval($location['primary'])) { + $r = q("update hubloc set hubloc_flags = (hubloc_flags ^ %d), hubloc_updated = '%s' where hubloc_hash = '%s' and (hubloc_flags & %d )", + intval(HUBLOC_FLAGS_PRIMARY), + dbesc(datetime_convert()), + dbesc($xchan_hash), + intval(HUBLOC_FLAGS_PRIMARY) + ); + } + logger('import_xchan: new hub: ' . $location['url']); + $r = q("insert into hubloc ( hubloc_guid, hubloc_guid_sig, hubloc_hash, hubloc_addr, hubloc_flags, hubloc_url, hubloc_url_sig, hubloc_host, hubloc_callback, hubloc_sitekey, hubloc_updated, hubloc_connected) + values ( '%s','%s','%s','%s', %d ,'%s','%s','%s','%s','%s','%s','%s')", dbesc($arr['guid']), dbesc($arr['guid_sig']), dbesc($xchan_hash), @@ -612,11 +933,80 @@ function import_xchan($arr) { dbesc($location['url_sig']), dbesc($location['host']), dbesc($location['callback']), - dbesc($location['sitekey']) + dbesc($location['sitekey']), + dbesc(datetime_convert()), + dbesc(datetime_convert()) ); + $what .= 'newhub '; + $changed = true; } + // get rid of any hubs we have for this channel which weren't reported. + // This was needed at one time to resolve complicated cross-site inconsistencies, but can cause sync conflict. + // currently disabled. + +// if($xisting) { +// foreach($xisting as $x) { +// if(! array_key_exists('updated',$x)) { +// logger('import_xchan: removing unreferenced hub location ' . $x['hubloc_url']); +// $r = q("delete from hubloc where hubloc_id = %d limit 1", +// intval($x['hubloc_id']) +// ); +// $what .= 'removed_hub'; +// $changed = true; +// } +// } +// } + + } + + + + + + // Are we a directory server of some kind? + + if($dirmode != DIRECTORY_MODE_NORMAL) { + if(array_key_exists('profile',$arr) && is_array($arr['profile'])) { + $profile_changed = import_directory_profile($xchan_hash,$arr['profile'],$arr['address'],$ud_flags, 1); + if($profile_changed) { + $what .= 'profile '; + $changed = true; + } + } + else { + logger('import_xchan: profile not available - hiding'); + // they may have made it private + $r = q("delete from xprof where xprof_hash = '%s' limit 1", + dbesc($xchan_hash) + ); + $r = q("delete from xtag where xtag_hash = '%s' limit 1", + dbesc($xchan_hash) + ); + } + } + + if(array_key_exists('site',$arr) && is_array($arr['site'])) { + $profile_changed = import_site($arr['site'],$arr['key']); + if($profile_changed) { + $what .= 'site '; + $changed = true; + } + } + + if(($changed) || ($ud_flags == UPDATE_FLAGS_FORCED)) { + $guid = random_string() . '@' . get_app()->get_hostname(); + update_modtime($xchan_hash,$guid,$arr['address'],$ud_flags); + logger('import_xchan: changed: ' . $what,LOGGER_DEBUG); + } + elseif(! $ud_flags) { + // nothing changed but we still need to update the updates record + q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not (ud_flags & %d) ", + intval(UPDATE_FLAGS_UPDATED), + dbesc($arr['address']), + intval(UPDATE_FLAGS_UPDATED) + ); } if(! x($ret,'message')) { @@ -624,10 +1014,26 @@ function import_xchan($arr) { $ret['hash'] = $xchan_hash; } + + logger('import_xchan: result: ' . print_r($ret,true), LOGGER_DATA); return $ret; } +/** + * @function zot_process_response($hub,$arr,$outq) { + * Called immediately after sending a zot message which is using queue processing + * Updates the queue item according to the response result and logs any information + * returned to aid communications troubleshooting. + * + * @param string $hub - url of site we just contacted + * @param array $arr - output of z_post_url() + * @param array $outq - The queue structure attached to this request + * + * @returns nothing + */ + + function zot_process_response($hub,$arr,$outq) { if(! $arr['success']) { @@ -662,6 +1068,20 @@ function zot_process_response($hub,$arr,$outq) { logger('zot_process_response: ' . print_r($x,true), LOGGER_DATA); } +/** + * @function zot_fetch($arr) + * + * We received a notification packet (in mod/post.php) that a message is waiting for us, and we've verified the sender. + * Now send back a pickup message, using our message tracking ID ($arr['secret']), which we will sign with our site private key. + * The entire pickup message is encrypted with the remote site's public key. + * If everything checks out on the remote end, we will receive back a packet containing one or more messages, + * which will be processed and delivered before this function ultimately returns. + * + * @param array $arr + * decrypted and json decoded notify packet from remote site + */ + + function zot_fetch($arr) { logger('zot_fetch: ' . print_r($arr,true), LOGGER_DATA); @@ -670,13 +1090,9 @@ function zot_fetch($arr) { $ret_hub = zot_gethub($arr['sender']); if(! $ret_hub) { - logger('zot_fetch: not ret_hub'); + logger('zot_fetch: no hub: ' . print_r($arr['sender'],true)); return; } - - - $ret_secret = json_encode(array($arr['secret'],'secret_sig' => base64url_encode(rsa_sign($arr['secret'],get_config('system','prvkey'))))); - $data = array( 'type' => 'pickup', @@ -687,29 +1103,43 @@ function zot_fetch($arr) { 'secret_sig' => base64url_encode(rsa_sign($arr['secret'],get_config('system','prvkey'))) ); - - $datatosend = json_encode(aes_encapsulate(json_encode($data),$ret_hub['hubloc_sitekey'])); + $datatosend = json_encode(crypto_encapsulate(json_encode($data),$ret_hub['hubloc_sitekey'])); $fetch = zot_zot($url,$datatosend); - - $result = zot_import($fetch); + $result = zot_import($fetch, $arr['sender']['url']); return $result; } +/** + * @function zot_import + * + * Process an incoming array of messages which were obtained via pickup, and + * import, update, delete as directed. + * + * @param array $arr => 'pickup' structure returned from remote site + * @param string $sender_url => the url specified by the sender in the initial communication + * we will verify the sender and url in each returned message structure and also verify + * that all the messages returned match the site url that we are currently processing. + * + * The message types handled here are 'activity' (e.g. posts), 'mail' , 'profile', and 'channel_sync' + * + * @returns array => array ( [0] => string $channel_hash, [1] => string $delivery_status, [2] => string $address ) + * suitable for logging remotely, enumerating the processing results of each message/recipient combination. + * + */ -function zot_import($arr) { - -// logger('zot_import: ' . print_r($arr,true), LOGGER_DATA); +function zot_import($arr, $sender_url) { $data = json_decode($arr['body'],true); -// logger('zot_import: data1: ' . print_r($data,true)); + if(! $data) { + logger('zot_import: empty body'); + return array(); + } if(array_key_exists('iv',$data)) { - $data = json_decode(aes_unencapsulate($data,get_config('system','prvkey')),true); - } - - logger('zot_import: data' . print_r($data,true), LOGGER_DATA); + $data = json_decode(crypto_unencapsulate($data,get_config('system','prvkey')),true); + } $incoming = $data['pickup']; @@ -720,10 +1150,17 @@ function zot_import($arr) { $result = null; if(array_key_exists('iv',$i['notify'])) { - $i['notify'] = json_decode(aes_unencapsulate($i['notify'],get_config('system','prvkey')),true); - } + $i['notify'] = json_decode(crypto_unencapsulate($i['notify'],get_config('system','prvkey')),true); + } + + logger('zot_import: notify: ' . print_r($i['notify'],true), LOGGER_DATA); + + $hub = zot_gethub($i['notify']['sender']); + if((! $hub) || ($hub['hubloc_url'] != $sender_url)) { + logger('zot_import: potential forgery: wrong site for sender: ' . $sender_url . ' != ' . print_r($i['notify'],true)); + continue; + } - logger('zot_import: notify: ' . print_r($i['notify'],true)); $i['notify']['sender']['hash'] = base64url_encode(hash('whirlpool',$i['notify']['sender']['guid'] . $i['notify']['sender']['guid_sig'], true)); $deliveries = null; @@ -734,12 +1171,11 @@ function zot_import($arr) { foreach($i['notify']['recipients'] as $recip) { $recip_arr[] = base64url_encode(hash('whirlpool',$recip['guid'] . $recip['guid_sig'], true)); } - logger('recip_arr: ' . print_r($recip_arr,true)); stringify_array_elms($recip_arr); - logger('recip_arr: ' . print_r($recip_arr,true)); $recips = implode(',',$recip_arr); - logger('recips: ' . $recips); - $r = q("select channel_hash as hash from channel where channel_hash in ( " . $recips . " ) "); + $r = q("select channel_hash as hash from channel where channel_hash in ( " . $recips . " ) and not ( channel_pageflags & %d ) ", + intval(PAGE_REMOVED) + ); if(! $r) { logger('recips: no recipients on this site'); continue; @@ -749,25 +1185,56 @@ function zot_import($arr) { // We found somebody on this site that's in the recipient list. - } else { + if(($i['message']) && (array_key_exists('flags',$i['message'])) && (in_array('private',$i['message']['flags']))) { + // This should not happen but until we can stop it... + logger('private message was delivered with no recipients.'); + continue; + } + logger('public post'); - // Public post. look for any site members who are accepting posts from this sender - $deliveries = public_recips($i); + // Public post. look for any site members who are or may be accepting posts from this sender + // and who are allowed to see them based on the sender's permissions + + $deliveries = allowed_public_recips($i); + } + + // Go through the hash array and remove duplicates. array_unique() won't do this because the array is more than one level. + + $no_dups = array(); + if($deliveries) { + foreach($deliveries as $d) { + if(! in_array($d['hash'],$no_dups)) + $no_dups[] = $d['hash']; + } + + if($no_dups) { + $deliveries = array(); + foreach($no_dups as $n) { + $deliveries[] = array('hash' => $n); + } + } + } + if(! $deliveries) { logger('zot_import: no deliveries on this site'); continue; } - + if($i['message']) { if($i['message']['type'] === 'activity') { $arr = get_item_elements($i['message']); - logger('Activity received: ' . print_r($arr,true)); - logger('Activity recipients: ' . print_r($deliveries,true)); + if(! array_key_exists('created',$arr)) { + logger('Activity rejected: probable failure to lookup author/owner. ' . print_r($i['message'],true)); + continue; + } + + logger('Activity received: ' . print_r($arr,true), LOGGER_DATA); + logger('Activity recipients: ' . print_r($deliveries,true), LOGGER_DATA); $relay = ((array_key_exists('flags',$i['message']) && in_array('relay',$i['message']['flags'])) ? true : false); $result = process_delivery($i['notify']['sender'],$arr,$deliveries,$relay); @@ -776,8 +1243,8 @@ function zot_import($arr) { elseif($i['message']['type'] === 'mail') { $arr = get_mail_elements($i['message']); - logger('Mail received: ' . print_r($arr,true)); - logger('Mail recipients: ' . print_r($deliveries,true)); + logger('Mail received: ' . print_r($arr,true), LOGGER_DATA); + logger('Mail recipients: ' . print_r($deliveries,true), LOGGER_DATA); $result = process_mail_delivery($i['notify']['sender'],$arr,$deliveries); @@ -786,15 +1253,26 @@ function zot_import($arr) { elseif($i['message']['type'] === 'profile') { $arr = get_profile_elements($i['message']); - logger('Profile received: ' . print_r($arr,true)); - logger('Profile recipients: ' . print_r($deliveries,true)); + logger('Profile received: ' . print_r($arr,true), LOGGER_DATA); + logger('Profile recipients: ' . print_r($deliveries,true), LOGGER_DATA); $result = process_profile_delivery($i['notify']['sender'],$arr,$deliveries); } + elseif($i['message']['type'] === 'channel_sync') { +// $arr = get_channelsync_elements($i['message']); + + $arr = $i['message']; + + logger('Channel sync received: ' . print_r($arr,true), LOGGER_DATA); + logger('Channel sync recipients: ' . print_r($deliveries,true), LOGGER_DATA); + + $result = process_channel_sync_delivery($i['notify']['sender'],$arr,$deliveries); + } } - if($result) + if($result){ $return = array_merge($return,$result); + } } } @@ -811,19 +1289,26 @@ function zot_import($arr) { // on the site that we should deliver to. - function public_recips($msg) { - logger('public_recips: ' . print_r($msg,true)); - + $check_mentions = false; if($msg['message']['type'] === 'activity') { + $col = 'channel_w_stream'; + $field = PERMS_W_STREAM; if(array_key_exists('flags',$msg['message']) && in_array('thread_parent', $msg['message']['flags'])) { - $col = 'channel_w_stream'; - $field = PERMS_W_STREAM; + // check mention recipient permissions on top level posts only + $check_mentions = true; } else { - $col = 'channel_w_comment'; - $field = PERMS_W_COMMENT; + // if this is a comment and it wasn't sent by the post owner, check to see who is allowing them to comment. + // We should have one specific recipient and this step shouldn't be needed unless somebody stuffed up their software. + // We may need this step to protect us from bad guys intentionally stuffing up their software. + // If it is sent by the post owner, we don't need to do this. We only need to see who is receiving the + // owner's stream (which was already set above) - as they control the comment permissions + if($msg['notify']['sender']['guid_sig'] != $msg['message']['owner']['guid_sig']) { + $col = 'channel_w_comment'; + $field = PERMS_W_COMMENT; + } } } elseif($msg['message']['type'] === 'mail') { @@ -834,18 +1319,21 @@ function public_recips($msg) { if(! $col) return NULL; + if($msg['notify']['sender']['url'] === z_root()) - $sql = " where (( " . $col . " & " . PERMS_NETWORK . " ) or ( " . $col . " & " . PERMS_SITE . " )) "; + $sql = " where (( " . $col . " & " . PERMS_NETWORK . " ) or ( " . $col . " & " . PERMS_SITE . " ) or ( " . $col . " & " . PERMS_PUBLIC . ")) "; else - $sql = " where ( " . $col . " & " . PERMS_NETWORK . " ) " ; + $sql = " where (( " . $col . " & " . PERMS_NETWORK . " ) or ( " . $col . " & " . PERMS_PUBLIC . ")) "; - $r = q("select channel_hash as hash from channel " . $sql ); + + $r = q("select channel_hash as hash from channel $sql or channel_hash = '%s' ", + dbesc($msg['notify']['sender']['hash']) + ); if(! $r) $r = array(); - $x = q("select channel_hash as hash from channel left join abook on abook_channel = channel_id where abook_xchan = '%s' - and ( " . $col . " & " . PERMS_SPECIFIC . " ) and ( abook_my_perms & " . $field . " ) ", + $x = q("select channel_hash as hash from channel left join abook on abook_channel = channel_id where abook_xchan = '%s' and not ( channel_pageflags & " . PAGE_REMOVED . " ) and (( " . $col . " & " . PERMS_SPECIFIC . " ) and ( abook_my_perms & " . $field . " )) OR ( " . $col . " & " . PERMS_PENDING . " ) OR (( " . $col . " & " . PERMS_CONTACTS . " ) and not ( abook_flags & " . ABOOK_FLAG_PENDING . " )) ", dbesc($msg['notify']['sender']['hash']) ); @@ -854,14 +1342,102 @@ function public_recips($msg) { $r = array_merge($r,$x); + // look for any public mentions on this site + // They will get filtered by tgroup_check() so we don't need to check permissions now + + if($check_mentions && $msg['message']['tags']) { + if(is_array($msg['message']['tags']) && $msg['message']['tags']) { + foreach($msg['message']['tags'] as $tag) { + if(($tag['type'] === 'mention') && (strpos($tag['url'],z_root()) !== false)) { + $address = basename($tag['url']); + if($address) { + $z = q("select channel_hash as hash from channel where channel_address = '%s' limit 1", + dbesc($address) + ); + if($z) + $r = array_merge($r,$z); + } + } + } + } + } + logger('public_recips: ' . print_r($r,true), LOGGER_DATA); return $r; } +// This is the second part of the above function. We'll find all the channels willing to accept public posts from us, +// then match them against the sender privacy scope and see who in that list that the sender is allowing. + +function allowed_public_recips($msg) { -function process_delivery($sender,$arr,$deliveries,$relay) { + + logger('allowed_public_recips: ' . print_r($msg,true)); + + $recips = public_recips($msg); + + if(! $recips) + return $recips; + + if($msg['message']['type'] === 'mail') + return $recips; + + if(array_key_exists('public_scope',$msg['message'])) + $scope = $msg['message']['public_scope']; + + $hash = base64url_encode(hash('whirlpool',$msg['notify']['sender']['guid'] . $msg['notify']['sender']['guid_sig'], true)); + + if($scope === 'public' || $scope === 'network: red') + return $recips; + + if(strpos($scope,'site:') === 0) { + if(($scope === 'site: ' . get_app()->get_hostname()) && ($msg['notify']['sender']['url'] === z_root())) + return $recips; + else + return array(); + } + + if($scope === 'self') { + foreach($recips as $r) + if($r['hash'] === $hash) + return array('hash' => $hash); + } + + if($scope === 'contacts') { + $condensed_recips = array(); + foreach($recips as $rr) + $condensed_recips[] = $rr['hash']; + + $results = array(); + $r = q("select channel_hash as hash from channel left join abook on abook_channel = channel_id where abook_xchan = '%s' and not ( channel_pageflags & %d ) ", + dbesc($hash), + intval(PAGE_REMOVED) + ); + if($r) { + foreach($r as $rr) + if(in_array($rr['hash'],$condensed_recips)) + $results[] = array('hash' => $rr['hash']); + } + return $results; + } + + return array(); +} + + +function process_delivery($sender,$arr,$deliveries,$relay,$public = false) { $result = array(); + + + // We've validated the sender. Now make sure that the sender is the owner or author + + if(! $public) { + if($sender['hash'] != $arr['owner_xchan'] && $sender['hash'] != $arr['author_xchan']) { + logger("process_delivery: sender {$sender['hash']} is not owner {$arr['owner_xchan']} or author {$arr['author_xchan']} - mid {$arr['mid']}"); + return; + } + } foreach($deliveries as $d) { $r = q("select * from channel where channel_hash = '%s' limit 1", @@ -869,200 +1445,389 @@ function process_delivery($sender,$arr,$deliveries,$relay) { ); if(! $r) { - $result[] = array($d['hash'],'not found'); + $result[] = array($d['hash'],'recipients not found'); continue; } $channel = $r[0]; - $perm = (($arr['uri'] == $arr['parent_uri']) ? 'send_stream' : 'post_comments'); + $tag_delivery = tgroup_check($channel['channel_id'],$arr); + + $perm = (($arr['mid'] == $arr['parent_mid']) ? 'send_stream' : 'post_comments'); + + // This is our own post, possibly coming from a channel clone + + if($arr['owner_xchan'] == $d['hash']) { + $arr['item_flags'] = $arr['item_flags'] | ITEM_WALL; + } + else { + // clear the wall flag if it is set + if($arr['item_flags'] & ITEM_WALL) { + $arr['item_flags'] = ($arr['item_flags'] ^ ITEM_WALL); + } + } - if(! perm_is_allowed($channel['channel_id'],$sender['hash'],$perm)) { - logger("permission denied for delivery {$channel['channel_id']}"); - $result[] = array($d['hash'],'permission denied'); + if((! perm_is_allowed($channel['channel_id'],$sender['hash'],$perm)) && (! $tag_delivery) && (! $public)) { + logger("permission denied for delivery to channel {$channel['channel_id']} {$channel['channel_address']}"); + $result[] = array($d['hash'],'permission denied',$channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>',$arr['mid']); continue; } - if($arr['item_restrict'] & ITEM_DELETED) { - delete_imported_item($sender,$arr,$channel['channel_id']); - $result[] = array($d['hash'],'deleted'); + + // remove_community_tag is a no-op if this isn't a community tag activity + remove_community_tag($sender,$arr,$channel['channel_id']); + + $item_id = delete_imported_item($sender,$arr,$channel['channel_id']); + $result[] = array($d['hash'],(($item_id) ? 'deleted' : 'delete_failed'),$channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>',$arr['mid']); + + if($relay && $item_id) { + logger('process_delivery: invoking relay'); + proc_run('php','include/notifier.php','relay',intval($item_id)); + $result[] = array($d['hash'],'relayed',$channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>',$arr['mid']); + } + continue; } - $r = q("select id, edited from item where uri = '%s' and uid = %d limit 1", - dbesc($arr['uri']), + $r = q("select id, edited from item where mid = '%s' and uid = %d limit 1", + dbesc($arr['mid']), intval($channel['channel_id']) ); if($r) { - if($arr['edited'] > $r[0]['edited']) + if($arr['edited'] > $r[0]['edited']) { + $arr['id'] = $r[0]['id']; + $arr['uid'] = $channel['channel_id']; update_imported_item($sender,$arr,$channel['channel_id']); - $result[] = array($d['hash'],'updated'); + } + $result[] = array($d['hash'],'updated',$channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>',$arr['mid']); $item_id = $r[0]['id']; } else { $arr['aid'] = $channel['channel_account_id']; $arr['uid'] = $channel['channel_id']; - $item_id = item_store($arr); - $result[] = array($d['hash'],'posted'); + $item_result = item_store($arr); + $item_id = $item_result['item_id']; + $parr = array('item_id' => $item_id,'item' => $arr,'sender' => $sender,'channel' => $channel); + call_hooks('activity_received',$parr); + + add_source_route($item_id,$sender['hash']); + + $result[] = array($d['hash'],(($item_id) ? 'posted' : 'storage failed:' . $item_result['message']),$channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>',$arr['mid']); } if($relay && $item_id) { logger('process_delivery: invoking relay'); proc_run('php','include/notifier.php','relay',intval($item_id)); - $result[] = array($d['hash'],'relayed'); + $result[] = array($d['hash'],'relayed',$channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>',$arr['mid']); } } if(! $deliveries) - $result[] = array('','no recipients'); + $result[] = array('','no recipients','',$arr['mid']); - logger('process_delivery: local results: ' . print_r($result,true)); + logger('process_delivery: local results: ' . print_r($result,true), LOGGER_DEBUG); return $result; } +function remove_community_tag($sender,$arr,$uid) { + + if(! (activity_match($arr['verb'],ACTIVITY_TAG) && ($arr['obj_type'] == ACTIVITY_OBJ_TAGTERM))) + return; + + logger('remove_community_tag: invoked'); + + + if(! get_pconfig($uid,'system','blocktags')) { + logger('remove_community tag: permission denied.'); + return; + } + + $r = q("select * from item where mid = '%s' and uid = %d limit 1", + dbesc($arr['mid']), + intval($uid) + ); + if(! $r) { + logger('remove_community_tag: no item'); + return; + } + + if(($sender['hash'] != $r[0]['owner_xchan']) && ($sender['hash'] != $r[0]['author_xchan'])) { + logger('remove_community_tag: sender not authorised.'); + return; + } + + $i = $r[0]; + + if($i['target']) + $i['target'] = json_decode_plus($i['target']); + if($i['object']) + $i['object'] = json_decode_plus($i['object']); + + if(! ($i['target'] && $i['object'])) { + logger('remove_community_tag: no target/object'); + return; + } + + $message_id = $i['target']['id']; + + $r = q("select id from item where mid = '%s' and uid = %d limit 1", + dbesc($message_id), + intval($uid) + ); + if(! $r) { + logger('remove_community_tag: no parent message'); + return; + } + + $x = q("delete from term where uid = %d and oid = %d and otype = %d and type = %d and term = '%s' and url = '%s' limit 1", + intval($uid), + intval($r[0]['id']), + intval(TERM_OBJ_POST), + intval(TERM_HASHTAG), + dbesc($i['object']['title']), + dbesc(get_rel_link($i['object']['link'],'alternate')) + ); + + return; +} + function update_imported_item($sender,$item,$uid) { -// FIXME - logger('update_imported_item'); + + $x = item_store_update($item); + if(! $x['item_id']) + logger('update_imported_item: failed: ' . $x['message']); + else + logger('update_imported_item'); } function delete_imported_item($sender,$item,$uid) { - $r = q("select id from item where author_xchan = '%s' or owner_xchan = '%s' - and uri = '%s' and uid = %d limit 1", + logger('delete_imported_item invoked',LOGGER_DEBUG); + + $r = q("select id, item_restrict from item where ( author_xchan = '%s' or owner_xchan = '%s' or source_xchan = '%s' ) + and mid = '%s' and uid = %d limit 1", dbesc($sender['hash']), dbesc($sender['hash']), - dbesc($item['uri']), + dbesc($sender['hash']), + dbesc($item['mid']), intval($uid) ); + if(! $r) { logger('delete_imported_item: failed: ownership issue'); - return; + return false; } + + if($r[0]['item_restrict'] & ITEM_DELETED) { + logger('delete_imported_item: item was already deleted'); + return false; + } - $r = q("update item set body = '', title = '', item_restrict = %d, edited = '%s', changed = '%s' - where ( thr_parent = '%s' or parent_uri = '%s' ) and uid = %d", - intval(ITEM_DELETED), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc($item['uri']), - dbesc($item['uri']), - intval($uid) - ); + require_once('include/items.php'); - if(! $r) - logger("delete_imported_item: db update failed. Item = {$item['uri']} uid = $uid"); + // FIXME issue #230 is related + // Chicken/egg problem because we have to drop_item, but this removes information that tag_deliver may need to do its stuff. + // We can't reverse the order because drop_item refuses to run if the item already has the deleted flag set and we need to + // set that flag prior to calling tag_deliver. + + // Use phased deletion to set the deleted flag, call both tag_deliver and the notifier to notify downstream channels + // and then clean up after ourselves with a cron job after several days to do the delete_item_lowlevel() (DROPITEM_PHASE2). + + drop_item($r[0]['id'],false, DROPITEM_PHASE1); + tag_deliver($uid,$r[0]['id']); + + return $r[0]['id']; } function process_mail_delivery($sender,$arr,$deliveries) { + + + $result = array(); + + + if($sender['hash'] != $arr['from_xchan']) { + logger('process_mail_delivery: sender is not mail author'); + return; + } + + foreach($deliveries as $d) { $r = q("select * from channel where channel_hash = '%s' limit 1", dbesc($d['hash']) ); - if(! $r) + if(! $r) { + $result[] = array($d['hash'],'not found'); continue; + } $channel = $r[0]; if(! perm_is_allowed($channel['channel_id'],$sender['hash'],'post_mail')) { logger("permission denied for mail delivery {$channel['channel_id']}"); + $result[] = array($d['hash'],'permission denied',$channel['channel_name'],$arr['mid']); continue; } - $r = q("select id from mail where uri = '%s' and channel_id = %d limit 1", - dbesc($arr['uri']), + $r = q("select id from mail where mid = '%s' and channel_id = %d limit 1", + dbesc($arr['mid']), intval($channel['channel_id']) ); if($r) { - logger('duplicate mail received'); + if($arr['mail_flags'] & MAIL_RECALLED) { + $x = q("delete from mail where id = %d and channel_id = %d limit 1", + intval($r[0]['id']), + intval($channel['channel_id']) + ); + $result[] = array($d['hash'],'mail recalled',$channel['channel_name'],$arr['mid']); + logger('mail_recalled'); + } + else { + $result[] = array($d['hash'],'duplicate mail received',$channel['channel_name'],$arr['mid']); + logger('duplicate mail received'); + } continue; } else { $arr['account_id'] = $channel['channel_account_id']; $arr['channel_id'] = $channel['channel_id']; $item_id = mail_store($arr); + $result[] = array($d['hash'],'mail delivered',$channel['channel_name'],$arr['mid']); + } } + return $result; } function process_profile_delivery($sender,$arr,$deliveries) { // deliveries is irrelevant, what to do about birthday notification....? - logger('process_profile_delivery'); - import_directory_profile($sender['hash'],$arr); + + logger('process_profile_delivery', LOGGER_DEBUG); + + $r = q("select xchan_addr from xchan where xchan_hash = '%s' limit 1", + dbesc($sender['hash']) + ); + if($r) + import_directory_profile($sender['hash'],$arr,$r[0]['xchan_addr'], UPDATE_FLAGS_UPDATED, 0); } -function import_directory_profile($hash,$profile) { - logger('import_directory_profile'); +/* + * @function import_directory_profile + * + * @returns boolean $updated if something changed + * + */ + +function import_directory_profile($hash,$profile,$addr,$ud_flags = UPDATE_FLAGS_UPDATED, $suppress_update = 0) { + + logger('import_directory_profile', LOGGER_DEBUG); if(! $hash) - return; + return false; $arr = array(); $arr['xprof_hash'] = $hash; - $arr['xprof_desc'] = (($profile['description']) ? htmlentities($profile['description'], ENT_COMPAT,'UTF-8') : ''); $arr['xprof_dob'] = datetime_convert('','',$profile['birthday'],'Y-m-d'); // !!!! check this for 0000 year - $arr['xprof_gender'] = (($profile['gender']) ? htmlentities($profile['gender'], ENT_COMPAT,'UTF-8') : ''); - $arr['xprof_marital'] = (($profile['marital']) ? htmlentities($profile['marital'], ENT_COMPAT,'UTF-8') : ''); - $arr['xprof_sexual'] = (($profile['sexual']) ? htmlentities($profile['sexual'], ENT_COMPAT,'UTF-8') : ''); - $arr['xprof_locale'] = (($profile['locale']) ? htmlentities($profile['locale'], ENT_COMPAT,'UTF-8') : ''); - $arr['xprof_region'] = (($profile['region']) ? htmlentities($profile['region'], ENT_COMPAT,'UTF-8') : ''); - $arr['xprof_postcode'] = (($profile['postcode']) ? htmlentities($profile['postcode'], ENT_COMPAT,'UTF-8') : ''); - $arr['xprof_country'] = (($profile['country']) ? htmlentities($profile['country'], ENT_COMPAT,'UTF-8') : ''); + $arr['xprof_age'] = (($profile['age']) ? intval($profile['age']) : 0); + $arr['xprof_desc'] = (($profile['description']) ? htmlspecialchars($profile['description'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_gender'] = (($profile['gender']) ? htmlspecialchars($profile['gender'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_marital'] = (($profile['marital']) ? htmlspecialchars($profile['marital'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_sexual'] = (($profile['sexual']) ? htmlspecialchars($profile['sexual'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_locale'] = (($profile['locale']) ? htmlspecialchars($profile['locale'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_region'] = (($profile['region']) ? htmlspecialchars($profile['region'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_postcode'] = (($profile['postcode']) ? htmlspecialchars($profile['postcode'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_country'] = (($profile['country']) ? htmlspecialchars($profile['country'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_about'] = (($profile['about']) ? htmlspecialchars($profile['about'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_homepage'] = (($profile['homepage']) ? htmlspecialchars($profile['homepage'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_hometown'] = (($profile['hometown']) ? htmlspecialchars($profile['hometown'], ENT_COMPAT,'UTF-8',false) : ''); $clean = array(); if(array_key_exists('keywords',$profile) and is_array($profile['keywords'])) { import_directory_keywords($hash,$profile['keywords']); foreach($profile['keywords'] as $kw) { - $kw = trim(htmlentities($kw,ENT_COMPAT,'UTF-8')); + $kw = trim(htmlspecialchars($kw,ENT_COMPAT,'UTF-8',false)); + $kw = trim($kw,','); + $clean[] = $kw; } - $clean[] = $kw; } $arr['xprof_keywords'] = implode(' ',$clean); + // Self censored, make it so + // These are not translated, so the German "erwachsenen" keyword will not censor the directory profile. Only the English form - "adult". + + + if(in_arrayi('nsfw',$clean) || in_arrayi('adult',$clean)) { + q("update xchan set xchan_flags = (xchan_flags | %d) where xchan_hash = '%s' limit 1", + intval(XCHAN_FLAGS_SELFCENSORED), + dbesc($hash) + ); + } + $r = q("select * from xprof where xprof_hash = '%s' limit 1", dbesc($hash) ); if($r) { - $x = q("update xprof set - xprof_desc = '%s', - xprof_dob = '%s', - xprof_gender = '%s', - xprof_marital = '%s', - xprof_sexual = '%s', - xprof_locale = '%s', - xprof_region = '%s', - xprof_postcode = '%s', - xprof_country = '%s', - xprof_keywords = '%s' - where xprof_hash = '%s' limit 1", - dbesc($arr['xprof_desc']), - dbesc($arr['xprof_dob']), - dbesc($arr['xprof_gender']), - dbesc($arr['xprof_marital']), - dbesc($arr['xprof_sexual']), - dbesc($arr['xprof_locale']), - dbesc($arr['xprof_region']), - dbesc($arr['xprof_postcode']), - dbesc($arr['xprof_country']), - dbesc($arr['xprof_keywords']), - dbesc($arr['xprof_hash']) - ); + $update = false; + foreach($r[0] as $k => $v) { + if((array_key_exists($k,$arr)) && ($arr[$k] != $v)) { + logger('import_directory_profile: update' . $k . ' => ' . $arr[$k]); + $update = true; + break; + } + } + if($update) { + $x = q("update xprof set + xprof_desc = '%s', + xprof_dob = '%s', + xprof_age = %d, + xprof_gender = '%s', + xprof_marital = '%s', + xprof_sexual = '%s', + xprof_locale = '%s', + xprof_region = '%s', + xprof_postcode = '%s', + xprof_country = '%s', + xprof_about = '%s', + xprof_homepage = '%s', + xprof_hometown = '%s', + xprof_keywords = '%s' + where xprof_hash = '%s' limit 1", + dbesc($arr['xprof_desc']), + dbesc($arr['xprof_dob']), + intval($arr['xprof_age']), + dbesc($arr['xprof_gender']), + dbesc($arr['xprof_marital']), + dbesc($arr['xprof_sexual']), + dbesc($arr['xprof_locale']), + dbesc($arr['xprof_region']), + dbesc($arr['xprof_postcode']), + dbesc($arr['xprof_country']), + dbesc($arr['xprof_about']), + dbesc($arr['xprof_homepage']), + dbesc($arr['xprof_hometown']), + dbesc($arr['xprof_keywords']), + dbesc($arr['xprof_hash']) + ); + } } else { - $x = q("insert into xprof (xprof_hash, xprof_desc, xprof_dob, xprof_gender, xprof_marital, xprof_sexual, xprof_locale, xprof_region, xprof_postcode, xprof_country, xprof_keywords) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ", + $update = true; + logger('import_directory_profile: new profile'); + $x = q("insert into xprof (xprof_hash, xprof_desc, xprof_dob, xprof_age, xprof_gender, xprof_marital, xprof_sexual, xprof_locale, xprof_region, xprof_postcode, xprof_country, xprof_about, xprof_homepage, xprof_hometown, xprof_keywords) values ('%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ", dbesc($arr['xprof_hash']), dbesc($arr['xprof_desc']), dbesc($arr['xprof_dob']), + intval($arr['xprof_age']), dbesc($arr['xprof_gender']), dbesc($arr['xprof_marital']), dbesc($arr['xprof_sexual']), @@ -1070,11 +1835,19 @@ function import_directory_profile($hash,$profile) { dbesc($arr['xprof_region']), dbesc($arr['xprof_postcode']), dbesc($arr['xprof_country']), + dbesc($arr['xprof_about']), + dbesc($arr['xprof_homepage']), + dbesc($arr['xprof_hometown']), dbesc($arr['xprof_keywords']) ); } - return; + $d = array('xprof' => $arr, 'profile' => $profile, 'update' => $update); + call_hooks('import_directory_profile', $d); + + if(($d['update']) && (! $suppress_update)) + update_modtime($arr['xprof_hash'],random_string() . '@' . get_app()->get_hostname(), $addr, $ud_flags); + return $d['update']; } function import_directory_keywords($hash,$keywords) { @@ -1091,7 +1864,8 @@ function import_directory_keywords($hash,$keywords) { $clean = array(); foreach($keywords as $kw) { - $kw = trim(htmlentities($kw,ENT_COMPAT,'UTF-8')); + $kw = trim(htmlspecialchars($kw,ENT_COMPAT,'UTF-8',false)); + $kw = trim($kw,','); $clean[] = $kw; } @@ -1104,9 +1878,462 @@ function import_directory_keywords($hash,$keywords) { } foreach($clean as $x) { if(! in_array($x,$existing)) - $r = q("insert int xtag ( xtag_hash, xtag_term) values ( '%s' ,'%s' )", + $r = q("insert into xtag ( xtag_hash, xtag_term) values ( '%s' ,'%s' )", dbesc($hash), dbesc($x) ); } -}
\ No newline at end of file +} + + +function update_modtime($hash,$guid,$addr,$flags = 0) { + + $dirmode = intval(get_config('system','directory_mode')); + + if($dirmode == DIRECTORY_MODE_NORMAL) + return; + + if($flags) { + q("insert into updates (ud_hash, ud_guid, ud_date, ud_flags, ud_addr ) values ( '%s', '%s', '%s', %d, '%s' )", + dbesc($hash), + dbesc($guid), + dbesc(datetime_convert()), + intval($flags), + dbesc($addr) + ); + } + else { + q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not (ud_flags & %d) ", + intval(UPDATE_FLAGS_UPDATED), + dbesc($addr), + intval(UPDATE_FLAGS_UPDATED) + ); + } +} + + +function import_site($arr,$pubkey) { + if( (! is_array($arr)) || (! $arr['url']) || (! $arr['url_sig'])) + return false; + + if(! rsa_verify($arr['url'],base64url_decode($arr['url_sig']),$pubkey)) { + logger('import_site: bad url_sig'); + return false; + } + + $update = false; + $exists = false; + + $r = q("select * from site where site_url = '%s' limit 1", + dbesc($arr['url']) + ); + if($r) { + $exists = true; + $siterecord = $r[0]; + } + + $site_directory = 0; + if($arr['directory_mode'] == 'normal') + $site_directory = DIRECTORY_MODE_NORMAL; + + if($arr['directory_mode'] == 'primary') + $site_directory = DIRECTORY_MODE_PRIMARY; + if($arr['directory_mode'] == 'secondary') + $site_directory = DIRECTORY_MODE_SECONDARY; + if($arr['directory_mode'] == 'standalone') + $site_directory = DIRECTORY_MODE_STANDALONE; + + $register_policy = 0; + if($arr['register_policy'] == 'closed') + $register_policy = REGISTER_CLOSED; + if($arr['register_policy'] == 'open') + $register_policy = REGISTER_OPEN; + if($arr['register_policy'] == 'approve') + $register_policy = REGISTER_APPROVE; + + $access_policy = 0; + if(array_key_exists('access_policy',$arr)) { + if($arr['access_policy'] === 'private') + $access_policy = ACCESS_PRIVATE; + if($arr['access_policy'] === 'paid') + $access_policy = ACCESS_PAID; + if($arr['access_policy'] === 'free') + $access_policy = ACCESS_FREE; + if($arr['access_policy'] === 'tiered') + $access_policy = ACCESS_TIERED; + } + + // don't let insecure sites register as public hubs + + if(strpos($arr['url'],'https://') === false) + $access_policy = ACCESS_PRIVATE; + + if($access_policy != ACCESS_PRIVATE) { + $x = z_fetch_url($arr['url'] . '/siteinfo/json'); + if(! $x['success']) + $access_policy = ACCESS_PRIVATE; + } + + $directory_url = htmlspecialchars($arr['directory_url'],ENT_COMPAT,'UTF-8',false); + $url = htmlspecialchars($arr['url'],ENT_COMPAT,'UTF-8',false); + $sellpage = htmlspecialchars($arr['sellpage'],ENT_COMPAT,'UTF-8',false); + $site_location = htmlspecialchars($arr['location'],ENT_COMPAT,'UTF-8',false); + + if($exists) { + if(($siterecord['site_flags'] != $site_directory) + || ($siterecord['site_access'] != $access_policy) + || ($siterecord['site_directory'] != $directory_url) + || ($siterecord['site_sellpage'] != $sellpage) + || ($siterecord['site_location'] != $site_location) + || ($siterecord['site_register'] != $register_policy)) { + $update = true; + +// logger('import_site: input: ' . print_r($arr,true)); +// logger('import_site: stored: ' . print_r($siterecord,true)); + + $r = q("update site set site_location = '%s', site_flags = %d, site_access = %d, site_directory = '%s', site_register = %d, site_update = '%s', site_sellpage = '%s' + where site_url = '%s' limit 1", + dbesc($site_location), + intval($site_directory), + intval($access_policy), + dbesc($directory_url), + intval($register_policy), + dbesc(datetime_convert()), + dbesc($sellpage), + dbesc($url) + ); + if(! $r) { + logger('import_site: update failed. ' . print_r($arr,true)); + } + } + } + else { + $update = true; + $r = q("insert into site ( site_location, site_url, site_access, site_flags, site_update, site_directory, site_register, site_sellpage ) + values ( '%s', '%s', %d, %d, '%s', '%s', %d, '%s' )", + dbesc($site_location), + dbesc($url), + intval($access_policy), + intval($site_directory), + dbesc(datetime_convert()), + dbesc($directory_url), + intval($register_policy), + dbesc($sellpage) + ); + if(! $r) { + logger('import_site: record create failed. ' . print_r($arr,true)); + } + } + + return $update; + +} + + + +/** + * Send a zot packet to all hubs where this channel is duplicated, refreshing + * such things as personal settings, channel permissions, address book updates, etc. + */ + +function build_sync_packet($uid = 0, $packet = null) { + + $a = get_app(); + + logger('build_sync_packet'); + + if(! $uid) + $uid = local_user(); + + if(! $uid) + return; + + $r = q("select * from channel where channel_id = %d limit 1", + intval($uid) + ); + if(! $r) + return; + + $channel = $r[0]; + + $h = q("select * from hubloc where hubloc_hash = '%s'", + dbesc($channel['channel_hash']) + ); + + if(! $h) + return; + + $synchubs = array(); + + foreach($h as $x) { + if($x['hubloc_host'] == $a->get_hostname()) + continue; + $synchubs[] = $x; + } + + if(! $synchubs) + return; + + $r = q("select xchan_guid, xchan_guid_sig from xchan where xchan_hash = '%s' limit 1", + dbesc($channel['channel_hash']) + ); + if(! $r) + return; + + $env_recips = array(); + $env_recips[] = array('guid' => $r[0]['xchan_guid'],'guid_sig' => $r[0]['xchan_guid_sig']); + + $info = (($packet) ? $packet : array()); + $info['type'] = 'channel_sync'; + + if(array_key_exists($uid,$a->config) && array_key_exists('transient',$a->config[$uid])) { + $settings = $a->config[$uid]['transient']; + if($settings) { + $info['config'] = $settings; + } + } + + if($channel) { + $info['channel'] = array(); + foreach($channel as $k => $v) { + + // filter out any joined tables like xchan + + if(strpos($k,'channel_') !== 0) + continue; + + // don't pass these elements, they should not be synchronised + + $disallowed = array('channel_id','channel_account_id','channel_primary','channel_prvkey','channel_address'); + + if(in_array($k,$disallowed)) + continue; + + $info['channel'][$k] = $v; + } + } + + $interval = ((get_config('system','delivery_interval') !== false) + ? intval(get_config('system','delivery_interval')) : 2 ); + + + logger('build_sync_packet: packet: ' . print_r($info,true), LOGGER_DATA); + + foreach($synchubs as $hub) { + $hash = random_string(); + $n = zot_build_packet($channel,'notify',$env_recips,$hub['hubloc_sitekey'],$hash); + q("insert into outq ( outq_hash, outq_account, outq_channel, outq_driver, outq_posturl, outq_async, outq_created, outq_updated, outq_notify, outq_msg ) values ( '%s', %d, %d, '%s', '%s', %d, '%s', '%s', '%s', '%s' )", + dbesc($hash), + intval($channel['channel_account']), + intval($channel['channel_id']), + dbesc('zot'), + dbesc($hub['hubloc_callback']), + intval(1), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc($n), + dbesc(json_encode($info)) + ); + + proc_run('php','include/deliver.php',$hash); + if($interval) + @time_sleep_until(microtime(true) + (float) $interval); + } + + +} + +function process_channel_sync_delivery($sender,$arr,$deliveries) { + +// FIXME - this will sync red structures (channel, pconfig and abook). Eventually we need to make this application agnostic. +// TODO: missing group membership changes + + $result = array(); + + foreach($deliveries as $d) { + $r = q("select * from channel where channel_hash = '%s' limit 1", + dbesc($d['hash']) + ); + + if(! $r) { + $result[] = array($d['hash'],'not found'); + continue; + } + + $channel = $r[0]; + + if($channel['channel_hash'] != $sender['hash']) { + logger('process_channel_sync_delivery: possible forgery. Sender ' . $sender['hash'] . ' is not ' . $channel['channel_hash']); + $result[] = array($d['hash'],'channel mismatch',$channel['channel_name'],''); + continue; + } + + if(array_key_exists('config',$arr) && is_array($arr['config']) && count($arr['config'])) { + foreach($arr['config'] as $cat => $k) { + foreach($arr['config'][$cat] as $k => $v) + set_pconfig($channel['channel_id'],$cat,$k,$v); + } + } + + if(array_key_exists('channel',$arr) && is_array($arr['channel']) && count($arr['channel'])) { + $disallowed = array('channel_id','channel_account_id','channel_primary','channel_prvkey', 'channel_address', 'channel_notifyflags'); + + $clean = array(); + foreach($arr['channel'] as $k => $v) { + if(in_array($k,$disallowed)) + continue; + $clean[$k] = $v; + } + if(count($clean)) { + foreach($clean as $k => $v) { + $r = dbq("UPDATE channel set " . dbesc($k) . " = '" . dbesc($v) + . "' where channel_id = " . intval($channel['channel_id']) . " limit 1"); + } + } + } + + + if(array_key_exists('abook',$arr) && is_array($arr['abook']) && count($arr['abook'])) { + + $disallowed = array('abook_id','abook_account','abook_channel'); + + $clean = array(); + foreach($arr['abook'] as $abook) { + + // Perform discovery if the referenced xchan hasn't ever been seen on this hub. + // This relies on the undocumented behaviour that red sites send xchan info with the abook + + if($abook['abook_xchan'] && $abook['xchan_address']) { + $h = zot_get_hublocs($abook['abook_xchan']); + if(! $h) { + $f = zot_finger($abook['xchan_address'],$channel); + if(! $f['success']) { + logger('process_channel_sync_delivery: abook not probe-able' . $abook['xchan_address']); + continue; + } + $j = json_decode($f['body'],true); + if(! ($j['success'] && $j['guid'])) { + logger('process_channel_sync_delivery: probe failed.'); + continue; + } + + $x = import_xchan($j); + + if(! $x['success']) { + logger('process_channel_sync_delivery: import failed.'); + continue; + } + } + } + + foreach($abook as $k => $v) { + if(in_array($k,$disallowed) || (strpos($k,'abook') !== 0)) + continue; + $clean[$k] = $v; + } + + if(! array_key_exists('abook_xchan',$clean)) + continue; + + $r = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", + dbesc($clean['abook_xchan']), + intval($channel['channel_id']) + ); + + // make sure we have an abook entry for this xchan on this system + + if(! $r) { + q("insert into abook ( abook_xchan, abook_channel ) values ('%s', %d ) ", + dbesc($clean['abook_xchan']), + intval($channel['channel_id']) + ); + } + + if(count($clean)) { + foreach($clean as $k => $v) { + $r = dbq("UPDATE abook set " . dbesc($k) . " = '" . dbesc($v) + . "' where abook_xchan = '" . dbesc($clean['abook_xchan']) . "' and abook_channel = " . intval($channel['channel_id']) + . " limit 1"); + } + } + } + } + + if(array_key_exists('profile',$arr) && is_array($arr['profile']) && count($arr['profile'])) { + + $disallowed = array('id','aid','uid'); + + foreach($arr['profile'] as $profile) { + $x = q("select * from profile where profile_guid = '%s' and uid = %d limit 1", + dbesc($profile['profile_guid']), + intval($channel['channel_id']) + ); + if(! $x) { + q("insert into profile ( profile_guid, aid, uid ) values ('%s', %d, %d)", + dbesc($profile['profile_guid']), + intval($channel['channel_account_id']), + intval($channel['channel_id']) + ); + $x = q("select * from profile where profile_guid = '%s' and uid = %d limit 1", + dbesc($profile['profile_guid']), + intval($channel['channel_id']) + ); + if(! $x) + continue; + } + $clean = array(); + foreach($profile as $k => $v) { + if(in_array($k,$disallowed)) + continue; + $clean[$k] = $v; + // TODO - check if these are allowed, otherwise we'll error + // We also need to import local photos if a custom photo is selected + } + if(count($clean)) { + foreach($clean as $k => $v) { + $r = dbq("UPDATE profile set " . dbesc($k) . " = '" . dbesc($v) + . "' where profile_guid = '" . dbesc($profile['profile_guid']) . "' and uid = " . intval($channel['channel_id']) + . " limit 1"); + } + } + } + } + + $result[] = array($d['hash'],'channel sync updated',$channel['channel_name'],''); + + + } + return $result; +} + +// We probably should make rpost discoverable. + +function get_rpost_path($observer) { + if(! $observer) + return ''; + $parsed = parse_url($observer['xchan_url']); + return $parsed['scheme'] . '://' . $parsed['host'] . (($parsed['port']) ? ':' . $parsed['port'] : '') . '/rpost?f='; + +} + +function import_author_zot($x) { + $hash = base64url_encode(hash('whirlpool',$x['guid'] . $x['guid_sig'], true)); + $r = q("select hubloc_url from hubloc where hubloc_guid = '%s' and hubloc_guid_sig = '%s' and (hubloc_flags & %d) limit 1", + dbesc($x['guid']), + dbesc($x['guid_sig']), + intval(HUBLOC_FLAGS_PRIMARY) + ); + + if($r) { + logger('import_author_zot: in cache', LOGGER_DEBUG); + return $hash; + } + + logger('import_author_zot: entry not in cache - probing: ' . print_r($x,true), LOGGER_DEBUG); + + $them = array('hubloc_url' => $x['url'],'xchan_guid' => $x['guid'], 'xchan_guid_sig' => $x['guid_sig']); + if(zot_refresh($them)) + return $hash; + return false; +} + |