X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=include%2Fitems.php;h=f87a92334c1776f22aa67f92853406e494c2be43;hb=11871195be019bb05c7c41dbb4a980fffce41697;hp=1dd39d2ba9f4f7012d8b8bb520c16088b6cd05c1;hpb=379af2521559c263622348bce41872b61067e532;p=friendica.git diff --git a/include/items.php b/include/items.php old mode 100644 new mode 100755 index 1dd39d2ba9..f87a92334c --- a/include/items.php +++ b/include/items.php @@ -1,17 +1,31 @@ argc > 2) { + for($x = 2; $x < $a->argc; $x++) { + if($a->argv[$x] == 'converse') + $converse = true; + if($a->argv[$x] == 'starred') + $starred = true; + } + } + + $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' "; $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone` @@ -29,7 +43,7 @@ function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) $birthday = feed_birthday($owner_id,$owner['timezone']); - if(strlen($dfrn_id)) { + if(! $public_feed) { $sql_extra = ''; switch($direction) { @@ -81,7 +95,7 @@ function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) ); } - if($dfrn_id === '' || $dfrn_id === '*') + if($public_feed) $sort = 'DESC'; else $sort = 'ASC'; @@ -89,15 +103,22 @@ function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) if(! strlen($last_update)) $last_update = 'now -30 days'; + if($public_feed) { + if(! $converse) + $sql_extra .= " AND `contact`.`self` = 1 "; + } + $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s'); $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`, - `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid` + `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`, + `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer` FROM `item` LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - WHERE `item`.`uid` = %d AND `item`.`visible` = 1 AND `item`.`parent` != 0 + LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id` + WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0 AND `item`.`wall` = 1 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' ) $sql_extra @@ -113,7 +134,7 @@ function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) $items = $r; - $feed_template = load_view_file('view/atom_feed.tpl'); + $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl'); $atom = ''; @@ -122,7 +143,7 @@ function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) $salmon = feed_salmonlinks($owner_nick); $atom .= replace_macros($feed_template, array( - '$version' => xmlify(FRIENDIKA_VERSION), + '$version' => xmlify(FRIENDICA_VERSION), '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick), '$feed_title' => xmlify($owner['name']), '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) , @@ -152,8 +173,11 @@ function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) // public feeds get html, our own nodes use bbcode - if($dfrn_id === '') { + if($public_feed) { $type = 'html'; + // catch any email that's in a public conversation and make sure it doesn't leak + if($item['private']) + continue; } else { $type = 'text'; @@ -180,7 +204,11 @@ function construct_activity_object($item) { if($item['object']) { $o = '' . "\r\n"; - $r = @simplexml_load_string($item['object']); + $r = parse_xml_string($item['object'],false); + + + if(! $r) + return ''; if($r->type) $o .= '' . xmlify($r->type) . '' . "\r\n"; if($r->id) @@ -188,8 +216,15 @@ function construct_activity_object($item) { if($r->title) $o .= '' . xmlify($r->title) . '' . "\r\n"; if($r->link) { - if(substr($r->link,0,1) === '<') + if(substr($r->link,0,1) === '<') { + // patch up some facebook "like" activity objects that got stored incorrectly + // for a couple of months prior to 9-Jun-2011 and generated bad XML. + // we can probably remove this hack here and in the following function in a few months time. + if(strstr($r->link,'&') && (! strstr($r->link,'&'))) + $r->link = str_replace('&','&', $r->link); + $r->link = preg_replace('/\/','',$r->link); $o .= $r->link; + } else $o .= '' . "\r\n"; } @@ -206,7 +241,9 @@ function construct_activity_target($item) { if($item['target']) { $o = '' . "\r\n"; - $r = @simplexml_load_string($item['target']); + $r = parse_xml_string($item['target'],false); + if(! $r) + return ''; if($r->type) $o .= '' . xmlify($r->type) . '' . "\r\n"; if($r->id) @@ -214,8 +251,12 @@ function construct_activity_target($item) { if($r->title) $o .= '' . xmlify($r->title) . '' . "\r\n"; if($r->link) { - if(substr($r->link,0,1) === '<') + if(substr($r->link,0,1) === '<') { + if(strstr($r->link,'&') && (! strstr($r->link,'&'))) + $r->link = str_replace('&','&', $r->link); + $r->link = preg_replace('/\/','',$r->link); $o .= $r->link; + } else $o .= '' . "\r\n"; } @@ -241,13 +282,24 @@ function get_atom_elements($feed,$item) { $res = array(); $author = $item->get_author(); - $res['author-name'] = unxmlify($author->get_name()); - $res['author-link'] = unxmlify($author->get_link()); + if($author) { + $res['author-name'] = unxmlify($author->get_name()); + $res['author-link'] = unxmlify($author->get_link()); + } + else { + $res['author-name'] = unxmlify($feed->get_title()); + $res['author-link'] = unxmlify($feed->get_permalink()); + } $res['uri'] = unxmlify($item->get_id()); $res['title'] = unxmlify($item->get_title()); $res['body'] = unxmlify($item->get_content()); $res['plink'] = unxmlify($item->get_link(0)); + if($res['plink']) + $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3)); + else + $base_url = ''; + // look for a photo. We should check media size and find the best one, // but for now let's just find any author photo @@ -313,6 +365,28 @@ function get_atom_elements($feed,$item) { } } + $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info'); + if($apps && $apps[0]['attribs']['']['source']) { + $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source'])); + if($res['app'] === 'web') + $res['app'] = 'OStatus'; + } + + // base64 encoded json structure representing Diaspora signature + + $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature'); + if($dsig) { + $res['dsprsig'] = unxmlify($dsig[0]['data']); + } + + $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid'); + if($dguid) + $res['guid'] = unxmlify($dguid[0]['data']); + + $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark'); + if($bm) + $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0); + /** * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it. @@ -343,11 +417,11 @@ function get_atom_elements($feed,$item) { // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining // html. - if((strpos($res['body'],'<') !== false) || (strpos($res['body'],'>') !== false)) { - $res['body'] = preg_replace('#]+>.+?' . 'http://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+).+?#s', - '[youtube]$1[/youtube]', $res['body']); + $res['body'] = reltoabs($res['body'],$base_url); + + $res['body'] = html2bb_video($res['body']); $res['body'] = oembed_html2bbcode($res['body']); @@ -376,6 +450,9 @@ function get_atom_elements($feed,$item) { else $res['private'] = 0; + $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid'); + if($extid && $extid[0]['data']) + $res['extid'] = $extid[0]['data']; $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location'); if($rawlocation) @@ -451,7 +528,44 @@ function get_atom_elements($feed,$item) { if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow')) $res['verb'] = ACTIVITY_UNFOLLOW; - + $cats = $item->get_categories(); + if($cats) { + $tag_arr = array(); + foreach($cats as $cat) { + $term = $cat->get_term(); + if(! $term) + $term = $cat->get_label(); + $scheme = $cat->get_scheme(); + if($scheme && $term && stristr($scheme,'X-DFRN:')) + $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]'; + elseif($term) + $tag_arr[] = notags(trim($term)); + } + $res['tag'] = implode(',', $tag_arr); + } + + $attach = $item->get_enclosures(); + if($attach) { + $att_arr = array(); + foreach($attach as $att) { + $len = intval($att->get_length()); + $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link())))); + $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title())))); + $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type())))); + if(strpos($type,';')) + $type = substr($type,0,strpos($type,';')); + if((! $link) || (strpos($link,'http') !== 0)) + continue; + + if(! $title) + $title = ' '; + if(! $type) + $type = 'application/octet-stream'; + + $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]'; + } + $res['attach'] = implode(',', $att_arr); + } $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object'); @@ -475,8 +589,7 @@ function get_atom_elements($feed,$item) { $res['object'] .= '' . xmlify($body) . '' . "\n"; if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) { - $body = preg_replace('#]+>.+?' . 'http://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+).+?#s', - '[youtube]$1[/youtube]', $body); + $body = html2bb_video($body); $config = HTMLPurifier_Config::createDefault(); $config->set('Cache.DefinitionImpl', null); @@ -511,11 +624,10 @@ function get_atom_elements($feed,$item) { if(! $body) $body = $rawobj[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data']; // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events - $res['object'] .= '' . xmlify($body) . '' . "\n"; + $res['target'] .= '' . xmlify($body) . '' . "\n"; if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) { - $body = preg_replace('#]+>.+?' . 'http://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+).+?#s', - '[youtube]$1[/youtube]', $body); + $body = html2bb_video($body); $config = HTMLPurifier_Config::createDefault(); $config->set('Cache.DefinitionImpl', null); @@ -561,9 +673,18 @@ function encode_rel_links($links) { function item_store($arr,$force_parent = false) { + // If a Diaspora signature structure was passed in, pull it out of the + // item array and set it aside for later storage. + + $dsprsig = null; + if(x($arr,'dsprsig')) { + $dsprsig = json_decode(base64_decode($arr['dsprsig'])); + unset($arr['dsprsig']); + } + if($arr['gravity']) $arr['gravity'] = intval($arr['gravity']); - elseif($arr['parent-uri'] == $arr['uri']) + elseif($arr['parent-uri'] === $arr['uri']) $arr['gravity'] = 0; elseif(activity_match($arr['verb'],ACTIVITY_POST)) $arr['gravity'] = 6; @@ -581,6 +702,7 @@ function item_store($arr,$force_parent = false) { $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0); $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string()); + $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : ''); $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : ''); $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : ''); $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : ''); @@ -589,6 +711,8 @@ function item_store($arr,$force_parent = false) { $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : ''); $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert()); $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert()); + $arr['commented'] = datetime_convert(); + $arr['received'] = datetime_convert(); $arr['changed'] = datetime_convert(); $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : ''); $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : ''); @@ -608,7 +732,13 @@ function item_store($arr,$force_parent = false) { $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : ''); $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : ''); $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 ); + $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 ); $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : ''); + $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : ''); + $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : ''); + $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : ''); + $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 ); + $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid()); if($arr['parent-uri'] === $arr['uri']) { $parent_id = 0; @@ -622,7 +752,7 @@ function item_store($arr,$force_parent = false) { // find the parent and snarf the item id and ACL's // and anything else we need to inherit - $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1", dbesc($arr['parent-uri']), intval($arr['uid']) ); @@ -636,6 +766,14 @@ function item_store($arr,$force_parent = false) { if($r[0]['uri'] != $r[0]['parent-uri']) { $arr['thr-parent'] = $arr['parent-uri']; $arr['parent-uri'] = $r[0]['parent-uri']; + $z = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d + ORDER BY `id` ASC LIMIT 1", + dbesc($r[0]['parent-uri']), + dbesc($r[0]['parent-uri']), + intval($arr['uid']) + ); + if($z && count($z)) + $r = $z; } $parent_id = $r[0]['id']; @@ -656,6 +794,7 @@ function item_store($arr,$force_parent = false) { $parent_id = 0; $arr['thr-parent'] = $arr['parent-uri']; $arr['parent-uri'] = $arr['uri']; + $arr['gravity'] = 0; } else { logger('item_store: item parent was not found - ignoring item'); @@ -664,8 +803,22 @@ function item_store($arr,$force_parent = false) { } } + $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($arr['uri']), + intval($arr['uid']) + ); + if($r && count($r)) { + logger('item-store: duplicate item ignored. ' . print_r($arr,true)); + return 0; + } + call_hooks('post_remote',$arr); + if(x($arr,'cancel')) { + logger('item_store: post cancelled by plugin.'); + return 0; + } + dbesc_array($arr); logger('item_store: ' . print_r($arr,true), LOGGER_DATA); @@ -678,10 +831,11 @@ function item_store($arr,$force_parent = false) { // find the item we just created - $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ", $arr['uri'], // already dbesc'd intval($arr['uid']) ); + if(count($r)) { $current_post = $r[0]['id']; logger('item_store: created item ' . $current_post); @@ -690,6 +844,14 @@ function item_store($arr,$force_parent = false) { logger('item_store: could not locate created item'); return 0; } + if(count($r) > 1) { + logger('item_store: duplicated post occurred. Removing duplicates.'); + q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ", + $arr['uri'], + intval($arr['uid']), + intval($current_post) + ); + } if((! $parent_id) || ($arr['parent-uri'] === $arr['uri'])) $parent_id = $current_post; @@ -713,6 +875,24 @@ function item_store($arr,$force_parent = false) { intval($current_post) ); + // update the commented timestamp on the parent + + q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d LIMIT 1", + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($parent_id) + ); + + if($dsprsig) { + q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", + intval($current_post), + dbesc($dsprsig->signed_text), + dbesc($dsprsig->signature), + dbesc($dsprsig->signer) + ); + } + + /** * If this is now the last-child, force all _other_ children of this parent to *not* be last-child */ @@ -725,6 +905,8 @@ function item_store($arr,$force_parent = false) { ); } + tag_deliver($arr['uid'],$current_post); + return $current_post; } @@ -741,12 +923,111 @@ function get_item_contact($item,$contacts) { } +function tag_deliver($uid,$item_id) { + + // look for mention tags and setup a second delivery chain for forum/community posts if appropriate + + $a = get_app(); + + $mention = false; + + $u = q("select uid, nickname, language, username, email, `page-flags`, `notify-flags` from user where uid = %d limit 1", + intval($uid) + ); + if(! count($u)) + return; + + $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false); + + $i = q("select * from item where id = %d and uid = %d limit 1", + intval($item_id), + intval($uid) + ); + if(! count($i)) + return; + + $item = $i[0]; + + $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']); + + // Diaspora uses their own hardwired link URL in @-tags + // instead of the one we supply with webfinger + + $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']); + + $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) { + $mention = true; + logger('tag_deliver: mention found: ' . $mtch[2]); + } + } + } + + if(! $mention) + return; + + // send a notification + + require_once('include/enotify.php'); + notification(array( + 'type' => NOTIFY_TAGSELF, + 'notify_flags' => $u[0]['notify-flags'], + 'language' => $u[0]['language'], + 'to_name' => $u[0]['username'], + 'to_email' => $u[0]['email'], + 'uid' => $u[0]['uid'], + 'item' => $item, + 'link' => $a->get_baseurl() . '/display/' . $u[0]['nickname'] . '/' . $item['id'], + 'source_name' => $item['author-name'], + 'source_link' => $item['author-link'], + 'source_photo' => $item['author-avatar'], + 'verb' => ACTIVITY_TAG, + 'otype' => 'item' + )); + + if(! $community_page) + return; + + // tgroup delivery - setup a second delivery chain + // prevent delivery looping - only proceed + // if the message originated elsewhere and is a top-level post + + if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent'])) + return; + + // now change this copy of the post to a forum head message and deliver to all the tgroup members + + + $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1", + intval($u[0]['uid']) + ); + if(! count($c)) + return; + + q("update item set wall = 1, origin = 1, forum_mode = 1, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s' where id = %d limit 1", + dbesc($c[0]['name']), + dbesc($c[0]['url']), + dbesc($c[0]['thumb']), + intval($item_id) + ); + + proc_run('php','include/notifier.php','tgroup',$item_id); + +} + + + + + + function dfrn_deliver($owner,$contact,$atom, $dissolve = false) { $a = get_app(); - if((! strlen($contact['issued-id'])) && (! $contact['duplex']) && (! ($owner['page-flags'] == PAGE_COMMUNITY))) - return 3; +// if((! strlen($contact['issued-id'])) && (! $contact['duplex']) && (! ($owner['page-flags'] == PAGE_COMMUNITY))) +// return 3; $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']); @@ -772,7 +1053,7 @@ function dfrn_deliver($owner,$contact,$atom, $dissolve = false) { if(! $curl_stat) return(-1); // timed out - logger('dfrn_deliver: ' . $xml); + logger('dfrn_deliver: ' . $xml, LOGGER_DATA); if(! $xml) return 3; @@ -783,7 +1064,7 @@ function dfrn_deliver($owner,$contact,$atom, $dissolve = false) { return 3; } - $res = simplexml_load_string($xml); + $res = parse_xml_string($xml); if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id))) return (($res->status) ? $res->status : 3); @@ -797,7 +1078,9 @@ function dfrn_deliver($owner,$contact,$atom, $dissolve = false) { $final_dfrn_id = ''; - if(($contact['duplex'] && strlen($contact['pubkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) { + if(($contact['duplex'] && strlen($contact['pubkey'])) + || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey'])) + || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) { openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']); openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']); } @@ -822,25 +1105,28 @@ function dfrn_deliver($owner,$contact,$atom, $dissolve = false) { if($dissolve) $postvars['dissolve'] = '1'; - if(($contact['rel']) && ($contact['rel'] != REL_FAN) && (! $contact['blocked']) && (! $contact['readonly'])) { - $postvars['data'] = $atom; - } - elseif($owner['page-flags'] == PAGE_COMMUNITY) { + + if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) { $postvars['data'] = $atom; + $postvars['perm'] = 'rw'; } else { $postvars['data'] = str_replace('1','0',$atom); + $postvars['perm'] = 'r'; } if($rino && $rino_allowed && (! $dissolve)) { $key = substr(random_string(),0,16); $data = bin2hex(aes_encrypt($postvars['data'],$key)); $postvars['data'] = $data; - logger('rino: sent key = ' . $key); + logger('rino: sent key = ' . $key, LOGGER_DEBUG); if($dfrn_version >= 2.1) { - if(($contact['duplex'] && strlen($contact['pubkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) { + if(($contact['duplex'] && strlen($contact['pubkey'])) + || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey'])) + || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) { + openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']); } else { @@ -871,6 +1157,8 @@ function dfrn_deliver($owner,$contact,$atom, $dissolve = false) { if((! $curl_stat) || (! strlen($xml))) return(-1); // timed out + if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after'))) + return(-1); if(strpos($xml,'status; - + return $res->status; } @@ -898,13 +1185,29 @@ function dfrn_deliver($owner,$contact,$atom, $dissolve = false) { * have a contact record. * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or * might not) try and subscribe to it. + * $datedir sorts in reverse order + * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been + * imported prior to its children being seen in the stream unless we are certain + * of how the feed is arranged/ordered. + * With $pass = 1, we only pull parent items out of the stream. + * With $pass = 2, we only pull children (comments/likes). * + * So running this twice, first with pass 1 and then with pass 2 will do the right + * thing regardless of feed ordering. This won't be adequate in a fully-threaded + * model where comments can have sub-threads. That would require some massive sorting + * to get all the feed items into a mostly linear ordering, and might still require + * recursion. */ -function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $secure_feed = false) { +function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) { - require_once('simplepie/simplepie.inc'); + require_once('library/simplepie/simplepie.inc'); + if(! strlen($xml)) { + logger('consume_feed: empty input'); + return; + } + $feed = new SimplePie(); $feed->set_raw_data($xml); if($datedir) @@ -916,6 +1219,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $secure_fee if($feed->error()) logger('consume_feed: Error parsing XML: ' . $feed->error()); + $permalink = $feed->get_permalink(); // Check at the feed level for updated contact name and/or photo @@ -930,7 +1234,9 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $secure_fee if(count($hubs)) $hub = implode(',', $hubs); - $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); + $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner'); + if(! $rawtags) + $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); if($rawtags) { $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) { @@ -979,13 +1285,13 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $secure_fee $img->scaleImageSquare(175); $hash = $resource_id; - $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), t('Contact Photos') , 4); + $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4); $img->scaleImage(80); - $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), t('Contact Photos') , 5); + $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5); $img->scaleImage(48); - $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), t('Contact Photos') , 6); + $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6); $a = get_app(); @@ -1061,7 +1367,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $secure_fee // process any deleted entries $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry'); - if(is_array($del_entries) && count($del_entries)) { + if(is_array($del_entries) && count($del_entries) && $pass != 2) { foreach($del_entries as $dentry) { $deleted = false; if(isset($dentry['attribs']['']['ref'])) { @@ -1075,7 +1381,8 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $secure_fee $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s'); } if($deleted && is_array($contact)) { - $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `contact-id` = %d LIMIT 1", + $r = q("SELECT `item`.*, `contact`.`self` FROM `item` left join `contact` on `item`.`contact-id` = `contact`.`id` + WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d LIMIT 1", dbesc($uri), intval($importer['uid']), intval($contact['id']) @@ -1086,6 +1393,41 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $secure_fee if(! $item['deleted']) logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG); + if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTVITY_OBJ_TAGTERM)) { + $xo = parse_xml_string($item['object'],false); + $xt = parse_xml_string($item['target'],false); + if($xt->type === ACTIVITY_OBJ_NOTE) { + $i = q("select * from `item` where uri = '%s' and uid = %d limit 1", + dbesc($xt->id), + intval($importer['importer_uid']) + ); + if(count($i)) { + + // For tags, the owner cannot remove the tag on the author's copy of the post. + + $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false); + $author_remove = (($item['origin'] && $item['self']) ? true : false); + $author_copy = (($item['origin']) ? true : false); + + if($owner_remove && $author_copy) + continue; + if($author_remove || $owner_remove) { + $tags = explode(',',$i[0]['tag']); + $newtags = array(); + if(count($tags)) { + foreach($tags as $tag) + if(trim($tag) !== trim($xo->body)) + $newtags[] = trim($tag); + } + q("update item set tag = '%s' where id = %d limit 1", + dbesc(implode(',',$newtags)), + intval($i[0]['id']) + ); + } + } + } + } + if($item['uri'] == $item['parent-uri']) { $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '', `title` = '' @@ -1113,7 +1455,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $secure_fee intval($item['uid']) ); // who is the last child now? - $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d + $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d ORDER BY `created` DESC LIMIT 1", dbesc($item['parent-uri']), intval($importer['uid']) @@ -1155,11 +1497,22 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $secure_fee if(($is_reply) && is_array($contact)) { + if($pass == 1) + continue; + // Have we seen it? If not, import it. $item_id = $item->get_id(); $datarray = get_atom_elements($feed,$item); + if(! x($datarray,'author-name')) + $datarray['author-name'] = $contact['name']; + if(! x($datarray,'author-link')) + $datarray['author-link'] = $contact['url']; + if(! x($datarray,'author-avatar')) + $datarray['author-avatar'] = $contact['thumb']; + + $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($item_id), intval($importer['uid']) @@ -1169,7 +1522,8 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $secure_fee if(count($r)) { if((x($datarray,'edited') !== false) && (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) { - $r = q("UPDATE `item` SET `body` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($datarray['title']), dbesc($datarray['body']), dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), dbesc($item_id), @@ -1197,7 +1551,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $secure_fee } $force_parent = false; - if($contact['network'] === 'stat') { + if($contact['network'] === NETWORK_OSTATUS) { $force_parent = true; if(strlen($datarray['title'])) unset($datarray['title']); @@ -1209,7 +1563,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $secure_fee $datarray['last-child'] = 1; } - if(($contact['network'] === 'feed') || (! strlen($contact['notify']))) { + if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) { // one way feed - no remote comment ability $datarray['last-child'] = 0; } @@ -1221,6 +1575,31 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $secure_fee $datarray['gravity'] = GRAVITY_LIKE; } + if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) { + $xo = parse_xml_string($datarray['object'],false); + $xt = parse_xml_string($datarray['target'],false); + + if($xt->type == ACTIVITY_OBJ_NOTE) { + $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1", + dbesc($xt->id), + intval($importer['importer_uid']) + ); + if(! count($r)) + continue; + + // extract tag, if not duplicate, add to parent item + if($xo->id && $xo->content) { + $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]'; + if(! (stristr($r[0]['tag'],$newtag))) { + q("UPDATE item SET tag = '%s' WHERE id = %d LIMIT 1", + dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag), + intval($r[0]['id']) + ); + } + } + } + } + $r = item_store($datarray,$force_parent); continue; } @@ -1230,8 +1609,41 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $secure_fee // Head post of a conversation. Have we seen it? If not, import it. $item_id = $item->get_id(); + $datarray = get_atom_elements($feed,$item); + if(is_array($contact)) { + if(! x($datarray,'author-name')) + $datarray['author-name'] = $contact['name']; + if(! x($datarray,'author-link')) + $datarray['author-link'] = $contact['url']; + if(! x($datarray,'author-avatar')) + $datarray['author-avatar'] = $contact['thumb']; + } + + // special handling for events + + if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) { + $ev = bbtoevent($datarray['body']); + if(x($ev,'desc') && x($ev,'start')) { + $ev['uid'] = $importer['uid']; + $ev['uri'] = $item_id; + $ev['edited'] = $datarray['edited']; + $ev['private'] = $datarray['private']; + + if(is_array($contact)) + $ev['cid'] = $contact['id']; + $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($item_id), + intval($importer['uid']) + ); + if(count($r)) + $ev['id'] = $r[0]['id']; + $xyz = event_store($ev); + continue; + } + } + $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($item_id), intval($importer['uid']) @@ -1241,7 +1653,8 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $secure_fee if(count($r)) { if((x($datarray,'edited') !== false) && (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) { - $r = q("UPDATE `item` SET `body` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($datarray['title']), dbesc($datarray['body']), dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), dbesc($item_id), @@ -1272,20 +1685,38 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $secure_fee lose_follower($importer,$contact,$datarray,$item); return; } + + if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) { + logger('consume-feed: New friend request'); + new_follower($importer,$contact,$datarray,$item,true); + return; + } + if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) { + lose_sharer($importer,$contact,$datarray,$item); + return; + } + + if(! is_array($contact)) return; - if($contact['network'] === 'stat') { + if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) { if(strlen($datarray['title'])) unset($datarray['title']); $datarray['last-child'] = 1; } - if(($contact['network'] === 'feed') || (! strlen($contact['notify']))) { + if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) { // one way feed - no remote comment ability $datarray['last-child'] = 0; } + // This is my contact on another system, but it's really me. + // Turn this into a wall post. + + if($contact['remote_self']) + $datarray['wall'] = 1; + $datarray['parent-uri'] = $item_id; $datarray['uid'] = $importer['uid']; $datarray['contact-id'] = $contact['id']; @@ -1297,131 +1728,818 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $secure_fee } } -function new_follower($importer,$contact,$datarray,$item) { - $url = notags(trim($datarray['author-link'])); - $name = notags(trim($datarray['author-name'])); - $photo = notags(trim($datarray['author-avatar'])); +function local_delivery($importer,$data) { - $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor'); - if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data']) - $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data']; + $a = get_app(); - if(is_array($contact)) { - if($contact['network'] == 'stat' && $contact['rel'] == REL_FAN) { - $r = q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d AND `uid` = %d LIMIT 1", - intval(REL_BUD), - intval($contact['id']), - intval($importer['uid']) - ); - } + if($importer['readonly']) { + // We aren't receiving stuff from this person. But we will quietly ignore them + // rather than a blatant "go away" message. + logger('local_delivery: ignoring'); + return 0; + //NOTREACHED + } + + // Consume notification feed. This may differ from consuming a public feed in several ways + // - might contain email or friend suggestions + // - might contain remote followup to our message + // - in which case we need to accept it and then notify other conversants + // - we may need to send various email notifications + + $feed = new SimplePie(); + $feed->set_raw_data($data); + $feed->enable_order_by_date(false); + $feed->init(); + + $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' ); + if(isset($reloc[0]['child'][NAMESPACE_DFRN])) { + $base = $reloc[0]['child'][NAMESPACE_DFRN]; + $newloc = array(); + $newloc['uid'] = $importer['importer_uid']; + $newloc['cid'] = $importer['id']; + $newloc['name'] = notags(unxmlify($base['name'][0]['data'])); + $newloc['photo'] = notags(unxmlify($base['photo'][0]['data'])); + $newloc['url'] = notags(unxmlify($base['url'][0]['data'])); + $newloc['request'] = notags(unxmlify($base['request'][0]['data'])); + $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data'])); + $newloc['notify'] = notags(unxmlify($base['notify'][0]['data'])); + $newloc['poll'] = notags(unxmlify($base['poll'][0]['data'])); + $newloc['site-pubkey'] = notags(unxmlify($base['site-pubkey'][0]['data'])); + $newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data'])); + $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data'])); + + // TODO + // merge with current record, current contents have priority + // update record, set url-updated + // update profile photos + // schedule a scan? - // send email notification to owner? } - else { - - // create contact record - set to readonly - $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `name`, `nick`, `photo`, `network`, `rel`, - `blocked`, `readonly`, `pending` ) - VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 1, 1 ) ", - intval($importer['uid']), - dbesc(datetime_convert()), - dbesc($url), - dbesc($name), - dbesc($nick), - dbesc($photo), - dbesc('stat'), - intval(REL_VIP) - ); - $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 AND `rel` = %d LIMIT 1", - intval($importer['uid']), - dbesc($url), - intval(REL_VIP) + // handle friend suggestion notification + + $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' ); + if(isset($sugg[0]['child'][NAMESPACE_DFRN])) { + $base = $sugg[0]['child'][NAMESPACE_DFRN]; + $fsugg = array(); + $fsugg['uid'] = $importer['importer_uid']; + $fsugg['cid'] = $importer['id']; + $fsugg['name'] = notags(unxmlify($base['name'][0]['data'])); + $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data'])); + $fsugg['url'] = notags(unxmlify($base['url'][0]['data'])); + $fsugg['request'] = notags(unxmlify($base['request'][0]['data'])); + $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data'])); + + // Does our member already have a friend matching this description? + + $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1", + dbesc($fsugg['name']), + dbesc(normalise_link($fsugg['url'])), + intval($fsugg['uid']) ); if(count($r)) - $contact_record = $r[0]; + return 0; - // create notification - $hash = random_string(); + // Do we already have an fcontact record for this person? - if(is_array($contact_record)) { - $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`) - VALUES ( %d, %d, 0, 0, '%s', '%s' )", - intval($importer['uid']), - intval($contact_record['id']), - dbesc($hash), - dbesc(datetime_convert()) - ); + $fid = 0; + $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", + dbesc($fsugg['url']), + dbesc($fsugg['name']), + dbesc($fsugg['request']) + ); + if(count($r)) { + $fid = $r[0]['id']; } - $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", - intval($importer['uid']) + if(! $fid) + $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ", + dbesc($fsugg['name']), + dbesc($fsugg['url']), + dbesc($fsugg['photo']), + dbesc($fsugg['request']) + ); + $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", + dbesc($fsugg['url']), + dbesc($fsugg['name']), + dbesc($fsugg['request']) ); - $a = get_app(); if(count($r)) { - if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) { - $email_tpl = load_view_file('view/follow_notify_eml.tpl'); - $email = replace_macros($email_tpl, array( - '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')), - '$url' => $url, - '$myname' => $r[0]['username'], - '$siteurl' => $a->get_baseurl(), - '$sitename' => $a->config['sitename'] - )); - $res = mail($r[0]['email'], - t("You have a new follower at ") . $a->config['sitename'], - $email, - 'From: ' . t('Administrator') . '@' . $_SERVER['SERVER_NAME'] ); - - } + $fid = $r[0]['id']; } - } -} - -function lose_follower($importer,$contact,$datarray,$item) { + // database record did not get created. Quietly give up. + else + return 0; - if(($contact['rel'] == REL_BUD) || ($contact['rel'] == REL_FAN)) { - q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d LIMIT 1", - intval(REL_FAN), - intval($contact['id']) + $hash = random_string(); + + $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` ) + VALUES( %d, %d, %d, '%s', '%s', '%s', %d )", + intval($fsugg['uid']), + intval($fid), + intval($fsugg['cid']), + dbesc($fsugg['body']), + dbesc($hash), + dbesc(datetime_convert()), + intval(0) ); - } - else { - contact_remove($contact['id']); - } -} - -function subscribe_to_hub($url,$importer,$contact) { + notification(array( + 'type' => NOTIFY_SUGGEST, + 'notify_flags' => $importer['notify-flags'], + 'language' => $importer['language'], + 'to_name' => $importer['username'], + 'to_email' => $importer['email'], + 'uid' => $importer['importer_uid'], + 'item' => $fsugg, + 'link' => $a->get_baseurl() . '/notifications/intros', + 'source_name' => $importer['name'], + 'source_link' => $importer['url'], + 'source_photo' => $importer['photo'], + 'verb' => ACTIVITY_REQ_FRIEND, + 'otype' => 'intro' + )); - if(is_array($importer)) { - $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1", - intval($importer['uid']) - ); + return 0; } - if(! count($r)) - return; - - $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id']; - - // Use a single verify token, even if multiple hubs - - $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string()); - - $params= 'hub.mode=subscribe&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token; - logger('subscribe_to_hub: subscribing ' . $contact['name'] . ' to hub ' . $url . ' with verifier ' . $verify_token); - - if(! strlen($contact['hub-verify'])) { - $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d LIMIT 1", - dbesc($verify_token), - intval($contact['id']) + $ismail = false; + + $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' ); + if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) { + + logger('local_delivery: private message received'); + + $ismail = true; + $base = $rawmail[0]['child'][NAMESPACE_DFRN]; + + $msg = array(); + $msg['uid'] = $importer['importer_uid']; + $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data'])); + $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data'])); + $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])); + $msg['contact-id'] = $importer['id']; + $msg['title'] = notags(unxmlify($base['subject'][0]['data'])); + $msg['body'] = escape_tags(unxmlify($base['content'][0]['data'])); + $msg['seen'] = 0; + $msg['replied'] = 0; + $msg['uri'] = notags(unxmlify($base['id'][0]['data'])); + $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data'])); + $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data']))); + + dbesc_array($msg); + + $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg)) + . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" ); + + // send notifications. + + require_once('include/enotify.php'); + + $notif_params = array( + 'type' => NOTIFY_MAIL, + 'notify_flags' => $importer['notify-flags'], + 'language' => $importer['language'], + 'to_name' => $importer['username'], + 'to_email' => $importer['email'], + 'uid' => $importer['importer_uid'], + 'item' => $msg, + 'source_name' => $msg['from-name'], + 'source_link' => $importer['url'], + 'source_photo' => $importer['thumb'], + 'verb' => ACTIVITY_POST, + 'otype' => 'mail' ); - } + + notification($notif_params); + return 0; - post_url($url,$params); - return; + // NOTREACHED + } + + logger('local_delivery: feed item count = ' . $feed->get_item_quantity()); -} + // process any deleted entries + + $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry'); + if(is_array($del_entries) && count($del_entries)) { + foreach($del_entries as $dentry) { + $deleted = false; + if(isset($dentry['attribs']['']['ref'])) { + $uri = $dentry['attribs']['']['ref']; + $deleted = true; + if(isset($dentry['attribs']['']['when'])) { + $when = $dentry['attribs']['']['when']; + $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s'); + } + else + $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s'); + } + if($deleted) { + + $r = q("SELECT `item`.*, `contact`.`self` FROM `item` left join contact on `item`.`contact-id` = `contact`.`id` + WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d LIMIT 1", + dbesc($uri), + intval($importer['importer_uid']), + intval($importer['id']) + ); + + if(count($r)) { + $item = $r[0]; + + if($item['deleted']) + continue; + + logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG); + + if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTVITY_OBJ_TAGTERM)) { + $xo = parse_xml_string($item['object'],false); + $xt = parse_xml_string($item['target'],false); + if($xt->type === ACTIVITY_OBJ_NOTE) { + $i = q("select * from `item` where uri = '%s' and uid = %d limit 1", + dbesc($xt->id), + intval($importer['importer_uid']) + ); + if(count($i)) { + + // For tags, the owner cannot remove the tag on the author's copy of the post. + + $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false); + $author_remove = (($item['origin'] && $item['self']) ? true : false); + $author_copy = (($item['origin']) ? true : false); + + if($owner_remove && $author_copy) + continue; + if($author_remove || $owner_remove) { + $tags = explode(',',$i[0]['tag']); + $newtags = array(); + if(count($tags)) { + foreach($tags as $tag) + if(trim($tag) !== trim($xo->body)) + $newtags[] = trim($tag); + } + q("update item set tag = '%s' where id = %d limit 1", + dbesc(implode(',',$newtags)), + intval($i[0]['id']) + ); + } + } + } + } + + if($item['uri'] == $item['parent-uri']) { + $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s' + WHERE `parent-uri` = '%s' AND `uid` = %d", + dbesc($when), + dbesc(datetime_convert()), + dbesc($item['uri']), + intval($importer['importer_uid']) + ); + } + else { + $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s' + WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($when), + dbesc(datetime_convert()), + dbesc($uri), + intval($importer['importer_uid']) + ); + if($item['last-child']) { + // ensure that last-child is set in case the comment that had it just got wiped. + q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ", + dbesc(datetime_convert()), + dbesc($item['parent-uri']), + intval($item['uid']) + ); + // who is the last child now? + $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d + ORDER BY `created` DESC LIMIT 1", + dbesc($item['parent-uri']), + intval($importer['importer_uid']) + ); + if(count($r)) { + q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d LIMIT 1", + intval($r[0]['id']) + ); + } + } + } + } + } + } + } + + + foreach($feed->get_items() as $item) { + + $is_reply = false; + $item_id = $item->get_id(); + $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to'); + if(isset($rawthread[0]['attribs']['']['ref'])) { + $is_reply = true; + $parent_uri = $rawthread[0]['attribs']['']['ref']; + } + + if($is_reply) { + + $community = false; + + if($importer['page-flags'] == PAGE_COMMUNITY) { + $sql_extra = ''; + $community = true; + logger('local_delivery: community reply'); + } + else + $sql_extra = " and contact.self = 1 and item.wall = 1 "; + + // was the top-level post for this reply written by somebody on this site? + // Specifically, the recipient? + + $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, + `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item` + LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` + WHERE `item`.`uri` = '%s' AND `item`.`parent-uri` = '%s' + AND `item`.`uid` = %d + $sql_extra + LIMIT 1", + dbesc($parent_uri), + dbesc($parent_uri), + intval($importer['importer_uid']) + ); + + if($r && count($r)) { + + logger('local_delivery: received remote comment'); + $is_like = false; + // remote reply to our post. Import and then notify everybody else. + $datarray = get_atom_elements($feed,$item); + + + // TODO: make this next part work against both delivery threads of a community post + +// if((! link_compare($datarray['author-link'],$importer['url'])) && (! $community)) { +// logger('local_delivery: received relay claiming to be from ' . $importer['url'] . ' however comment author url is ' . $datarray['author-link'] ); + // they won't know what to do so don't report an error. Just quietly die. +// return 0; +// } + + $datarray['type'] = 'remote-comment'; + $datarray['wall'] = 1; + $datarray['parent-uri'] = $parent_uri; + $datarray['uid'] = $importer['importer_uid']; + $datarray['owner-name'] = $r[0]['name']; + $datarray['owner-link'] = $r[0]['url']; + $datarray['owner-avatar'] = $r[0]['thumb']; + $datarray['contact-id'] = $importer['id']; + if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) { + $is_like = true; + $datarray['type'] = 'activity'; + $datarray['gravity'] = GRAVITY_LIKE; + $datarray['last-child'] = 0; + } + + if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) { + + + $xo = parse_xml_string($datarray['object'],false); + $xt = parse_xml_string($datarray['target'],false); + + if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id == $r[0]['uri'])) { + + // extract tag, if not duplicate, and this user allows tags, add to parent item + + if($xo->id && $xo->content) { + $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]'; + + if(! (stristr($r[0]['tag'],$newtag))) { + $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1", + intval($importer['importer_uid']) + ); + if(count($i) && ! ($i[0]['blocktags'])) { + q("UPDATE item SET tag = '%s' WHERE id = %d LIMIT 1", + dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag), + intval($r[0]['id']) + ); + } + } + } + } + } + +// if($community) { +// $newtag = '@[url=' . $a->get_baseurl() . '/profile/' . $importer['nickname'] . ']' . $importer['username'] . '[/url]'; +// if(! stristr($datarray['tag'],$newtag)) { +// if(strlen($datarray['tag'])) +// $datarray['tag'] .= ','; +// $datarray['tag'] .= $newtag; +// } +// } + + + $posted_id = item_store($datarray); + $parent = 0; + + if($posted_id) { + $r = q("SELECT `parent` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval($posted_id), + intval($importer['importer_uid']) + ); + if(count($r)) + $parent = $r[0]['parent']; + + if(! $is_like) { + $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d", + dbesc(datetime_convert()), + intval($importer['importer_uid']), + intval($r[0]['parent']) + ); + + $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1", + dbesc(datetime_convert()), + intval($importer['importer_uid']), + intval($posted_id) + ); + } + + if($posted_id && $parent) { + + proc_run('php',"include/notifier.php","comment-import","$posted_id"); + + if((! $is_like) && (! $importer['self'])) { + + require_once('include/enotify.php'); + + notification(array( + 'type' => NOTIFY_COMMENT, + 'notify_flags' => $importer['notify-flags'], + 'language' => $importer['language'], + 'to_name' => $importer['username'], + 'to_email' => $importer['email'], + 'uid' => $importer['importer_uid'], + 'item' => $datarray, + 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id, + 'source_name' => stripslashes($datarray['author-name']), + 'source_link' => $datarray['author-link'], + 'source_photo' => ((link_compare($datarray['author-link'],$importer['url'])) + ? $importer['thumb'] : $datarray['author-avatar']), + 'verb' => ACTIVITY_POST, + 'otype' => 'item' + + )); + + } + } + return 0; + // NOTREACHED + } + } + else { + + // regular comment that is part of this total conversation. Have we seen it? If not, import it. + + $item_id = $item->get_id(); + $datarray = get_atom_elements($feed,$item); + + $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($item_id), + intval($importer['importer_uid']) + ); + + // Update content if 'updated' changes + + if(count($r)) { + if((x($datarray,'edited') !== false) && (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) { + $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($datarray['title']), + dbesc($datarray['body']), + dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), + dbesc($item_id), + intval($importer['importer_uid']) + ); + } + + // update last-child if it changes + + $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow'); + if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) { + $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d", + dbesc(datetime_convert()), + dbesc($parent_uri), + intval($importer['importer_uid']) + ); + $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + intval($allow[0]['data']), + dbesc(datetime_convert()), + dbesc($item_id), + intval($importer['importer_uid']) + ); + } + continue; + } + + $datarray['parent-uri'] = $parent_uri; + $datarray['uid'] = $importer['importer_uid']; + $datarray['contact-id'] = $importer['id']; + if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) { + $datarray['type'] = 'activity'; + $datarray['gravity'] = GRAVITY_LIKE; + } + + if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) { + + $xo = parse_xml_string($datarray['object'],false); + $xt = parse_xml_string($datarray['target'],false); + + if($xt->type == ACTIVITY_OBJ_NOTE) { + $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1", + dbesc($xt->id), + intval($importer['importer_uid']) + ); + if(! count($r)) + continue; + + // extract tag, if not duplicate, add to parent item + if($xo->content) { + if(! (stristr($r[0]['tag'],trim($xo->content)))) { + q("UPDATE item SET tag = '%s' WHERE id = %d LIMIT 1", + dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'), + intval($r[0]['id']) + ); + } + } + } + } + + $posted_id = item_store($datarray); + + // find out if our user is involved in this conversation and wants to be notified. + + if($datarray['type'] != 'activity') { + + $myconv = q("SELECT `author-link`, `author-avatar` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 ", + dbesc($parent_uri), + intval($importer['importer_uid']) + ); + + if(count($myconv)) { + $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname']; + foreach($myconv as $conv) { + + if(! link_compare($conv['author-link'],$importer_url)) + continue; + + require_once('include/enotify.php'); + + notification(array( + 'type' => NOTIFY_COMMENT, + 'notify_flags' => $importer['notify-flags'], + 'language' => $importer['language'], + 'to_name' => $importer['username'], + 'to_email' => $importer['email'], + 'uid' => $importer['importer_uid'], + 'item' => $datarray, + 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id, + 'source_name' => stripslashes($datarray['author-name']), + 'source_link' => $datarray['author-link'], + 'source_photo' => ((link_compare($datarray['author-link'],$importer['url'])) + ? $importer['thumb'] : $datarray['author-avatar']), + 'verb' => ACTIVITY_POST, + 'otype' => 'item' + + )); + + break; + } + } + } + continue; + } + } + + else { + + // Head post of a conversation. Have we seen it? If not, import it. + + + $item_id = $item->get_id(); + $datarray = get_atom_elements($feed,$item); + + if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) { + $ev = bbtoevent($datarray['body']); + if(x($ev,'desc') && x($ev,'start')) { + $ev['cid'] = $importer['id']; + $ev['uid'] = $importer['uid']; + $ev['uri'] = $item_id; + $ev['edited'] = $datarray['edited']; + $ev['private'] = $datarray['private']; + + $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($item_id), + intval($importer['uid']) + ); + if(count($r)) + $ev['id'] = $r[0]['id']; + $xyz = event_store($ev); + continue; + } + } + + $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($item_id), + intval($importer['importer_uid']) + ); + + // Update content if 'updated' changes + + if(count($r)) { + if((x($datarray,'edited') !== false) && (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) { + $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($datarray['title']), + dbesc($datarray['body']), + dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), + dbesc($item_id), + intval($importer['importer_uid']) + ); + } + + // update last-child if it changes + + $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow'); + if($allow && $allow[0]['data'] != $r[0]['last-child']) { + $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + intval($allow[0]['data']), + dbesc(datetime_convert()), + dbesc($item_id), + intval($importer['importer_uid']) + ); + } + continue; + } + + // This is my contact on another system, but it's really me. + // Turn this into a wall post. + + if($contact['remote_self']) + $datarray['wall'] = 1; + + $datarray['parent-uri'] = $item_id; + $datarray['uid'] = $importer['importer_uid']; + $datarray['contact-id'] = $importer['id']; + $r = item_store($datarray); + continue; + } + } + + return 0; + // NOTREACHED + +} + + +function new_follower($importer,$contact,$datarray,$item,$sharing = false) { + $url = notags(trim($datarray['author-link'])); + $name = notags(trim($datarray['author-name'])); + $photo = notags(trim($datarray['author-avatar'])); + + $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor'); + if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data']) + $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data']; + + if(is_array($contact)) { + if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING) + || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) { + $r = q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval(CONTACT_IS_FRIEND), + intval($contact['id']), + intval($importer['uid']) + ); + } + // send email notification to owner? + } + else { + + // create contact record + + $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`, + `blocked`, `readonly`, `pending`, `writable` ) + VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ", + intval($importer['uid']), + dbesc(datetime_convert()), + dbesc($url), + dbesc(normalise_link($url)), + dbesc($name), + dbesc($nick), + dbesc($photo), + dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS), + intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER) + ); + $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1", + intval($importer['uid']), + dbesc($url) + ); + if(count($r)) + $contact_record = $r[0]; + + // create notification + $hash = random_string(); + + if(is_array($contact_record)) { + $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`) + VALUES ( %d, %d, 0, 0, '%s', '%s' )", + intval($importer['uid']), + intval($contact_record['id']), + dbesc($hash), + dbesc(datetime_convert()) + ); + } + $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", + intval($importer['uid']) + ); + $a = get_app(); + if(count($r)) { + if(($r[0]['notify-flags'] & NOTIFY_INTRO) && ($r[0]['page-flags'] == PAGE_NORMAL)) { + $email_tpl = get_intltext_template('follow_notify_eml.tpl'); + $email = replace_macros($email_tpl, array( + '$requestor' => ((strlen($name)) ? $name : t('[Name Withheld]')), + '$url' => $url, + '$myname' => $r[0]['username'], + '$siteurl' => $a->get_baseurl(), + '$sitename' => $a->config['sitename'] + )); + $res = mail($r[0]['email'], + (($sharing) ? t('A new person is sharing with you at ') : t("You have a new follower at ")) . $a->config['sitename'], + $email, + 'From: ' . t('Administrator') . '@' . $_SERVER['SERVER_NAME'] . "\n" + . 'Content-type: text/plain; charset=UTF-8' . "\n" + . 'Content-transfer-encoding: 8bit' ); + + } + } + } +} + +function lose_follower($importer,$contact,$datarray,$item) { + + if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) { + q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d LIMIT 1", + intval(CONTACT_IS_SHARING), + intval($contact['id']) + ); + } + else { + contact_remove($contact['id']); + } +} + +function lose_sharer($importer,$contact,$datarray,$item) { + + if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) { + q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d LIMIT 1", + intval(CONTACT_IS_FOLLOWER), + intval($contact['id']) + ); + } + else { + contact_remove($contact['id']); + } +} + + +function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') { + + if(is_array($importer)) { + $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1", + intval($importer['uid']) + ); + } + + // Diaspora has different message-ids in feeds than they do + // through the direct Diaspora protocol. If we try and use + // the feed, we'll get duplicates. So don't. + + if((! count($r)) || $contact['network'] === NETWORK_DIASPORA) + return; + + $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id']; + + // Use a single verify token, even if multiple hubs + + $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string()); + + $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token; + + logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token); + + if(! strlen($contact['hub-verify'])) { + $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d LIMIT 1", + dbesc($verify_token), + intval($contact['id']) + ); + } + + post_url($url,$params); + return; + +} function atom_author($tag,$name,$uri,$h,$w,$photo) { @@ -1449,29 +2567,39 @@ function atom_author($tag,$name,$uri,$h,$w,$photo) { function atom_entry($item,$type,$author,$owner,$comment = false) { + $a = get_app(); + + if(! $item['parent']) + return; + if($item['deleted']) return '' . "\r\n"; - $a = get_app(); + + if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid']) + $body = fix_private_photos($item['body'],$owner['uid']); + else + $body = $item['body']; + $o = "\r\n\r\n\r\n"; if(is_array($author)) $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']); else - $o .= atom_author('author',$item['name'],$item['url'],80,80,$item['thumb']); + $o .= atom_author('author',(($item['author-name']) ? $item['author-name'] : $item['name']),(($item['author-link']) ? $item['author-link'] : $item['url']),80,80,(($item['author-avatar']) ? $item['author-avatar'] : $item['thumb'])); if(strlen($item['owner-name'])) $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']); - if($item['parent'] != $item['id']) - $o .= '' . "\r\n"; + if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri'])) + $o .= '' . "\r\n"; $o .= '' . xmlify($item['uri']) . '' . "\r\n"; $o .= '' . xmlify($item['title']) . '' . "\r\n"; $o .= '' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '' . "\r\n"; $o .= '' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '' . "\r\n"; - $o .= '' . base64url_encode($item['body'], true) . '' . "\r\n"; - $o .= '' . xmlify(($type === 'html') ? bbcode($item['body']) : $item['body']) . '' . "\r\n"; + $o .= '' . base64url_encode($body, true) . '' . "\r\n"; + $o .= '' . xmlify((($type === 'html') ? bbcode($body) : $body)) . '' . "\r\n"; $o .= '' . "\r\n"; if($comment) $o .= '' . intval($item['last-child']) . '' . "\r\n"; @@ -1487,6 +2615,22 @@ function atom_entry($item,$type,$author,$owner,$comment = false) { if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])) $o .= '1' . "\r\n"; + if($item['extid']) + $o .= '' . xmlify($item['extid']) . '' . "\r\n"; + if($item['bookmark']) + $o .= 'true' . "\r\n"; + + if($item['app']) + $o .= '' . "\r\n"; + + if($item['guid']) + $o .= '' . $item['guid'] . '' . "\r\n"; + + if($item['signed_text']) { + $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer']))); + $o .= '' . xmlify($sign) . '' . "\r\n"; + } + $verb = construct_verb($item); $o .= '' . xmlify($verb) . '' . "\r\n"; $actobj = construct_activity_object($item); @@ -1496,6 +2640,15 @@ function atom_entry($item,$type,$author,$owner,$comment = false) { if(strlen($actarg)) $o .= $actarg; + $tags = item_getfeedtags($item); + if(count($tags)) { + foreach($tags as $t) { + $o .= '' . "\r\n"; + } + } + + $o .= item_getfeedattach($item); + $mentioned = get_mentions($item); if($mentioned) $o .= $mentioned; @@ -1506,6 +2659,81 @@ function atom_entry($item,$type,$author,$owner,$comment = false) { return $o; } + +function fix_private_photos($s,$uid) { + $a = get_app(); + logger('fix_private_photos'); + + if(preg_match("/\[img\](.*?)\[\/img\]/is",$s,$matches)) { + $image = $matches[1]; + logger('fix_private_photos: found photo ' . $image); + if(stristr($image ,$a->get_baseurl() . '/photo/')) { + $i = basename($image); + $i = str_replace('.jpg','',$i); + $x = strpos($i,'-'); + if($x) { + $res = substr($i,$x+1); + $i = substr($i,0,$x); + $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d", + dbesc($i), + intval($res), + intval($uid) + ); + if(count($r)) { + logger('replacing photo'); + $s = str_replace($image, 'data:image/jpg;base64,' . base64_encode($r[0]['data']), $s); + } + } + logger('fix_private_photos: replaced: ' . $s, LOGGER_DATA); + } + } + return($s); +} + + + +function item_getfeedtags($item) { + $ret = array(); + $matches = false; + $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches); + if($cnt) { + for($x = 0; $x < $cnt; $x ++) { + if($matches[1][$x]) + $ret[] = array('#',$matches[1][$x], $matches[2][$x]); + } + } + $matches = false; + $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches); + if($cnt) { + for($x = 0; $x < $cnt; $x ++) { + if($matches[1][$x]) + $ret[] = array('@',$matches[1][$x], $matches[2][$x]); + } + } + return $ret; +} + +function item_getfeedattach($item) { + $ret = ''; + $arr = explode(',',$item['attach']); + if(count($arr)) { + foreach($arr as $r) { + $matches = false; + $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"\[\/attach\]|',$r,$matches); + if($cnt) { + $ret .= 'get_baseurl() . '/' . $_SESSION['return_url']); + } + + $item = $r[0]; + + $owner = $item['uid']; + + // check if logged in user is either the author or owner of this item + + if((local_user() == $item['uid']) || (remote_user() == $item['contact-id'])) { + + // delete the item + + $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d LIMIT 1", + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($item['id']) + ); + + // If item is a link to a photo resource, nuke all the associated photos + // (visitors will not have photo resources) + // This only applies to photos uploaded from the photos page. Photos inserted into a post do not + // generate a resource-id and therefore aren't intimately linked to the item. + + if(strlen($item['resource-id'])) { + q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ", + dbesc($item['resource-id']), + intval($item['uid']) + ); + // ignore the result + } + + // If item is a link to an event, nuke the event record. + + if(intval($item['event-id'])) { + q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval($item['event-id']), + intval($item['uid']) + ); + // ignore the result + } + + + // If it's the parent of a comment thread, kill all the kids + + if($item['uri'] == $item['parent-uri']) { + $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' + WHERE `parent-uri` = '%s' AND `uid` = %d ", + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc($item['parent-uri']), + intval($item['uid']) + ); + // ignore the result + } + else { + // ensure that last-child is set in case the comment that had it just got wiped. + q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ", + dbesc(datetime_convert()), + dbesc($item['parent-uri']), + intval($item['uid']) + ); + // who is the last child now? + $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d ORDER BY `edited` DESC LIMIT 1", + dbesc($item['parent-uri']), + intval($item['uid']) + ); + if(count($r)) { + q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d LIMIT 1", + intval($r[0]['id']) + ); + } + } + $drop_id = intval($item['id']); + + // send the notification upstream/downstream as the case may be + + if(! $interactive) + return $owner; + + proc_run('php',"include/notifier.php","drop","$drop_id"); + goaway($a->get_baseurl() . '/' . $_SESSION['return_url']); + //NOTREACHED + } + else { + if(! $interactive) + return 0; + notice( t('Permission denied.') . EOL); + goaway($a->get_baseurl() . '/' . $_SESSION['return_url']); + //NOTREACHED + } + +}