diff options
Diffstat (limited to 'include')
38 files changed, 1732 insertions, 551 deletions
diff --git a/include/Contact.php b/include/Contact.php index bf536ccd5..46c84aab6 100644 --- a/include/Contact.php +++ b/include/Contact.php @@ -15,13 +15,20 @@ function rconnect_url($channel_id,$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']; + return $r[0]['hubloc_url'] . '/follow?f=&url=%s'; return ''; } @@ -140,7 +147,9 @@ function user_remove($uid) { } -function account_remove($account_id) { +function account_remove($account_id,$local = true) { + + logger('account_remove: ' . $account_id); if(! intval($account_id)) { logger('account_remove: no account.'); @@ -172,7 +181,7 @@ function account_remove($account_id) { ); if($x) { foreach($x as $xx) { - channel_remove($xx['channel_id']); + channel_remove($xx['channel_id'],$local); } } @@ -184,7 +193,7 @@ function account_remove($account_id) { } -function channel_remove($channel_id) { +function channel_remove($channel_id, $local = true) { if(! $channel_id) return; @@ -192,13 +201,39 @@ function channel_remove($channel_id) { logger('Removing channel: ' . $channel_id); $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; + } + + $channel = $r[0]; call_hooks('channel_remove',$r[0]); + if(! $local) { + // FIXME notify the directory // FIXME notify all contacts + $r = q("update channel set 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", + intval(PAGE_REMOVED), + intval($channel_id) + ); + + $r = q("update hubloc set hubloc_flags = hubloc_flags | %d where hubloc_hash = '%s'", + intval(HUBLOC_FLAGS_DELETED), + dbesc($channel['channel_hash']) + ); + + proc_run('php','include/notifier.php','purge_all',$channel_id); + + + } q("DELETE FROM `group` WHERE `uid` = %d", intval($channel_id)); q("DELETE FROM `group_member` WHERE `uid` = %d", intval($channel_id)); @@ -271,18 +306,20 @@ function remove_all_xchan_resources($xchan, $channel_id = 0) { ); - $r = q("delete from xchan where xchan_hash = '%s' limit 1", - dbesc($xchan) - ); - $r = q("delete from hubloc where hubloc_hash = '%s'", - dbesc($xchan) - ); - $r = q("delete from abook where abook_xchan = '%s'", - dbesc($xchan) - ); - $r = q("delete from xtag where xtag_hash = '%s'", - dbesc($xchan) - ); +// This could get sticky with directories + +// $r = q("delete from xchan where xchan_hash = '%s' limit 1", +// dbesc($xchan) +// ); +// $r = q("delete from hubloc where hubloc_hash = '%s'", +// dbesc($xchan) +// ); +// $r = q("delete from abook where abook_xchan = '%s'", +// dbesc($xchan) +// ); +// $r = q("delete from xtag where xtag_hash = '%s'", +// dbesc($xchan) +// ); } } diff --git a/include/ConversationObject.php b/include/ConversationObject.php index 30026e908..bb144d893 100644 --- a/include/ConversationObject.php +++ b/include/ConversationObject.php @@ -7,6 +7,7 @@ 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 @@ -138,22 +139,28 @@ class Conversation extends BaseObject { return false; } - if(local_user() && $item->get_data_value('uid') == local_user()) - $this->commentable = true; +// if(local_user() && $item->get_data_value('uid') == local_user()) +// $this->commentable = true; - if($this->writable) - $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) { - $this->commentable = false; + $item->set_commentable(false); } - elseif(($this->observer) && (! $this->writable)) { - $this->commentable = can_comment_on_post($this->observer['xchan_hash'],$item->data); + 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)); } - - - $item->set_conversation($this); $this->threads[] = $item; return end($this->threads); diff --git a/include/ItemObject.php b/include/ItemObject.php index 8c8c0ee2a..df9386232 100644 --- a/include/ItemObject.php +++ b/include/ItemObject.php @@ -14,6 +14,7 @@ class Item extends BaseObject { public $data = array(); private $template = 'conv_item.tpl'; private $comment_box_template = 'comment_item.tpl'; + private $commentable = false; private $toplevel = false; private $children = array(); private $parent = null; @@ -39,10 +40,10 @@ class Item extends BaseObject { foreach($data['children'] as $item) { /* - * Only add thos that 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; } @@ -74,7 +75,7 @@ class Item extends BaseObject { $buttons = ''; $dropping = false; $star = false; - $isstarred = "unstarred"; + $isstarred = "unstarred icon-star-empty"; $indent = ''; $osparkle = ''; $total_children = $this->count_descendants(); @@ -88,6 +89,8 @@ class Item extends BaseObject { : false); $shareable = ((($conv->get_profile_owner() == local_user()) && ($item['item_private'] != 1)) ? true : false); + $mode = $conv->get_mode(); + if(local_user() && $observer['xchan_hash'] === $item['author_xchan']) $edpost = array($a->get_baseurl($ssl_state)."/editpost/".$item['id'], t("Edit")); else @@ -144,7 +147,7 @@ class Item extends BaseObject { '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'), ); @@ -153,6 +156,12 @@ class Item extends BaseObject { $indent = 'comment'; } + + $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( @@ -161,7 +170,7 @@ class Item extends BaseObject { ); } - if($conv->is_commentable()) { + 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) @@ -183,7 +192,7 @@ class Item extends BaseObject { $tmp_item = array( 'template' => $this->get_template(), - + 'mode' => $mode, 'type' => implode("",array_slice(explode("/",$item['verb']),-1)), 'tags' => array(), 'body' => $body, @@ -207,6 +216,8 @@ class Item extends BaseObject { 'isotime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'c'), 'localtime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'r'), 'lock' => $lock, + 'verified' => $verified, + 'unverified' => $unverified, 'location' => $location, 'indent' => $indent, 'owner_url' => $this->get_owner_url(), @@ -293,6 +304,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 */ @@ -478,7 +499,9 @@ class Item extends BaseObject { $comment_box = ''; $conv = $this->get_conversation(); - if(! $conv->is_commentable()) +// logger('Commentable conv: ' . $conv->is_commentable()); + + if(! $this->is_commentable()) return; $template = get_markup_template($this->get_comment_box_template()); diff --git a/include/activities.php b/include/activities.php index 10a01792f..73180eae0 100644 --- a/include/activities.php +++ b/include/activities.php @@ -45,6 +45,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 +75,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 a49258d18..2760914e9 100644 --- a/include/api.php +++ b/include/api.php @@ -730,9 +730,10 @@ require_once('include/photos.php'); $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, @@ -803,8 +804,9 @@ require_once('include/photos.php'); $in_reply_to_screen_name = $user_info['screen_name']; } } + unobscure($lastwall); $user_info['status'] = 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, @@ -1369,19 +1371,19 @@ require_once('include/photos.php'); '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']); @@ -1425,9 +1427,9 @@ require_once('include/photos.php'); $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)) @@ -1448,7 +1450,7 @@ require_once('include/photos.php'); 'geo' => '', 'favorited' => (($item['item_flags'] & ITEM_STARRED) ? true : false), 'user' => $status_user , - 'statusnet_html' => trim(bbcode($item['body'])), + 'statusnet_html' => trim(prepare_text($item['body']),$item['mimetype']), 'statusnet_conversation_id' => $item['parent'], ); @@ -1823,9 +1825,13 @@ require_once('include/photos.php'); 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(); @@ -1833,7 +1839,8 @@ require_once('include/photos.php'); 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/attach.php b/include/attach.php index 46d406f4b..da08154c6 100644 --- a/include/attach.php +++ b/include/attach.php @@ -339,7 +339,7 @@ function attach_store($channel,$observer_hash,$options = '',$arr = null) { intval($channel_id) ); if(($r) && (($r[0]['total'] + $filesize) > ($limit - $existing_size))) { - $ret['message'] = upgrade_message(true); + $ret['message'] = upgrade_message(true).sprintf(t("You have reached your limit of %1$.0f Mbytes attachment storage."),$limit / 1024000); @unlink($src); return $ret; } diff --git a/include/auth.php b/include/auth.php index 143a16de8..d04ebbe43 100644 --- a/include/auth.php +++ b/include/auth.php @@ -15,6 +15,7 @@ function nuke_session() { unset($_SESSION['cid']); unset($_SESSION['theme']); unset($_SESSION['mobile_theme']); + unset($_SESSION['show_mobile']); unset($_SESSION['page_flags']); unset($_SESSION['submanage']); unset($_SESSION['my_url']); diff --git a/include/bbcode.php b/include/bbcode.php index a0a53a310..756d73aba 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -106,52 +106,32 @@ function bb_ShareAttributes($match) { $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]; + $author = urldecode($matches[1]); $link = ""; preg_match("/link='(.*?)'/ism", $attributes, $matches); if ($matches[1] != "") $link = $matches[1]; - 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]; - 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]; - 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]; - preg_match('/posted="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $posted = $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_header">'; + $headline = '<div class="shared_container"> <div class="shared_header">'; if ($avatar != "") $headline .= '<img src="' . $avatar . '" alt="' . $author . '" height="32" width="32" />'; @@ -166,7 +146,7 @@ function bb_ShareAttributes($match) { $headline .= '<span>' . $fmt . '</span></div>'; - $text = $headline . '<div class="reshared-content">' . trim($match[2]) . '</div>'; + $text = $headline . '<div class="reshared-content">' . trim($match[2]) . '</div></div>'; return($text); } @@ -237,6 +217,18 @@ 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) { + if ($observer) { + $Text = preg_replace("/\[observer\=1\](.*?)\[\/observer\]/ism", '$1', $Text); + $Text = preg_replace("/\[observer\=0\].*?\[\/observer\]/ism", '', $Text); + } else { + $Text = preg_replace("/\[observer\=1\].*?\[\/observer\]/ism", '', $Text); + $Text = preg_replace("/\[observer\=0\](.*?)\[\/observer\]/ism", '$1', $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 @@ -259,12 +251,29 @@ 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 diff --git a/include/comanche.php b/include/comanche.php new file mode 100644 index 000000000..f1dd0e521 --- /dev/null +++ b/include/comanche.php @@ -0,0 +1,170 @@ +<?php /** @file */ + +require_once('include/security.php'); +require_once('include/menu.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("/\[layout\](.*?)\[\/layout\]/ism", $s, $matches); + if($cnt) + $a->page['template'] = trim($matches[1]); + + $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) { + $a = get_app(); + $m = menu_fetch($name,$a->profile['profile_uid'],get_observer_hash()); + return menu_render($m); +} + +function comanche_replace_region($match) { + $a = get_app(); + if(array_key_exists($match[1],$a->page)) { + return $a->page[$match[1]]; + } +} + +function comanche_block($name) { + + $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(get_app()->profile['profile_uid']), + 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,$args = null) { + $a = get_app(); + $func = 'widget_' . trim($name); + if(function_exists($func)) + return $func($args); +} + + +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); + } + } + $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])),$s); + } + } + + return $s; +} + + +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); +} diff --git a/include/config.php b/include/config.php index 38840f5e4..bccf0737f 100644 --- a/include/config.php +++ b/include/config.php @@ -24,6 +24,15 @@ function load_config($family) { if(! array_key_exists('config_loaded',$a->config[$family])) { $r = q("SELECT * FROM config WHERE cat = '%s'", dbesc($family)); + + // This is often one of the earliest database calls in the life of the page. + // If the DB was successfully opened, but we can't read from it, + // we must assume catastrophic failure of the DB. Report the system down. + +// if($r === false) { +// system_unavailable(); +// } + if($r !== false) { if($r) { foreach($r as $rr) { diff --git a/include/contact_widgets.php b/include/contact_widgets.php index ca212796f..af05f8c9f 100644 --- a/include/contact_widgets.php +++ b/include/contact_widgets.php @@ -1,12 +1,27 @@ <?php /** @file */ function follow_widget() { - + $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') + '$follow' => t('Connect'), + '$abook_usage_message' => $abook_usage_message )); } @@ -32,7 +47,7 @@ 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') )); @@ -70,15 +85,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) diff --git a/include/conversation.php b/include/conversation.php index aade06b8a..1820a8568 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -92,8 +92,14 @@ function item_redir_and_replace_images($body, $images, $cid) { function localize_item(&$item){ if (activity_match($item['verb'],ACTIVITY_LIKE) || activity_match($item['verb'],ACTIVITY_DISLIKE)){ - + + if(! $item['object']) + 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'); @@ -162,6 +168,9 @@ function localize_item(&$item){ $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); + } } @@ -391,6 +400,9 @@ function visible_activity($item) { function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { $tstart = dba_timer(); + $t0 = $t1 = $t2 = $t3 = $t4 = $t5 = $t6 = null; + $content_html = ''; + $o = ''; require_once('bbcode.php'); @@ -488,10 +500,16 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { } - 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); @@ -614,12 +632,17 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { ); $star = false; - $isstarred = "unstarred"; + $isstarred = "unstarred icon-star-empty"; $lock = 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)) @@ -633,6 +656,7 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { $tmp_item = array( 'template' => $tpl, '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, @@ -646,6 +670,8 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { 'tags' => $tags, 'hashtags' => $hashtags, 'mentions' => $mentions, + 'verified' => $verified, + 'unverified' => $unverified, 'txt_cats' => t('Categories:'), 'txt_folders' => t('Filed under:'), 'has_cats' => ((count($categories)) ? 'true' : ''), @@ -712,6 +738,8 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { $threads = array(); foreach($items as $item) { + // Check for any blocked authors + if($arr_blocked) { $blocked = false; foreach($arr_blocked as $b) { @@ -724,6 +752,18 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { 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; + } + } + } + + // Can we put this after the visibility check? like_puller($a,$item,$alike,'like'); @@ -781,8 +821,9 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional') { // logger('nouveau: ' . print_r($threads,true)); - $o = replace_macros($page_template, array( + $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, @@ -843,6 +884,12 @@ function item_photo_menu($item){ $ssl_state = false; + $sub_link=""; + $poke_link=""; + $contact_url=""; + $pm_url=""; + $vsrc_link = ""; + if(local_user()) { $ssl_state = true; if(! count($a->contacts)) @@ -851,14 +898,11 @@ 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 = $a->get_baseurl() . '/viewsrc/' . $item['id']; + 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']; @@ -877,11 +921,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 @@ -976,9 +1021,34 @@ function status_editor($a,$x,$popup=false) { $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'] . '" />'; + } + + + + $webpage = ((x($x,'webpage')) ? $x['webpage'] : ''); + $tpl = get_markup_template('jot-header.tpl'); $a->page['htmlhead'] .= replace_macros($tpl, array( @@ -1009,7 +1079,7 @@ function status_editor($a,$x,$popup=false) { '$return_path' => $a->query_string, '$action' => $a->get_baseurl(true) . '/item', '$share' => (x($x,'button') ? $x['button'] : t('Share')), - '$webpage' => (x($x,'webpage') ? '1' : ''), + '$webpage' => $webpage, '$placeholdpagetitle' => t('Page link title'), '$pagetitle' => (x($x,'pagetitle') ? $x['pagetitle'] : ''), '$upload' => t('Upload photo'), @@ -1028,7 +1098,7 @@ function status_editor($a,$x,$popup=false) { '$shortnoloc' => t('clear location'), '$title' => "", '$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'), @@ -1046,6 +1116,8 @@ function status_editor($a,$x,$popup=false) { '$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'] : 'yes'), '$bang' => $x['bang'], '$profile_uid' => $x['profile_uid'], @@ -1125,6 +1197,7 @@ function conv_sort($arr,$order) { 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); @@ -1200,12 +1273,21 @@ function render_location_default($item) { function prepare_page($item) { + + $a = get_app(); + $naked = ((get_pconfig($item['uid'],'system','nakedpage')) ? 1 : 0); + 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 + } + return replace_macros(get_markup_template('page_display.tpl'),array( - '$author' => $item['author']['xchan_name'], - '$auth_url' => $item['author']['xchan_url'], - '$date' => datetime_convert('UTC',date_default_timezone_get(),$item['created'],'Y-m-d H:i'), + '$author' => (($naked) ? '' : $item['author']['xchan_name']), + '$auth_url' => (($naked) ? '' : $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' => smilies(bbcode($item['body'])) + '$body' => prepare_text($item['body'],$item['mimetype']) )); } diff --git a/include/dba/dba_mysqli.php b/include/dba/dba_mysqli.php index f1a50cc3f..19907705b 100755 --- a/include/dba/dba_mysqli.php +++ b/include/dba/dba_mysqli.php @@ -70,7 +70,7 @@ class dba_mysqli extends dba_driver { function close() { if($this->db) $this->db->close(); - $this->connected = flase; + $this->connected = false; } }
\ No newline at end of file diff --git a/include/deliver.php b/include/deliver.php index 547d009cc..b1314ce39 100644 --- a/include/deliver.php +++ b/include/deliver.php @@ -26,7 +26,7 @@ function deliver_run($argv, $argc) { // 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); + zot_import($msg,z_root()); $r = q("delete from outq where outq_hash = '%s' limit 1", dbesc($argv[$x]) ); diff --git a/include/dir_fns.php b/include/dir_fns.php index 0b678fd91..585121434 100644 --- a/include/dir_fns.php +++ b/include/dir_fns.php @@ -13,7 +13,7 @@ function sync_directories($dirmode) { return; $r = q("select * from site where (site_flags & %d) and site_url != '%s'", - intval(DIRECTORY_MODE_PRIMARY), + intval(DIRECTORY_MODE_PRIMARY|DIRECTORY_MODE_SECONDARY), dbesc(z_root()) ); @@ -34,21 +34,79 @@ function sync_directories($dirmode) { 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; + $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))) { + $x = zot_finger($ud['ud_addr'],''); + if($x['success']) { + $j = json_decode($x['body'],true); + $y = import_xchan($j,0); + } + else { + $r = q("update updates set ud_last = '%s' where ud_addr = '%s'", + dbesc(datetime_convert()), + dbesc($ud['ud_addr']) + ); + } + } } - - function syncdirs($uid) { logger('syncdirs', LOGGER_DEBUG); diff --git a/include/enotify.php b/include/enotify.php index a15e42b73..147023f41 100644 --- a/include/enotify.php +++ b/include/enotify.php @@ -51,9 +51,9 @@ function notification($params) { $additional_mail_header = ""; if(array_key_exists('item',$params)) { + require_once('include/conversation.php'); // if it's a normal item... if(array_key_exists('verb',$params['item'])) { - require_once('include/conversation.php'); // localize_item() alters the original item so make a copy first $i = $params['item']; logger('calling localize'); @@ -89,6 +89,11 @@ function notification($params) { if($params['type'] == NOTIFY_COMMENT) { // logger("notification: params = " . print_r($params, true), LOGGER_DEBUG); + // 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_id = $params['parent']; // Check to see if there was already a notify for this post. @@ -379,11 +384,9 @@ function notification($params) { logger('notification: sending notification email'); - $textversion = strip_tags(html_entity_decode(bbcode(stripslashes(str_replace(array("\\r\\n", "\\r", "\\n"), "\n", - $body))),ENT_QUOTES,'UTF-8')); + $textversion = strip_tags(html_entity_decode(bbcode(stripslashes(str_replace(array("\\r", "\\n"), array( "", "\n"), $body))),ENT_QUOTES,'UTF-8')); - $htmlversion = html_entity_decode(bbcode(stripslashes(str_replace(array("\\r\\n", "\\r","\\n\\n" ,"\\n"), - "<br />\n",$body))), ENT_QUOTES,'UTF-8'); + $htmlversion = html_entity_decode(bbcode(stripslashes(str_replace(array("\\r","\\n"), array("","<br />\n"),$body))), ENT_QUOTES,'UTF-8'); // use $_SESSION['zid_override'] to force zid() to use diff --git a/include/event.php b/include/event.php index 29ada2e96..7873de1ef 100644 --- a/include/event.php +++ b/include/event.php @@ -360,7 +360,8 @@ function event_store($arr) { } - $item_id = item_store($item_arr); + $res = item_store($item_arr); + $item_id = $res['item_id']; call_hooks("event_created", $event['id']); diff --git a/include/features.php b/include/features.php index 757f719df..9950039c0 100644 --- a/include/features.php +++ b/include/features.php @@ -27,8 +27,7 @@ function get_features() { //FIXME - needs a description, but how the hell do we explain this to normals? array('sendzid', t('Extended Identity Sharing'), t(' ')), 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 @@ -36,6 +35,7 @@ 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')), ), // Network Tools diff --git a/include/follow.php b/include/follow.php index ce550b07f..10bcddf2b 100644 --- a/include/follow.php +++ b/include/follow.php @@ -11,7 +11,7 @@ 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' => ''); @@ -60,6 +60,11 @@ function new_contact($uid,$url,$channel,$interactive = false) { return $result; } + // Premium channel, set confirm before callback to avoid recursion + + if(array_key_exists('connect_url',$j) && (! $confirm)) + goaway(zid($j['connect_url'])); + // check service class limits diff --git a/include/friendica_smarty.php b/include/friendica_smarty.php index 1e8ef5406..12a789c9a 100755 --- a/include/friendica_smarty.php +++ b/include/friendica_smarty.php @@ -49,7 +49,7 @@ class FriendicaSmartyEngine implements ITemplateEngine { public function __construct(){ $a = get_app(); - $basecompiledir = $a->config['system']['smarty3_folder']; + $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(); diff --git a/include/identity.php b/include/identity.php index 5f210c456..d6b6735f6 100644 --- a/include/identity.php +++ b/include/identity.php @@ -7,7 +7,7 @@ require_once('include/crypto.php'); 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 ", + $r = q("select count(channel_id) as total from channel where channel_account_id = %d ", intval($account_id) ); if(! ($r && count($r))) { @@ -48,7 +48,7 @@ function validate_channelname($name) { function create_dir_account() { - create_account(array( + create_identity(array( 'account_id' => 'xxx', // This will create an identity with an (integer) account_id of 0, but account_id is required 'nickname' => 'dir', 'name' => 'Directory', @@ -80,8 +80,11 @@ function create_identity($arr) { $ret['message'] = t('No account identifier'); return $ret; } - - $nick = trim($arr['nickname']); + $ret=identity_check_service_class($arr['account_id']); + if (!$ret['success']) { + return $ret; + } + $nick = mb_strtolower(trim($arr['nickname'])); $name = escape_tags($arr['name']); $pageflags = ((x($arr,'pageflags')) ? intval($arr['pageflags']) : PAGE_NORMAL); @@ -182,7 +185,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_name, xchan_network, xchan_photo_date, xchan_name_date ) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", dbesc($hash), dbesc($guid), dbesc($sig), @@ -192,6 +195,7 @@ 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($ret['channel']['channel_name']), dbesc('zot'), dbesc(datetime_convert()), diff --git a/include/items.php b/include/items.php index 10bdcb38f..800684ae2 100755 --- a/include/items.php +++ b/include/items.php @@ -18,13 +18,37 @@ function collect_recipients($item,&$private) { require_once('include/group.php'); + if($item['item_private']) + $private = true; + if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid']) { $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; @@ -59,24 +83,44 @@ function collect_recipients($item,&$private) { } - +/** + * @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 can_comment_on_post($observer_xchan,$item) { + +// logger('can_comment_on_post: comment_policy: ' . $item['comment_policy'], LOGGER_DEBUG); + 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(($item['owner']['abook_xchan']) && ($item['owner']['abook_their_perms'] & PERMS_W_COMMENT)) - return true; + if(array_key_exists('owner',$item)) { + if(($item['owner']['abook_xchan']) && ($item['owner']['abook_their_perms'] & PERMS_W_COMMENT)) + return true; + } break; default: break; @@ -193,7 +237,9 @@ function post_activity_item($arr) { } - $post_id = item_store($arr); + $post = item_store($arr); + if($post['result']) + $post_id = $post['item_id']; if($post_id) { $arr['id'] = $post_id; @@ -220,16 +266,18 @@ function get_public_feed($channel,$params) { $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['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': @@ -250,9 +298,15 @@ function get_feed_for($channel, $observer_hash, $params) { if(! channel) http_status_exit(401); - if(! perm_is_allowed($channel['channel_id'],$observer_hash,'view_stream')) - http_status_exit(403); + 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'], @@ -260,6 +314,7 @@ function get_feed_for($channel, $observer_hash, $params) { '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); @@ -491,6 +546,7 @@ function title_is_body($title, $body) { function get_item_elements($x) { +// logger('get_item_elements'); $arr = array(); $arr['body'] = (($x['body']) ? htmlentities($x['body'],ENT_COMPAT,'UTF-8',false) : ''); @@ -524,6 +580,9 @@ function get_item_elements($x) { $arr['obj_type'] = (($x['object_type']) ? htmlentities($x['object_type'], ENT_COMPAT,'UTF-8',false) : ''); $arr['tgt_type'] = (($x['target_type']) ? htmlentities($x['target_type'], ENT_COMPAT,'UTF-8',false) : ''); $arr['comment_policy'] = (($x['comment_scope']) ? htmlentities($x['comment_scope'], ENT_COMPAT,'UTF-8',false) : 'contacts'); + + $arr['sig'] = (($x['signature']) ? htmlentities($x['signature'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['object'] = activity_sanitise($x['object']); $arr['target'] = activity_sanitise($x['target']); @@ -535,18 +594,6 @@ function get_item_elements($x) { $arr['item_flags'] = 0; - // 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(aes_encapsulate($arr['title'],$key)); - if($arr['body']) - $arr['body'] = json_encode(aes_encapsulate($arr['body'],$key)); - } if(array_key_exists('flags',$x) && in_array('deleted',$x['flags'])) $arr['item_restrict'] = ITEM_DELETED; @@ -573,6 +620,31 @@ function get_item_elements($x) { } + 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(aes_encapsulate($arr['title'],$key)); + if($arr['body']) + $arr['body'] = json_encode(aes_encapsulate($arr['body'],$key)); + } + + return $arr; } @@ -601,7 +673,7 @@ 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']) @@ -652,6 +724,7 @@ function encode_item($item) { $x['permalink'] = $item['plink']; $x['location'] = $item['location']; $x['longlat'] = $item['coord']; + $x['signature'] = $item['sig']; $x['owner'] = encode_item_xchan($item['owner']); $x['author'] = encode_item_xchan($item['author']); @@ -675,6 +748,8 @@ function encode_item($item) { if($item['term']) $x['tags'] = encode_item_terms($item['term']); + logger('encode_item: ' . print_r($x,true)); + return $x; } @@ -1246,14 +1321,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"; @@ -1284,13 +1354,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); } @@ -1329,6 +1393,7 @@ function get_atom_elements($feed,$item) { $arr = array('feed' => $feed, 'item' => $item, 'result' => $res); call_hooks('parse_atom', $arr); + logger('get_atom_elements: ' . print_r($res,true)); return $res; } @@ -1356,9 +1421,23 @@ function encode_rel_links($links) { function item_store($arr,$allow_exec = false) { + $ret = array('result' => false, 'item_id' => 0); + if(! $arr['uid']) { logger('item_store: no uid'); - return 0; + $ret['message'] = 'No uid.'; + return ret; + } + + // 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 @@ -1372,7 +1451,8 @@ function item_store($arr,$allow_exec = false) { if(($arr['mimetype'] == 'application/x-php') && (! $allow_exec)) { logger('item_store: php mimetype but allow_exec is denied.'); - return 0; + $ret['message'] = 'exec denied.'; + return $ret; } @@ -1385,21 +1465,27 @@ function item_store($arr,$allow_exec = false) { $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 ); - - // this is a bit messy - we really need an input filter chain that temporarily undoes obscuring - if($arr['mimetype'] != 'text/html' && $arr['mimetype'] != 'application/x-php') { - if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false)) - $arr['body'] = escape_tags($arr['body']); - if((strpos($arr['title'],'<') !== false) || (strpos($arr['title'],'>') !== false)) - $arr['title'] = escape_tags($arr['title']); - } + $arr['title'] = escape_tags($arr['title']); + // 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'); @@ -1408,7 +1494,8 @@ function item_store($arr,$allow_exec = 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; + $ret['message'] = 'language not accepted'; + return $ret; } $arr = $translate['item']; } @@ -1423,15 +1510,10 @@ function item_store($arr,$allow_exec = false) { } - if($arr['object']) - logger('item_store: input object: ' . print_r($arr['object'],true), LOGGER_DATA); - if((x($arr,'object')) && is_array($arr['object'])) { activity_sanitise($arr['object']); - logger('item_store: sanitised object: ' . print_r($arr['object'],true), LOGGER_DATA); $arr['object'] = json_encode($arr['object']); - logger('item_store: encoded object: ' . print_r($arr['object'],true), LOGGER_DATA); } if((x($arr,'target')) && is_array($arr['target'])) { @@ -1554,7 +1636,8 @@ function item_store($arr,$allow_exec = false) { } else { logger('item_store: item parent was not found - ignoring item'); - return 0; + $ret['message'] = 'parent not found.'; + return $ret; } } @@ -1567,15 +1650,17 @@ function item_store($arr,$allow_exec = false) { 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('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 @@ -1598,18 +1683,21 @@ function item_store($arr,$allow_exec = false) { // find the item we just created - $r = q("SELECT `id` FROM `item` WHERE `mid` = '%s' AND `uid` = %d ORDER BY `id` ASC ", + $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.'); @@ -1641,6 +1729,7 @@ function item_store($arr,$allow_exec = 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; @@ -1681,82 +1770,113 @@ function item_store($arr,$allow_exec = false) { 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) { + $ret = array('result' => false, 'item_id' => 0); if(! intval($arr['uid'])) { logger('item_store_update: no uid'); - return 0; + $ret['message'] = 'no uid.'; + return $ret; } if(! intval($arr['id'])) { logger('item_store_update: no id'); - return 0; + $ret['message'] = 'no id.'; + return $ret; } $orig_post_id = $arr['id']; - unset($arr['id']); $uid = $arr['uid']; - unset($arr['uid']); - - $arr['lang'] = detect_language($arr['body']); + $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; + } + + if($orig[0]['item_flags'] & ITEM_VERIFIED) + $orig[0]['item_flags'] = $orig[0]['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; - } - $arr = $translate['item']; - } + $arr['item_flags'] = intval($arr['item_flags']) | $orig[0]['item_flags']; + $arr['item_restrict'] = intval($arr['item_restrict']) | $orig[0]['item_restrict']; + unset($arr['id']); + unset($arr['uid']); + 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.'); - return 0; + $ret['message'] = 'exec denied.'; + return $ret; } + if(! ($arr['item_flags'] & ITEM_OBSCURED)) { - // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin. + $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; + } + } - if($arr['mimetype'] != 'text/html' && $arr['mimetype'] != 'application/x-php') { + $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(aes_encapsulate($arr['title'],$key)); + if($arr['body']) + $arr['body'] = json_encode(aes_encapsulate($arr['body'],$key)); + } - if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false)) - $arr['body'] = escape_tags($arr['body']); + } - 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,'object')) && is_array($arr['object'])) { + activity_sanitise($arr['object']); + $arr['object'] = json_encode($arr['object']); + } - if((x($arr,'attach')) && is_array($arr['attach'])) { - activity_sanitise($arr['attach']); - $arr['attach'] = json_encode($arr['attach']); - } + 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']); } - $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); - return 0; - } + unset($arr['aid']); unset($arr['mid']); @@ -1774,13 +1894,13 @@ function item_store_update($arr,$allow_exec = false) { $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['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['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'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : $orig[0]['allow_cid']); $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : $orig[0]['allow_gid']); @@ -1788,17 +1908,20 @@ function item_store_update($arr,$allow_exec = false) { $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : $orig[0]['deny_gid']); $arr['item_private'] = ((x($arr,'item_private')) ? 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'])) : ''); - $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['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['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'] = ((x($arr,'layout_mid')) ? 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.'); - return 0; + $ret['message'] = 'cancelled.'; + return $ret; } // pull out all the taxonomy stuff for separate storage @@ -1826,7 +1949,8 @@ function item_store_update($arr,$allow_exec = false) { logger('item_store_update: updated item ' . $orig_post_id, LOGGER_DEBUG); else { logger('item_store_update: could not update item'); - return 0; + $ret['message'] = 'DB update failed.'; + return $ret; } $r = q("delete from term where oid = %d and otype = %d", @@ -1855,8 +1979,10 @@ function item_store_update($arr,$allow_exec = false) { send_status_notifications($orig_post_id,$arr); tag_deliver($uid,$orig_post_id); + $ret['success'] = true; + $ret['item_id'] = $orig_post_id; - return $orig_post_id; + return $ret; } @@ -1993,6 +2119,12 @@ function tag_deliver($uid,$item_id) { logger('tag_deliver: tag permission denied for ' . $u[0]['channel_address']); } + + $union = check_item_source($uid,$item); + if($union) + logger('check_item_source returns true'); + + // This might be a followup by the original post author to a tagged forum // If so setup a second delivery chain @@ -2003,8 +2135,13 @@ function tag_deliver($uid,$item_id) { intval($item['parent']), intval($uid) ); - if(($x) && ($x[0]['item_flags'] & ITEM_UPLINK) && ($x[0]['author_xchan'] == $item['author_xchan'])) { - logger('tag_deliver: creating second delivery chain for owner comment.'); + +// issue #59 +// FIXME - check security on post and allowed senders, right now we just allow it. The author *may* be foreign and the original owner is lost on our copy of the post. So this could be very hard to verify. For instance what happens if the top-level post was a wall-to-wall? +// if(($x) && ($x[0]['item_flags'] & ITEM_UPLINK) && ($x[0]['author_xchan'] == $item['author_xchan'])) { + if(($x) && ($x[0]['item_flags'] & ITEM_UPLINK)) { +// logger('tag_deliver: creating second delivery chain for owner comment.'); + 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 @@ -2054,42 +2191,44 @@ function tag_deliver($uid,$item_id) { intval(ITEM_MENTIONSME), intval($item_id) ); - } - else - return; - // 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 + // 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 - $body = preg_replace('/\[share(.*?)\[\/share\]/','',$item['body']); + $body = preg_replace('/\[share(.*?)\[\/share\]/','',$item['body']); - $pattern = '/@\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($u[0]['channel_name'],'/') . '\[\/zrl\]/'; + $pattern = '/@\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($u[0]['channel_name'],'/') . '\[\/zrl\]/'; - if(! preg_match($pattern,$body,$matches)) { - logger('tag_deliver: mention was in a reshare - ignoring'); - return; - } + if(! preg_match($pattern,$body,$matches)) { + logger('tag_deliver: mention was in a reshare - ignoring'); + return; + } - // All good. - // Send a notification + // All good. + // 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' - )); + 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' + )); + + + if(! perm_is_allowed($uid,$item['author_xchan'],'tag_deliver')) { + logger('tag_delivery denied for uid ' . $uid . ' and xchan ' . $item['author_xchan']); + return; + } + } - if(! perm_is_allowed($uid,$item['author_xchan'],'tag_deliver')) { - logger('tag_delivery denied for uid ' . $uid . ' and xchan ' . $item['author_xchan']); + if((! $mention) && (! $union)) return; - } // tgroup delivery - setup a second delivery chain @@ -2198,6 +2337,73 @@ function tgroup_check($uid,$item) { } +/** + * @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' limit 1", + intval($uid), + dbesc($item['owner_xchan']) + ); + + if(! $r) + return false; + + $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; + + 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) === $word) + return true; + } + if(stristr($text,$word) !== false) + return true; + } + } + return false; +} + + + + function mail_store($arr) { if(! $arr['channel_id']) { @@ -2320,21 +2526,21 @@ function dfrn_deliver($owner,$contact,$atom, $dissolve = false) { 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; - } +// $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' : ''); @@ -2423,7 +2629,7 @@ function dfrn_deliver($owner,$contact,$atom, $dissolve = false) { $postvars['perm'] = 'r'; } - $postvars['ssl_policy'] = $ssl_policy; +// $postvars['ssl_policy'] = $ssl_policy; if($page) $postvars['page'] = $page; @@ -2526,6 +2732,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); @@ -2542,12 +2751,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 = ''; - // process any deleted entries @@ -2569,7 +2772,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) /* $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['uid']), + intval($importer['channel_id']), intval($contact['id']) ); */ @@ -2587,7 +2790,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) dbesc($when), dbesc(datetime_convert()), dbesc($item['mid']), - intval($importer['uid']) + intval($importer['channel_id']) ); } else { @@ -2598,7 +2801,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) dbesc($when), dbesc(datetime_convert()), dbesc($mid), - intval($importer['uid']) + intval($importer['channel_id']) ); } } @@ -2623,6 +2826,9 @@ 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; @@ -2634,11 +2840,6 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) if($pass == 1) continue; - // not allowed to post -// FIXME - check permissions -// if($contact['rel'] == CONTACT_IS_FOLLOWER) -// continue; - // Have we seen it? If not, import it. @@ -2660,12 +2861,12 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) $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. @@ -2677,7 +2878,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) dbesc($datarray['body']), dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), dbesc($item_id), - intval($importer['uid']) + intval($importer['channel_id']) ); } @@ -2686,7 +2887,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) $datarray['parent_mid'] = $parent_mid; - $datarray['uid'] = $importer['uid']; + $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'; @@ -2710,7 +2911,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) if($xt->type == ACTIVITY_OBJ_NOTE) { $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; @@ -2728,7 +2929,10 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) } } - $r = item_store($datarray); +logger('consume_feed: ' . print_r($datarray,true)); + +// $xx = item_store($datarray); + $r = $xx['item_id']; continue; } @@ -2759,7 +2963,7 @@ 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['uid'] = $importer['channel_id']; $ev['mid'] = $item_id; $ev['edited'] = $datarray['edited']; $ev['private'] = $datarray['private']; @@ -2768,23 +2972,23 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) $ev['cid'] = $contact['id']; $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 `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. @@ -2796,7 +3000,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) dbesc($datarray['body']), dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), dbesc($item_id), - intval($importer['uid']) + intval($importer['channel_id']) ); } @@ -2824,8 +3028,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. @@ -2836,7 +3040,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) } $datarray['parent_mid'] = $item_id; - $datarray['uid'] = $importer['uid']; + $datarray['uid'] = $importer['channel_id']; $datarray['contact-id'] = $contact['id']; if(! link_compare($datarray['owner-link'],$contact['url'])) { @@ -2854,16 +3058,20 @@ 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; } } } + + } @@ -2926,9 +3134,8 @@ function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) { $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 .= '<zot:env>' . base64url_encode($body, true) . '</zot:env>' . "\r\n"; - // FIXME for other content types - $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '</content>' . "\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']) { @@ -3599,23 +3806,29 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C return $result; } - $contacts = expand_groups(array($arr['group'])); - if((is_array($contacts)) && count($contacts)) { - $contact_str = implode(',',$contacts); + + $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 has no members.'); + $contact_str = ' 0 '; + info( t('Group is empty')); } - $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 item_restrict = 0 ) "; + $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 ) "; } elseif($arr['cid'] && $uid) { - $r = q("SELECT * from abook where abook_id = %d and abook_channel = %d and not ( abook_flags & " . intval(ABOOK_FLAG_BLOCKED) . ") limit 1", + $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($uid) + 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 ) "; @@ -3698,11 +3911,17 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C 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_MODELOAD) && $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 AND item_restrict = 0 + WHERE $item_uids $item_restrict $simple_update $sql_extra $sql_nets ORDER BY item.received DESC $pager_sql " @@ -3729,7 +3948,7 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C $r = q("SELECT distinct item.id AS item_id FROM item left join abook on item.author_xchan = abook.abook_xchan - WHERE $item_uids AND item.item_restrict = 0 + 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 @@ -3742,7 +3961,7 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C // update $r = q("SELECT item.parent AS item_id FROM item left join abook on item.author_xchan = abook.abook_xchan - WHERE $item_uids AND item.item_restrict = 0 $simple_update + 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) @@ -3758,7 +3977,7 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C $parents_str = ids_to_querystr($r,'item_id'); $items = q("SELECT item.*, item.id AS item_id FROM item - WHERE $item_uids AND item.item_restrict = 0 + WHERE $item_uids $item_restrict AND item.parent IN ( %s ) $sql_extra ", dbesc($parents_str) @@ -3790,4 +4009,4 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C } return $items; -}
\ No newline at end of file +} diff --git a/include/menu.php b/include/menu.php index 8d4664385..900b48e65 100644 --- a/include/menu.php +++ b/include/menu.php @@ -40,6 +40,7 @@ function menu_render($menu) { } + function menu_fetch_id($menu_id,$channel_id) { $r = q("select * from menu where menu_id = %d and menu_channel_id = %d limit 1", @@ -117,6 +118,8 @@ function menu_edit($arr) { return false; + $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) @@ -127,9 +130,6 @@ function menu_edit($arr) { } - - $menu_channel_id = intval($arr['menu_channel_id']); - $r = q("select * from menu where menu_id = %d and menu_channel_id = %d limit 1", intval($menu_id), intval($menu_channel_id) @@ -140,14 +140,16 @@ function menu_edit($arr) { } - $r = q("select * from menu where menu_name = '%s' and menu_channel_id = %d limit 1", + $r = q("select * from menu where menu_name = '%s' and menu_channel_id = %d and menu_desc = '%s' limit 1", dbesc($menu_name), - intval($menu_channel_id) + intval($menu_channel_id), + dbesc($menu_desc) ); if($r) return false; + return q("update menu set menu_name = '%s', menu_desc = '%s' where menu_id = %d and menu_channel_id = %d limit 1", dbesc($menu_name), diff --git a/include/nav.php b/include/nav.php index 626caf981..a16d8d078 100644 --- a/include/nav.php +++ b/include/nav.php @@ -95,12 +95,16 @@ EOT; ); } - if($observer) + if($observer) { + $nav['locked'] = true; $nav['lock'] = array('logout','','lock', sprintf( t('%s - click to logout'), $observer['xchan_addr'])); - else + } + 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 @@ -141,11 +145,11 @@ EOT; if(local_user()) { - $nav['network'] = array('network', t('Matrix'), "", t('Conversations from your grid')); + $nav['network'] = array('network', t('Matrix'), "", t('Your matrix')); $nav['network']['all']=array('notifications/network', t('See all matrix notifications'), "", ""); $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']['all']=array('notifications/channel', t('See all channel notifications'), "", ""); $nav['home']['mark'] = array('', t('Mark all channel notifications seen'), '',''); @@ -200,7 +204,7 @@ EOT; $a->page['nav'] .= replace_macros($tpl, array( '$baseurl' => $a->get_baseurl(), - '$langselector' => lang_selector(), + '$langselector' => ((get_config('system','select_language')) ? lang_selector() : ''), '$sitelocation' => $sitelocation, '$nav' => $nav, '$banner' => $banner, @@ -235,5 +239,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 8b9a8a6a6..99a0a8e2b 100644 --- a/include/network.php +++ b/include/network.php @@ -312,8 +312,13 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) { $rc = intval($http_code); $ret['return_code'] = $rc; $ret['success'] = (($rc >= 200 && $rc <= 299) ? true : false); + if(! $ret['success']) { + $ret['debug'] = $curl_info; + 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); } @@ -407,6 +412,11 @@ function z_post_url($url,$params, $redirects = 0, $opts = array()) { $rc = intval($http_code); $ret['return_code'] = $rc; $ret['success'] = (($rc >= 200 && $rc <= 299) ? true : false); + if(! $ret['success']) { + $ret['debug'] = $curl_info; + logger('z_fetch_url: debug:' . print_r($curl_info,true), LOGGER_DATA); + } + $ret['body'] = substr($s,strlen($header)); $ret['header'] = $header; curl_close($ch); @@ -1326,7 +1336,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 2a0301357..0c7ac5264 100644 --- a/include/notifier.php +++ b/include/notifier.php @@ -54,6 +54,7 @@ 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) * @@ -208,9 +209,8 @@ function notifier_run($argv, $argc){ $channel = $s[0]; $uid = $item_id; $recipients = array(); - $r = q("select * from abook where abook_channel = %d and not (abook_flags & %d)", - intval($item_id), - intval(ABOOK_FLAG_SELF) + $r = q("select abook_xchan from abook where abook_channel = %d", + intval($item_id) ); if($r) { foreach($r as $rr) { @@ -220,6 +220,26 @@ function notifier_run($argv, $argc){ $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 @@ -257,6 +277,10 @@ function notifier_run($argv, $argc){ return; } + if($target_item['item_restrict'] & ITEM_PDL) { + logger('notifier: target item ITEM_PDL', LOGGER_DEBUG); + return; + } $s = q("select * from channel where channel_id = %d limit 1", intval($target_item['uid']) @@ -369,6 +393,10 @@ function notifier_run($argv, $argc){ } } + 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); @@ -381,7 +409,7 @@ function notifier_run($argv, $argc){ $sql_extra = (($private) ? "" : " or hubloc_url = '" . z_root() . "' "); - $r = q("select distinct hubloc_sitekey, hubloc_callback, hubloc_host from hubloc + $r = q("select distinct hubloc_sitekey, hubloc_flags, hubloc_callback, hubloc_host from hubloc where hubloc_hash in (" . implode(',',$recipients) . ") $sql_extra group by hubloc_sitekey"); if(! $r) { logger('notifier: no hubs'); @@ -390,8 +418,12 @@ function notifier_run($argv, $argc){ $hubs = $r; $hublist = array(); - foreach($hubs as $hub) - $hublist[] = $hub['hubloc_host']; + foreach($hubs as $hub) { + // don't try to deliver to deleted hublocs + if(! ($hub['hubloc_flags'] & HUBLOC_FLAGS_DELETED)) { + $hublist[] = $hub['hubloc_host']; + } + } logger('notifier: will notify/deliver to these hubs: ' . print_r($hublist,true), LOGGER_DEBUG); @@ -407,8 +439,8 @@ function notifier_run($argv, $argc){ foreach($hubs as $hub) { $hash = random_string(); - if($packet_type === 'refresh') { - $n = zot_build_packet($channel,'refresh'); + 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_posturl, outq_async, outq_created, outq_updated, outq_notify, outq_msg ) values ( '%s', %d, %d, '%s', %d, '%s', '%s', '%s', '%s' )", dbesc($hash), intval($channel['channel_account']), diff --git a/include/oauth.php b/include/oauth.php index 6ec5285e4..b10802ecd 100644 --- a/include/oauth.php +++ b/include/oauth.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,36 @@ 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['page_flags'] = $record['page-flags']; + $_SESSION['my_url'] = $a->get_baseurl() . '/channel/' . $record['channel_address']; $_SESSION['addr'] = $_SERVER['REMOTE_ADDR']; + $_SESSION['allow_api'] = true; - //notice( t("Welcome back ") . $record['username'] . EOL); - $a->user = $record; + $a->channel = $record; - if(strlen($a->user['timezone'])) { - date_default_timezone_set($a->user['timezone']); - $a->timezone = $a->user['timezone']; + if(strlen($a->channel['channel_timezone'])) { + date_default_timezone_set($a->channel['channel_timezone']); +// $a->timezone = $a->user['timezone']; } - $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); +// $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/onedirsync.php b/include/onedirsync.php new file mode 100644 index 000000000..b9c17628a --- /dev/null +++ b/include/onedirsync.php @@ -0,0 +1,42 @@ +<?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'); + + 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['ud_flags'] & UPDATE_FLAGS_UPDATED) + 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 a225edfd8..50c2566be 100644 --- a/include/onepoll.php +++ b/include/onepoll.php @@ -69,7 +69,7 @@ function onepoll_run($argv, $argc){ $last_update = (($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 @@ -98,11 +98,12 @@ function onepoll_run($argv, $argc){ return; if($contact['xchan_connurl']) { - $feedurl = str_replace('/poco/','/zotfeed/',$channel['xchan_connurl']); - - $x = z_fetch_url($feedurl . '?f=$mindate=' . $last_update); + $feedurl = str_replace('/poco/','/zotfeed/',$channel['xchan_connurl']); + $x = z_fetch_url($feedurl . '?f=&mindate=' . $last_update); if($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) { diff --git a/include/page_widgets.php b/include/page_widgets.php index 23d6d25ba..d70281afc 100644 --- a/include/page_widgets.php +++ b/include/page_widgets.php @@ -5,7 +5,7 @@ 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'), + '$edit' => t('Edit'), '$editurl' => "editwebpage/$who/$which" )); } diff --git a/include/permissions.php b/include/permissions.php index bf50ebdd1..45ea7c3eb 100644 --- a/include/permissions.php +++ b/include/permissions.php @@ -28,6 +28,7 @@ function get_perms() { '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')), ); $ret = array('global_permissions' => $global_perms); diff --git a/include/photos.php b/include/photos.php index c670bd90c..ea4b494e0 100644 --- a/include/photos.php +++ b/include/photos.php @@ -216,13 +216,14 @@ function photo_upload($channel, $observer, $args) { $arr['allow_gid'] = $str_group_allow; $arr['deny_cid'] = $str_contact_deny; $arr['deny_gid'] = $str_group_deny; - + $arr['verb'] = ACTIVITY_POST; $arr['body'] = '[zrl=' . z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo_hash . ']' . '[zmg]' . z_root() . "/photo/{$photo_hash}-{$smallest}.".$ph->getExt() . '[/zmg]' . '[/zrl]'; - $item_id = item_store($arr); + $result = item_store($arr); + $item_id = $result['item_id']; if($visible) proc_run('php', "include/notifier.php", 'wall-new', $item_id); @@ -402,7 +403,8 @@ function photos_create_item($channel, $creator_hash, $photo, $visible = false) { . '[zmg]' . z_root() . '/photo/' . $photo['resource_id'] . '-' . $photo['scale'] . '[/zmg]' . '[/zrl]'; - $item_id = item_store($arr); + $result = item_store($arr); + $item_id = $result['item_id']; return $item_id; }
\ No newline at end of file diff --git a/include/plugin.php b/include/plugin.php index 01ee99786..ea88a61df 100755 --- a/include/plugin.php +++ b/include/plugin.php @@ -374,12 +374,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)) { @@ -404,13 +406,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']; @@ -420,6 +424,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; diff --git a/include/poller.php b/include/poller.php index 7a6aaeb22..95eb810a0 100644 --- a/include/poller.php +++ b/include/poller.php @@ -69,6 +69,8 @@ function poller_run($argv, $argc){ $d1 = get_config('system','last_expire_day'); $d2 = intval(datetime_convert('UTC','UTC','now','d')); + $dirmode = get_config('system','directory_mode'); + if($d2 != intval($d1)) { // If this is a directory server, request a sync with an upstream @@ -76,7 +78,6 @@ function poller_run($argv, $argc){ // Pull remote changes and push local changes. // potential issue: how do we keep from creating an endless update loop? - $dirmode = get_config('system','directory_mode'); if($dirmode == DIRECTORY_MODE_SECONDARY || $dirmode == DIRECTORY_MODE_PRIMARY) { require_once('include/dir_fns.php'); sync_directories($dirmode); @@ -158,7 +159,7 @@ function poller_run($argv, $argc){ ); - $contacts = q("SELECT abook_id, abook_updated, abook_connected, 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 )) @@ -171,69 +172,95 @@ function poller_run($argv, $argc){ ); - if(! $contacts) { - return; - } + if($contacts) { - foreach($contacts as $contact) { + foreach($contacts as $contact) { - $update = false; + $update = false; - $t = $contact['abook_updated']; - $c = $contact['abook_connected']; + $t = $contact['abook_updated']; + $c = $contact['abook_connected']; - 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 - - 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($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 + + 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; + } + + // 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; + + // After that back off and put them on a morphine drip + + if(strcmp(datetime_convert('UTC','UTC', 'now'),datetime_convert('UTC','UTC', $t . " + 2 day")) > 0) { + $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; } - // might be dead, so maybe don't poll quite so often + if((! $update) && (! $force)) + continue; - // 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; + proc_run('php','include/onepoll.php',$contact['abook_id']); + if($interval) + @time_sleep_until(microtime(true) + (float) $interval); - // After that back off and put them on a morphine drip + } + } - if(strcmp(datetime_convert('UTC','UTC', 'now'),datetime_convert('UTC','UTC', $t . " + 2 day")) > 0) { - $update = true; + if($dirmode == DIRECTORY_MODE_SECONDARY || $dirmode == DIRECTORY_MODE_PRIMARY) { + $r = q("select ud_id from updates where not ( ud_flags & %d ) and ( ud_last = '0000-00-00 00:00:00' OR ud_last > UTC_TIMESTAMP() - INTERVAL 7 DAY ) ", + 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); } } -dbg(0); - if((! $update) && (! $force)) - continue; - - proc_run('php','include/onepoll.php',$contact['abook_id']); - if($interval) - @time_sleep_until(microtime(true) + (float) $interval); - } - + return; } diff --git a/include/security.php b/include/security.php index 4738e473b..1181e6bf2 100644 --- a/include/security.php +++ b/include/security.php @@ -302,16 +302,19 @@ function public_permissions_sql($observer_hash) { foreach($groups as $g) $gs .= '|<' . $g . '>'; } - $sql = sprintf( - " 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( '%<' . $observer_hash . '>%')), - dbesc($gs), - dbesc(protect_sprintf( '%<' . $observer_hash . '>%')), - dbesc($gs) - ); + $sql = ''; + if($observer_hash) { + $sql = sprintf( + " 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( '%<' . $observer_hash . '>%')), + dbesc($gs), + dbesc(protect_sprintf( '%<' . $observer_hash . '>%')), + dbesc($gs) + ); + } return $sql; } @@ -409,7 +412,7 @@ function stream_perms_api_uids($perms_min = PERMS_SITE) { $ret = array(); if(local_user()) $ret[] = local_user(); - $r = q("select channel_id from channel where channel_r_stream <= %d", + $r = q("select channel_id from channel where channel_r_stream > 0 and channel_r_stream <= %d", intval($perms_min) ); if($r) @@ -424,6 +427,30 @@ function stream_perms_api_uids($perms_min = PERMS_SITE) { $str .= ','; $str .= intval($rr); } +logger('stream_perms_api_uids: ' . $str); return $str; } +function stream_perms_xchans($perms_min = PERMS_SITE) { + $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", + intval($perms_min) + ); + 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); + return $str; +} diff --git a/include/taxonomy.php b/include/taxonomy.php index b6803743a..85396a51d 100644 --- a/include/taxonomy.php +++ b/include/taxonomy.php @@ -99,6 +99,7 @@ function format_term_for_display($term) { function tagadelic($uid, $count = 0, $authors = '', $flags = 0, $type = TERM_HASHTAG) { $sql_options = ''; + $count = intval($count); if($flags) $sql_options .= " and ((item_flags & " . intval($flags) . ") = " . intval($flags) . ") "; @@ -157,6 +158,47 @@ function tags_sort($a,$b) { } +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,$type = TERM_HASHTAG) { $o = ''; $tab = 0; @@ -172,6 +214,24 @@ function tagblock($link,$uid,$count = 0,$authors = '',$flags = 0,$type = TERM_HA return $o; } +function dir_tagblock($link,$r) { + $o = ''; + $tab = 0; + + 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" diff --git a/include/text.php b/include/text.php index 99d5c9d78..8f700458c 100755 --- a/include/text.php +++ b/include/text.php @@ -81,6 +81,34 @@ function escape_tags($string) { } +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'); @@ -698,7 +726,7 @@ 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="text" class="icon-search" name="search" id="search-text" placeholder="" value="' . $s .'" onclick="this.submit();" />'; $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') . '" />'; @@ -976,6 +1004,17 @@ function link_compare($a,$b) { // If attach is true, also add icons for item attachments +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'] = aes_unencapsulate(json_decode_plus($item['title']),$key); + if($item['body']) + $item['body'] = aes_unencapsulate(json_decode_plus($item['body']),$key); + } + +} + function prepare_body(&$item,$attach = false) { @@ -985,13 +1024,7 @@ function prepare_body(&$item,$attach = false) { call_hooks('prepare_body_init', $item); - if(array_key_exists('item_flags',$item) && ($item['item_flags'] & ITEM_OBSCURED)) { - $key = get_config('system','prvkey'); - if($item['title']) - $item['title'] = aes_unencapsulate(json_decode_plus($item['title']),$key); - if($item['body']) - $item['body'] = aes_unencapsulate(json_decode_plus($item['body']),$key); - } + unobscure($item); $s = prepare_text($item['body'],$item['mimetype']); @@ -1127,6 +1160,7 @@ function prepare_body(&$item,$attach = false) { function prepare_text($text,$content_type = 'text/bbcode') { + switch($content_type) { case 'text/plain': @@ -1171,6 +1205,8 @@ function prepare_text($text,$content_type = 'text/bbcode') { break; } +//logger('prepare_text: ' . $s); + return $s; } @@ -1276,7 +1312,7 @@ function get_plink($item) { $a = get_app(); if (x($item,'plink') && ($item['item_private'] != 1)) { return array( - 'href' => $item['plink'], + 'href' => zid($item['plink']), 'title' => t('link to source'), ); } @@ -1290,9 +1326,64 @@ 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; + +} + + function lang_selector() { global $a; @@ -1740,4 +1831,27 @@ function json_decode_plus($s) { $x = json_decode(str_replace(array('\\"','\\\\'),array('"','\\'),$s),true); return $x; -}
\ No newline at end of file +} + + +function design_tools() { +$channel = get_app()->get_channel(); +$who = $channel['channel_address']; + +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') + )); + +} + +/* case insensitive in_array() */ + +function in_arrayi($needle, $haystack) { + return in_array(strtolower($needle), array_map('strtolower', $haystack)); +} + diff --git a/include/zot.php b/include/zot.php index bddbc9bee..b250557e6 100644 --- a/include/zot.php +++ b/include/zot.php @@ -111,12 +111,14 @@ function zot_zot($url,$data) { * 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) { +function zot_finger($webbie,$channel,$autofallback = true) { if(strpos($webbie,'@') === false) { @@ -138,7 +140,7 @@ function zot_finger($webbie,$channel) { $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) ); @@ -165,7 +167,7 @@ function zot_finger($webbie,$channel) { $result = z_post_url($url . $rhs,$postvars); - if(! $result['success']) { + if((! $result['success']) && ($autofallback)) { if($https) { logger('zot_finger: https failed. falling back to http'); $result = z_post_url('http://' . $host . $rhs,$postvars); @@ -176,7 +178,7 @@ function zot_finger($webbie,$channel) { $rhs .= '?f=&address=' . urlencode($address); $result = z_fetch_url($url . $rhs); - if(! $result['success']) { + if((! $result['success']) && ($autofallback)) { if($https) { logger('zot_finger: https failed. falling back to http'); $result = z_fetch_url('http://' . $host . $rhs); @@ -411,12 +413,13 @@ function zot_register_hub($arr) { // If the xchan already exists, update the name and photo if these have changed. // -function import_xchan($arr) { +function import_xchan($arr,$ud_flags = 1) { $ret = array('success' => false); $dirmode = intval(get_config('system','directory_mode')); $changed = false; + $what = ''; if(! (is_array($arr) && array_key_exists('success',$arr) && $arr['success'])) { logger('import_xchan: invalid data packet: ' . print_r($arr,true)); @@ -441,6 +444,11 @@ function import_xchan($arr) { 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']) @@ -462,18 +470,28 @@ function import_xchan($arr) { $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; + + 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_flags = %d, + $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']), @@ -482,8 +500,7 @@ function import_xchan($arr) { logger('import_xchan: existing: ' . print_r($r[0],true), LOGGER_DATA); logger('import_xchan: new: ' . print_r($arr,true), LOGGER_DATA); - - update_modtime($xchan_hash); + $what .= 'xchan '; $changed = true; } } @@ -501,10 +518,12 @@ function import_xchan($arr) { $new_flags = XCHAN_FLAGS_HIDDEN; else $new_flags = 0; + if($arr['adult_content']) + $new_flags |= XCHAN_FLAGS_SELFCENSORED; $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, xchan_flags) - values ( '%s', '%s', '%s', '%s' , '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d) ", + 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']), @@ -514,13 +533,16 @@ 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']), intval($new_flags) ); - update_modtime($xchan_hash); + + $what .= 'new_xchan'; $changed = true; } @@ -541,7 +563,7 @@ function import_xchan($arr) { dbesc($xchan_hash) ); - update_modtime($xchan_hash); + $what .= 'photo '; $changed = true; } @@ -567,23 +589,44 @@ function import_xchan($arr) { } } - $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' limit 1", + // 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' limit 1", dbesc($xchan_hash), dbesc($arr['guid']), dbesc($arr['guid_sig']), dbesc($location['url']), - dbesc($location['url_sig']) + dbesc($location['url_sig']), + dbesc($location['host']), + dbesc($location['address']), + dbesc($location['callback']), + dbesc($location['sitekey']) ); if($r) { logger('import_xchan: hub exists: ' . $location['url']); + // update connection timestamp + q("update hubloc set hubloc_connected = '%s' where hubloc_id = %d limit 1", + dbesc(datetime_convert()), + intval($r[0]['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) where hubloc_id = %d limit 1", + $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']) ); - update_modtime($xchan_hash); + $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; @@ -594,18 +637,22 @@ function import_xchan($arr) { continue; } + if(strpos($location['address'],'/') !== false) + $location['address'] = substr($location['address'],0,strpos($location['address'],'/')); + // new hub claiming to be primary. Make it so. if(intval($location['primary'])) { - $r = q("update hubloc set hubloc_flags = (hubloc_flags ^ %d) where hubloc_hash = '%s' and (hubloc_flags & %d )", + $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) - values ( '%s','%s','%s','%s', %d ,'%s','%s','%s','%s','%s')", + $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), @@ -615,9 +662,11 @@ 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()) ); - update_modtime($xchan_hash); + $what .= 'newhub '; $changed = true; } @@ -629,7 +678,7 @@ function import_xchan($arr) { $r = q("delete from hubloc where hubloc_id = %d limit 1", intval($x['hubloc_id']) ); - update_modtime($xchan_hash); + $what .= 'removed_hub'; $changed = true; } } @@ -641,9 +690,9 @@ function import_xchan($arr) { if($dirmode != DIRECTORY_MODE_NORMAL) { if(array_key_exists('profile',$arr) && is_array($arr['profile'])) { - $profile_changed = import_directory_profile($xchan_hash,$arr['profile']); + $profile_changed = import_directory_profile($xchan_hash,$arr['profile'],$arr['address'],$ud_flags, 1); if($profile_changed) { - update_modtime($xchan_hash); + $what .= 'profile '; $changed = true; } } @@ -662,7 +711,7 @@ function import_xchan($arr) { if(array_key_exists('site',$arr) && is_array($arr['site'])) { $profile_changed = import_site($arr['site'],$arr['key']); if($profile_changed) { - update_modtime($xchan_hash); + $what .= 'site '; $changed = true; } } @@ -670,9 +719,9 @@ function import_xchan($arr) { if($changed) { - // send out a directory mirror update packet if we're a directory server or some kind - - + $guid = random_string() . '@' . get_app()->get_hostname(); + update_modtime($xchan_hash,$guid,$arr['address'],$ud_flags); + logger('import_xchan: changed: ' . $what,LOGGER_DEBUG); } if(! x($ret,'message')) { @@ -741,10 +790,6 @@ function zot_fetch($arr) { 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', @@ -755,12 +800,10 @@ 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'])); $fetch = zot_zot($url,$datatosend); - - $result = zot_import($fetch); + $result = zot_import($fetch, $arr['sender']['url']); return $result; } @@ -773,7 +816,7 @@ function zot_fetch($arr) { * The message types handled here are 'activity' (e.g. posts), 'mail' and 'profile' */ -function zot_import($arr) { +function zot_import($arr, $sender_url) { $data = json_decode($arr['body'],true); @@ -800,6 +843,13 @@ function zot_import($arr) { 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; + } + + $i['notify']['sender']['hash'] = base64url_encode(hash('whirlpool',$i['notify']['sender']['guid'] . $i['notify']['sender']['guid_sig'], true)); $deliveries = null; @@ -823,12 +873,19 @@ function zot_import($arr) { } else { + if((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 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); + } if(! $deliveries) { logger('zot_import: no deliveries on this site'); @@ -881,8 +938,9 @@ function zot_import($arr) { $result = process_channel_sync_delivery($i['notify']['sender'],$arr,$deliveries); } } - if($result) + if($result){ $return = array_merge($return,$result); + } } } @@ -901,14 +959,24 @@ function zot_import($arr) { function public_recips($msg) { + $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') { @@ -919,18 +987,22 @@ 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 . " ) OR ( " . $col . " & " . PERMS_CONTACTS . " )) and ( abook_my_perms & " . $field . " ) ", + and (( " . $col . " & " . PERMS_SPECIFIC . " ) and ( abook_my_perms & " . $field . " )) OR ( " . $col . " & " . PERMS_CONTACTS . " ) ", dbesc($msg['notify']['sender']['hash']) ); @@ -938,6 +1010,27 @@ function public_recips($msg) { $x = array(); $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; } @@ -1008,6 +1101,14 @@ function allowed_public_recips($msg) { function process_delivery($sender,$arr,$deliveries,$relay) { $result = array(); + + + // We've validated the sender. Now make sure that the sender is the owner or author + + if($sender['hash'] != $arr['owner_xchan'] && $sender['hash'] != $arr['author_xchan']) { + logger('process_delivery: sender is not owner or author'); + return; + } foreach($deliveries as $d) { $r = q("select * from channel where channel_hash = '%s' limit 1", @@ -1108,8 +1209,9 @@ function process_delivery($sender,$arr,$deliveries,$relay) { else { $arr['aid'] = $channel['channel_account_id']; $arr['uid'] = $channel['channel_id']; - $item_id = item_store($arr); - $result[] = array($d['hash'],(($item_id) ? 'posted' : 'storage failed'),$channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>'); + $item_result = item_store($arr); + $item_id = $item_result['item_id']; + $result[] = array($d['hash'],(($item_id) ? 'posted' : 'storage failed:' . $item_result['message']),$channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>'); } if($relay && $item_id) { @@ -1192,8 +1294,11 @@ function remove_community_tag($sender,$arr,$uid) { function update_imported_item($sender,$item,$uid) { - item_store_update($item); - 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'); } @@ -1223,6 +1328,14 @@ 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", @@ -1277,7 +1390,12 @@ function process_profile_delivery($sender,$arr,$deliveries) { // deliveries is irrelevant, what to do about birthday notification....? logger('process_profile_delivery', LOGGER_DEBUG); - import_directory_profile($sender['hash'],$arr); + + $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'], 1, 0); } @@ -1288,7 +1406,7 @@ function process_profile_delivery($sender,$arr,$deliveries) { * */ -function import_directory_profile($hash,$profile) { +function import_directory_profile($hash,$profile,$addr,$ud_flags = 1, $suppress_update = 0) { logger('import_directory_profile', LOGGER_DEBUG); if(! $hash) @@ -1314,12 +1432,24 @@ function import_directory_profile($hash,$profile) { foreach($profile['keywords'] as $kw) { $kw = trim(htmlentities($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) ); @@ -1327,6 +1457,7 @@ function import_directory_profile($hash,$profile) { $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; } @@ -1362,6 +1493,7 @@ function import_directory_profile($hash,$profile) { } else { $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_keywords) values ('%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ", dbesc($arr['xprof_hash']), dbesc($arr['xprof_desc']), @@ -1381,8 +1513,8 @@ function import_directory_profile($hash,$profile) { $d = array('xprof' => $arr, 'profile' => $profile, 'update' => $update); call_hooks('import_directory_profile', $d); - if($d['update']) - update_modtime($arr['xprof_hash']); + if(($d['update']) && (! $suppress_update)) + update_modtime($arr['xprof_hash'],random_string() . '@' . get_app()->get_hostname(), $addr, $ud_flags); return $d['update']; } @@ -1401,6 +1533,7 @@ function import_directory_keywords($hash,$keywords) { $clean = array(); foreach($keywords as $kw) { $kw = trim(htmlentities($kw,ENT_COMPAT,'UTF-8',false)); + $kw = trim($kw,','); $clean[] = $kw; } @@ -1421,20 +1554,23 @@ function import_directory_keywords($hash,$keywords) { } -function update_modtime($hash) { - $r = q("select * from updates where ud_hash = '%s' limit 1", - dbesc($hash) - ); - if($r) - q("update updates set ud_date = '%s' where ud_hash = '%s' limit 1", +function update_modtime($hash,$guid,$addr,$flags = 0) { + + 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()), - dbesc($hash) + intval($flags), + dbesc($addr) ); - else - q("insert into updates (ud_hash, ud_date) values ( '%s', '%s' )", - dbesc($hash), - dbesc(datetime_convert()) + } + else { + q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not (ud_flags & %d) ", + intval(UPDATE_FLAGS_UPDATED), + intval(UPDATE_FLAGS_UPDATED) ); + } } @@ -1448,12 +1584,15 @@ function import_site($arr,$pubkey) { } $update = false; + $exists = false; $r = q("select * from site where site_url = '%s' limit 1", dbesc($arr['url']) ); - if($r) - $update = true; + if($r) { + $exists = true; + $siterecord = $r[0]; + } $site_directory = 0; if($arr['directory_mode'] == 'normal') @@ -1474,34 +1613,66 @@ function import_site($arr,$pubkey) { if($arr['register_policy'] == 'approve') $register_policy = REGISTER_APPROVE; - if($update) { - $r = q("update site set site_flags = %d, site_directory = '%s', site_register = %d, site_update = '%s' - where site_url = '%s' limit 1", - intval($site_directory), - dbesc(htmlentities($arr['directory_url'],ENT_COMPAT,'UTF-8',false)), - intval($register_policy), - dbesc(datetime_convert()), - dbesc(htmlentities($arr['url'],ENT_COMPAT,'UTF-8',false)) - ); - if(! $r) { - logger('import_site: update failed. ' . print_r($arr,true)); + $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; + } + + $directory_url = htmlentities($arr['directory_url'],ENT_COMPAT,'UTF-8',false); + $url = htmlentities($arr['url'],ENT_COMPAT,'UTF-8',false); + $sellpage = htmlentities($arr['sellpage'],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_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_flags = %d, site_access = %d, site_directory = '%s', site_register = %d, site_update = '%s', site_sellpage = '%s' + where site_url = '%s' limit 1", + 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 { - $r = q("insert into site ( site_url, site_flags, site_update, site_directory, site_register ) - values ( '%s', %d, '%s', '%s', %d )", - dbesc(htmlentities($arr['url'],ENT_COMPAT,'UTF-8',false)), + $update = true; + $r = q("insert into site ( site_url, site_access, site_flags, site_update, site_directory, site_register, site_sellpage ) + values ( '%s', %d, %d, '%s', '%s', %d, '%s' )", + dbesc($url), intval($site_directory), + intval($access_policy), dbesc(datetime_convert()), - dbesc(htmlentities($arr['directory_url'],ENT_COMPAT,'UTF-8',false)), - intval($register_policy) + dbesc($directory_url), + intval($register_policy), + dbesc($sellpage) ); if(! $r) { logger('import_site: record create failed. ' . print_r($arr,true)); } } - return $r; + return $update; } @@ -1675,7 +1846,7 @@ function process_channel_sync_delivery($sender,$arr,$deliveries) { $clean = array(); foreach($arr['abook'] as $abook) { foreach($abook as $k => $v) { - if(in_array($k,$disallowed)) + if(in_array($k,$disallowed) || (strpos($k,'abook') !== 0)) continue; $clean[$k] = $v; } @@ -1683,6 +1854,20 @@ function process_channel_sync_delivery($sender,$arr,$deliveries) { 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) |