3 require_once('include/bbcode.php');
4 require_once('include/oembed.php');
5 require_once('include/salmon.php');
6 require_once('include/crypto.php');
7 require_once('include/Photo.php');
8 require_once('include/tags.php');
9 require_once('include/files.php');
10 require_once('include/text.php');
11 require_once('include/email.php');
12 require_once('include/threads.php');
13 require_once('include/socgraph.php');
14 require_once('include/plaintext.php');
15 require_once('include/ostatus.php');
16 require_once('mod/share.php');
18 require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
21 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0, $forpubsub = false) {
24 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
25 $public_feed = (($dfrn_id) ? false : true);
26 $starred = false; // not yet implemented, possible security issues
29 if($public_feed && $a->argc > 2) {
30 for($x = 2; $x < $a->argc; $x++) {
31 if($a->argv[$x] == 'converse')
33 if($a->argv[$x] == 'starred')
35 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
36 $category = $a->argv[$x+1];
42 // default permissions - anonymous user
44 $sql_extra = " AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' ";
46 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
47 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
48 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
56 $owner_id = $owner['user_uid'];
57 $owner_nick = $owner['nickname'];
59 $birthday = feed_birthday($owner_id,$owner['timezone']);
69 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
73 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
74 $my_id = '1:' . $dfrn_id;
77 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
78 $my_id = '0:' . $dfrn_id;
85 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
93 require_once('include/security.php');
94 $groups = init_groups_visitor($contact['id']);
97 for($x = 0; $x < count($groups); $x ++)
98 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
99 $gs = implode('|', $groups);
102 $gs = '<<>>' ; // Impossible to match
104 $sql_extra = sprintf("
105 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
106 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
107 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
108 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
110 intval($contact['id']),
111 intval($contact['id']),
122 // Include answers to status.net posts in pubsub feeds
124 $sql_post_table = "INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent`
125 LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid`";
126 $visibility = sprintf("AND (`item`.`parent` = `item`.`id`) OR (`item`.`network` = '%s' AND ((`thread`.`network`='%s') OR (`thritem`.`network` = '%s')))",
127 dbesc(NETWORK_DFRN), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS));
128 $date_field = "`received`";
129 $sql_order = "`item`.`received` DESC";
131 $date_field = "`changed`";
132 $sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
135 if(! strlen($last_update))
136 $last_update = 'now -30 days';
138 if(isset($category)) {
139 $sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
140 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
141 //$sql_extra .= file_tag_file_query('item',$category,'category');
146 $sql_extra .= " AND `contact`.`self` = 1 ";
149 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
151 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
152 // dbesc($check_date),
154 $r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
155 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
156 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
157 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
158 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
159 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
160 FROM `item` $sql_post_table
161 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
162 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
163 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
164 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
165 AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
167 ORDER BY $sql_order LIMIT 0, 300",
173 // Will check further below if this actually returned results.
174 // We will provide an empty feed if that is the case.
178 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
182 $hubxml = feed_hublinks();
184 $salmon = feed_salmonlinks($owner_nick);
186 $alternatelink = $owner['url'];
189 $alternatelink .= "/category/".$category;
191 $atom .= replace_macros($feed_template, array(
192 '$version' => xmlify(FRIENDICA_VERSION),
193 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
194 '$feed_title' => xmlify($owner['name']),
195 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
197 '$salmon' => $salmon,
198 '$alternatelink' => xmlify($alternatelink),
199 '$name' => xmlify($owner['name']),
200 '$profile_page' => xmlify($owner['url']),
201 '$photo' => xmlify($owner['photo']),
202 '$thumb' => xmlify($owner['thumb']),
203 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
204 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
205 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
206 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
207 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
210 call_hooks('atom_feed', $atom);
212 if(! count($items)) {
214 call_hooks('atom_feed_end', $atom);
216 $atom .= '</feed>' . "\r\n";
220 foreach($items as $item) {
222 // prevent private email from leaking.
223 if($item['network'] === NETWORK_MAIL)
226 // public feeds get html, our own nodes use bbcode
230 // catch any email that's in a public conversation and make sure it doesn't leak
238 $atom .= atom_entry($item,$type,null,$owner,true);
241 call_hooks('atom_feed_end', $atom);
243 $atom .= '</feed>' . "\r\n";
249 function construct_verb($item) {
251 return $item['verb'];
252 return ACTIVITY_POST;
255 function construct_activity_object($item) {
257 if($item['object']) {
258 $o = '<as:object>' . "\r\n";
259 $r = parse_xml_string($item['object'],false);
265 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
267 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
269 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
271 if(substr($r->link,0,1) === '<') {
272 // patch up some facebook "like" activity objects that got stored incorrectly
273 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
274 // we can probably remove this hack here and in the following function in a few months time.
275 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
276 $r->link = str_replace('&','&', $r->link);
277 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
281 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
284 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
285 $o .= '</as:object>' . "\r\n";
292 function construct_activity_target($item) {
294 if($item['target']) {
295 $o = '<as:target>' . "\r\n";
296 $r = parse_xml_string($item['target'],false);
300 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
302 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
304 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
306 if(substr($r->link,0,1) === '<') {
307 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
308 $r->link = str_replace('&','&', $r->link);
309 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
313 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
316 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
317 $o .= '</as:target>' . "\r\n";
326 * The purpose of this function is to apply system message length limits to
327 * imported messages without including any embedded photos in the length
329 if(! function_exists('limit_body_size')) {
330 function limit_body_size($body) {
332 // logger('limit_body_size: start', LOGGER_DEBUG);
334 $maxlen = get_max_import_size();
336 // If the length of the body, including the embedded images, is smaller
337 // than the maximum, then don't waste time looking for the images
338 if($maxlen && (strlen($body) > $maxlen)) {
340 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
347 $img_start = strpos($orig_body, '[img');
348 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
349 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
350 while(($img_st_close !== false) && ($img_end !== false)) {
352 $img_st_close++; // make it point to AFTER the closing bracket
353 $img_end += $img_start;
354 $img_end += strlen('[/img]');
356 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
357 // This is an embedded image
359 if( ($textlen + $img_start) > $maxlen ) {
360 if($textlen < $maxlen) {
361 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
362 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
367 $new_body = $new_body . substr($orig_body, 0, $img_start);
368 $textlen += $img_start;
371 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
375 if( ($textlen + $img_end) > $maxlen ) {
376 if($textlen < $maxlen) {
377 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
378 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
383 $new_body = $new_body . substr($orig_body, 0, $img_end);
384 $textlen += $img_end;
387 $orig_body = substr($orig_body, $img_end);
389 if($orig_body === false) // in case the body ends on a closing image tag
392 $img_start = strpos($orig_body, '[img');
393 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
394 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
397 if( ($textlen + strlen($orig_body)) > $maxlen) {
398 if($textlen < $maxlen) {
399 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
400 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
405 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
406 $new_body = $new_body . $orig_body;
407 $textlen += strlen($orig_body);
416 function title_is_body($title, $body) {
418 $title = strip_tags($title);
419 $title = trim($title);
420 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
421 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
423 $body = strip_tags($body);
425 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
426 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
428 if (strlen($title) < strlen($body))
429 $body = substr($body, 0, strlen($title));
431 if (($title != $body) and (substr($title, -3) == "...")) {
432 $pos = strrpos($title, "...");
434 $title = substr($title, 0, $pos);
435 $body = substr($body, 0, $pos);
439 return($title == $body);
444 function get_atom_elements($feed, $item, $contact = array()) {
446 require_once('library/HTMLPurifier.auto.php');
447 require_once('include/html2bbcode.php');
449 $best_photo = array();
453 $author = $item->get_author();
455 $res['author-name'] = unxmlify($author->get_name());
456 $res['author-link'] = unxmlify($author->get_link());
459 $res['author-name'] = unxmlify($feed->get_title());
460 $res['author-link'] = unxmlify($feed->get_permalink());
462 $res['uri'] = unxmlify($item->get_id());
463 $res['title'] = unxmlify($item->get_title());
464 $res['body'] = unxmlify($item->get_content());
465 $res['plink'] = unxmlify($item->get_link(0));
467 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
468 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
470 $res['body'] = nl2br($res['body']);
473 // removing the content of the title if its identically to the body
474 // This helps with auto generated titles e.g. from tumblr
475 if (title_is_body($res["title"], $res["body"]))
479 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
483 // look for a photo. We should check media size and find the best one,
484 // but for now let's just find any author photo
485 // Additionally we look for an alternate author link. On OStatus this one is the one we want.
487 $authorlinks = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
488 if (is_array($authorlinks)) {
489 foreach ($authorlinks as $link) {
490 $linkdata = array_shift($link["attribs"]);
492 if ($linkdata["rel"] == "alternate")
493 $res["author-link"] = $linkdata["href"];
497 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
499 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
500 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
501 foreach($base as $link) {
502 if($link['attribs']['']['rel'] === 'alternate')
503 $res['author-link'] = unxmlify($link['attribs']['']['href']);
505 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
506 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
507 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
512 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
514 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
515 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
516 if($base && count($base)) {
517 foreach($base as $link) {
518 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
519 $res['author-link'] = unxmlify($link['attribs']['']['href']);
520 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
521 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
522 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
528 // No photo/profile-link on the item - look at the feed level
530 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
531 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
532 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
533 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
534 foreach($base as $link) {
535 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
536 $res['author-link'] = unxmlify($link['attribs']['']['href']);
537 if(! $res['author-avatar']) {
538 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
539 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
544 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
546 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
547 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
549 if($base && count($base)) {
550 foreach($base as $link) {
551 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
552 $res['author-link'] = unxmlify($link['attribs']['']['href']);
553 if(! (x($res,'author-avatar'))) {
554 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
555 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
562 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
563 if($apps && $apps[0]['attribs']['']['source']) {
564 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
565 if($res['app'] === 'web')
566 $res['app'] = 'OStatus';
569 // base64 encoded json structure representing Diaspora signature
571 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
573 $res['dsprsig'] = unxmlify($dsig[0]['data']);
576 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
578 $res['guid'] = unxmlify($dguid[0]['data']);
580 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
582 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
586 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
589 $have_real_body = false;
591 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
593 $have_real_body = true;
594 $res['body'] = $rawenv[0]['data'];
595 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
596 // make sure nobody is trying to sneak some html tags by us
597 $res['body'] = notags(base64url_decode($res['body']));
601 $res['body'] = limit_body_size($res['body']);
603 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
604 // the content type. Our own network only emits text normally, though it might have been converted to
605 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
606 // have to assume it is all html and needs to be purified.
608 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
609 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
610 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
613 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
615 $res['body'] = reltoabs($res['body'],$base_url);
617 $res['body'] = html2bb_video($res['body']);
619 $res['body'] = oembed_html2bbcode($res['body']);
621 $config = HTMLPurifier_Config::createDefault();
622 $config->set('Cache.DefinitionImpl', null);
624 // we shouldn't need a whitelist, because the bbcode converter
625 // will strip out any unsupported tags.
627 $purifier = new HTMLPurifier($config);
628 $res['body'] = $purifier->purify($res['body']);
630 $res['body'] = @html2bbcode($res['body']);
634 elseif(! $have_real_body) {
636 // it's not one of our messages and it has no tags
637 // so it's probably just text. We'll escape it just to be safe.
639 $res['body'] = escape_tags($res['body']);
643 // this tag is obsolete but we keep it for really old sites
645 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
646 if($allow && $allow[0]['data'] == 1)
647 $res['last-child'] = 1;
649 $res['last-child'] = 0;
651 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
652 if($private && intval($private[0]['data']) > 0)
653 $res['private'] = intval($private[0]['data']);
657 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
658 if($extid && $extid[0]['data'])
659 $res['extid'] = $extid[0]['data'];
661 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
663 $res['location'] = unxmlify($rawlocation[0]['data']);
666 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
668 $res['created'] = unxmlify($rawcreated[0]['data']);
671 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
673 $res['edited'] = unxmlify($rawedited[0]['data']);
675 if((x($res,'edited')) && (! (x($res,'created'))))
676 $res['created'] = $res['edited'];
678 if(! $res['created'])
679 $res['created'] = $item->get_date('c');
682 $res['edited'] = $item->get_date('c');
685 // Disallow time travelling posts
687 $d1 = strtotime($res['created']);
688 $d2 = strtotime($res['edited']);
689 $d3 = strtotime('now');
692 $res['created'] = datetime_convert();
694 $res['edited'] = datetime_convert();
696 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
697 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
698 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
699 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
700 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
701 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
702 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
703 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
704 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
706 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
707 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
709 foreach($base as $link) {
710 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
711 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
712 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
717 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
719 $res['coord'] = unxmlify($rawgeo[0]['data']);
721 if ($contact["network"] == NETWORK_FEED) {
722 $res['verb'] = ACTIVITY_POST;
723 $res['object-type'] = ACTIVITY_OBJ_NOTE;
726 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
728 // select between supported verbs
731 $res['verb'] = unxmlify($rawverb[0]['data']);
734 // translate OStatus unfollow to activity streams if it happened to get selected
736 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
737 $res['verb'] = ACTIVITY_UNFOLLOW;
739 $cats = $item->get_categories();
742 foreach($cats as $cat) {
743 $term = $cat->get_term();
745 $term = $cat->get_label();
746 $scheme = $cat->get_scheme();
747 if($scheme && $term && stristr($scheme,'X-DFRN:'))
748 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
750 $tag_arr[] = notags(trim($term));
752 $res['tag'] = implode(',', $tag_arr);
755 $attach = $item->get_enclosures();
758 foreach($attach as $att) {
759 $len = intval($att->get_length());
760 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
761 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
762 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
763 if(strpos($type,';'))
764 $type = substr($type,0,strpos($type,';'));
765 if((! $link) || (strpos($link,'http') !== 0))
771 $type = 'application/octet-stream';
773 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
775 $res['attach'] = implode(',', $att_arr);
778 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
781 $res['object'] = '<object>' . "\n";
782 $child = $rawobj[0]['child'];
783 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
784 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
785 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
787 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
788 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
789 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
790 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
791 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
792 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
793 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
794 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
796 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
797 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
798 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
799 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
801 $body = html2bb_video($body);
803 $config = HTMLPurifier_Config::createDefault();
804 $config->set('Cache.DefinitionImpl', null);
806 $purifier = new HTMLPurifier($config);
807 $body = $purifier->purify($body);
808 $body = html2bbcode($body);
811 $res['object'] .= '<content>' . $body . '</content>' . "\n";
814 $res['object'] .= '</object>' . "\n";
817 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
820 $res['target'] = '<target>' . "\n";
821 $child = $rawobj[0]['child'];
822 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
823 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
825 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
826 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
827 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
828 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
829 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
830 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
831 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
832 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
834 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
835 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
836 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
837 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
839 $body = html2bb_video($body);
841 $config = HTMLPurifier_Config::createDefault();
842 $config->set('Cache.DefinitionImpl', null);
844 $purifier = new HTMLPurifier($config);
845 $body = $purifier->purify($body);
846 $body = html2bbcode($body);
849 $res['target'] .= '<content>' . $body . '</content>' . "\n";
852 $res['target'] .= '</target>' . "\n";
855 // This is some experimental stuff. By now retweets are shown with "RT:"
856 // But: There is data so that the message could be shown similar to native retweets
857 // There is some better way to parse this array - but it didn't worked for me.
858 $child = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://activitystrea.ms/spec/1.0/"][object][0]["child"];
859 if (is_array($child)) {
860 logger('get_atom_elements: Looking for status.net repeated message');
862 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
863 $orig_id = ostatus_convert_href($child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"]);
864 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
865 $uri = $author["uri"][0]["data"];
866 $name = $author["name"][0]["data"];
867 $avatar = @array_shift($author["link"][2]["attribs"]);
868 $avatar = $avatar["href"];
870 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
871 logger('get_atom_elements: fixing sender of repeated message. '.$orig_id, LOGGER_DEBUG);
873 if (!intval(get_config('system','wall-to-wall_share'))) {
874 $prefix = share_header($name, $uri, $avatar, "", "", $orig_link);
876 $res["body"] = $prefix.html2bbcode($message)."[/share]";
878 $res["owner-name"] = $res["author-name"];
879 $res["owner-link"] = $res["author-link"];
880 $res["owner-avatar"] = $res["author-avatar"];
882 $res["author-name"] = $name;
883 $res["author-link"] = $uri;
884 $res["author-avatar"] = $avatar;
886 $res["body"] = html2bbcode($message);
891 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
894 // Handle enclosures and treat them as preview picture
896 foreach ($attach AS $attachment)
897 if ($attachment->type == "image/jpeg")
898 $preview = $attachment->link;
900 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
901 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
903 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
904 unset($res["attach"]);
905 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
906 $res["body"] = add_page_info_to_body($res["body"]);
907 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
908 $res["body"] = add_page_info_to_body($res["body"]);
911 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
913 call_hooks('parse_atom', $arr);
918 function add_page_info_data($data) {
919 call_hooks('page_info_data', $data);
921 // It maybe is a rich content, but if it does have everything that a link has,
922 // then treat it that way
923 if (($data["type"] == "rich") AND is_string($data["title"]) AND
924 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
925 $data["type"] = "link";
927 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
930 if ($no_photos AND ($data["type"] == "photo"))
933 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
934 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
935 require_once("include/network.php");
936 $data["url"] = short_link($data["url"]);
939 if (($data["type"] != "photo") AND is_string($data["title"]))
940 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
942 if (($data["type"] != "video") AND ($photo != ""))
943 $text .= '[img]'.$photo.'[/img]';
944 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
945 $imagedata = $data["images"][0];
946 $text .= '[img]'.$imagedata["src"].'[/img]';
949 if (($data["type"] != "photo") AND is_string($data["text"]))
950 $text .= "[quote]".$data["text"]."[/quote]";
953 if (isset($data["keywords"]) AND count($data["keywords"])) {
956 foreach ($data["keywords"] AS $keyword) {
957 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
958 array("","", "", "", "", ""), $keyword);
959 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
963 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
966 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
967 require_once("mod/parse_url.php");
969 $data = Cache::get("parse_url:".$url);
971 $data = parseurl_getsiteinfo($url, true);
972 Cache::set("parse_url:".$url,serialize($data), CACHE_DAY);
974 $data = unserialize($data);
977 $data["images"][0]["src"] = $photo;
979 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
981 if (!$keywords AND isset($data["keywords"]))
982 unset($data["keywords"]);
984 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
985 $list = explode(",", $keyword_blacklist);
986 foreach ($list AS $keyword) {
987 $keyword = trim($keyword);
988 $index = array_search($keyword, $data["keywords"]);
989 if ($index !== false)
990 unset($data["keywords"][$index]);
997 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
998 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1001 if (isset($data["keywords"]) AND count($data["keywords"])) {
1003 foreach ($data["keywords"] AS $keyword) {
1004 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
1005 array("","", "", "", "", ""), $keyword);
1010 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
1017 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1018 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1020 $text = add_page_info_data($data);
1025 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1027 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1029 $URLSearchString = "^\[\]";
1031 // Adding these spaces is a quick hack due to my problems with regular expressions :)
1032 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1035 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1037 // Convert urls without bbcode elements
1038 if (!$matches AND $texturl) {
1039 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1041 // Yeah, a hack. I really hate regular expressions :)
1043 $matches[1] = $matches[2];
1047 $footer = add_page_info($matches[1], $no_photos);
1049 // Remove the link from the body if the link is attached at the end of the post
1050 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1051 $removedlink = trim(str_replace($matches[1], "", $body));
1052 if (($removedlink == "") OR strstr($body, $removedlink))
1053 $body = $removedlink;
1055 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1056 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1057 if (($removedlink == "") OR strstr($body, $removedlink))
1058 $body = $removedlink;
1061 // Add the page information to the bottom
1062 if (isset($footer) AND (trim($footer) != ""))
1068 function encode_rel_links($links) {
1070 if(! ((is_array($links)) && (count($links))))
1072 foreach($links as $link) {
1074 if($link['attribs']['']['rel'])
1075 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1076 if($link['attribs']['']['type'])
1077 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1078 if($link['attribs']['']['href'])
1079 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1080 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1081 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1082 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1083 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1084 $o .= ' />' . "\n" ;
1089 function add_guid($item) {
1090 $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
1094 q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
1095 dbesc($item["guid"]), dbesc($item["plink"]),
1096 dbesc($item["uri"]), dbesc($item["network"]));
1099 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1101 // If it is a posting where users should get notifications, then define it as wall posting
1104 $arr['type'] = 'wall';
1106 $arr['last-child'] = 1;
1107 $arr['network'] = NETWORK_DFRN;
1110 // If a Diaspora signature structure was passed in, pull it out of the
1111 // item array and set it aside for later storage.
1114 if(x($arr,'dsprsig')) {
1115 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1116 unset($arr['dsprsig']);
1119 // Converting the plink
1120 if ($arr['network'] == NETWORK_OSTATUS) {
1121 if (isset($arr['plink']))
1122 $arr['plink'] = ostatus_convert_href($arr['plink']);
1123 elseif (isset($arr['uri']))
1124 $arr['plink'] = ostatus_convert_href($arr['uri']);
1127 if(x($arr, 'gravity'))
1128 $arr['gravity'] = intval($arr['gravity']);
1129 elseif($arr['parent-uri'] === $arr['uri'])
1130 $arr['gravity'] = 0;
1131 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1132 $arr['gravity'] = 6;
1134 $arr['gravity'] = 6; // extensible catchall
1136 if(! x($arr,'type'))
1137 $arr['type'] = 'remote';
1141 /* check for create date and expire time */
1142 $uid = intval($arr['uid']);
1143 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
1145 $expire_interval = $r[0]['expire'];
1146 if ($expire_interval>0) {
1147 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1148 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1149 if ($created_date < $expire_date) {
1150 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1156 // If there is no guid then take the same guid that was taken before for the same uri
1157 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
1158 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1159 $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
1160 dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
1163 $arr['guid'] = $r[0]["guid"];
1164 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1168 // If there is no guid then take the same guid that was taken before for the same plink
1169 if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
1170 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
1171 $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
1172 dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
1175 $arr['guid'] = $r[0]["guid"];
1176 logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
1178 if ($r[0]["uri"] != $arr['uri'])
1179 logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
1183 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1184 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1185 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1186 // $arr['body'] = strip_tags($arr['body']);
1189 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1190 require_once('library/langdet/Text/LanguageDetect.php');
1191 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1192 $l = new Text_LanguageDetect;
1193 //$lng = $l->detectConfidence($naked_body);
1194 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1195 $lng = $l->detect($naked_body, 3);
1197 if (sizeof($lng) > 0) {
1200 foreach ($lng as $language => $score) {
1201 if ($postopts == "")
1202 $postopts = "lang=";
1206 $postopts .= $language.";".$score;
1208 $arr['postopts'] = $postopts;
1215 $guid_prefix = $arr['network'];
1217 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1218 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
1219 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
1220 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1221 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1222 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1223 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1224 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1225 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1226 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1227 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1228 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1229 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1230 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1231 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1232 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1233 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1234 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1235 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1236 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1237 $arr['deleted'] = 0;
1238 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1239 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1240 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1241 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1242 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1243 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1244 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1245 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1246 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1247 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1248 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1249 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1250 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1251 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1252 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1253 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1254 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1255 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1256 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1257 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1258 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1259 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1260 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1261 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1263 if ($arr['plink'] == "") {
1265 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1268 if ($arr['network'] == "") {
1269 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1270 intval($arr['contact-id']),
1275 $arr['network'] = $r[0]["network"];
1277 // Fallback to friendica (why is it empty in some cases?)
1278 if ($arr['network'] == "")
1279 $arr['network'] = NETWORK_DFRN;
1281 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1284 if ($arr['guid'] != "") {
1285 // Checking if there is already an item with the same guid
1286 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1287 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1288 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1291 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1296 // Check for hashtags in the body and repair or add hashtag links
1297 item_body_set_hashtags($arr);
1299 $arr['thr-parent'] = $arr['parent-uri'];
1300 if($arr['parent-uri'] === $arr['uri']) {
1302 $parent_deleted = 0;
1303 $allow_cid = $arr['allow_cid'];
1304 $allow_gid = $arr['allow_gid'];
1305 $deny_cid = $arr['deny_cid'];
1306 $deny_gid = $arr['deny_gid'];
1307 $notify_type = 'wall-new';
1311 // find the parent and snarf the item id and ACLs
1312 // and anything else we need to inherit
1314 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1315 dbesc($arr['parent-uri']),
1321 // is the new message multi-level threaded?
1322 // even though we don't support it now, preserve the info
1323 // and re-attach to the conversation parent.
1325 if($r[0]['uri'] != $r[0]['parent-uri']) {
1326 $arr['parent-uri'] = $r[0]['parent-uri'];
1327 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1328 ORDER BY `id` ASC LIMIT 1",
1329 dbesc($r[0]['parent-uri']),
1330 dbesc($r[0]['parent-uri']),
1337 $parent_id = $r[0]['id'];
1338 $parent_deleted = $r[0]['deleted'];
1339 $allow_cid = $r[0]['allow_cid'];
1340 $allow_gid = $r[0]['allow_gid'];
1341 $deny_cid = $r[0]['deny_cid'];
1342 $deny_gid = $r[0]['deny_gid'];
1343 $arr['wall'] = $r[0]['wall'];
1344 $notify_type = 'comment-new';
1346 // if the parent is private, force privacy for the entire conversation
1347 // This differs from the above settings as it subtly allows comments from
1348 // email correspondents to be private even if the overall thread is not.
1350 if($r[0]['private'])
1351 $arr['private'] = $r[0]['private'];
1353 // Edge case. We host a public forum that was originally posted to privately.
1354 // The original author commented, but as this is a comment, the permissions
1355 // weren't fixed up so it will still show the comment as private unless we fix it here.
1357 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1358 $arr['private'] = 0;
1361 // If its a post from myself then tag the thread as "mention"
1362 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1363 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1366 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1367 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1368 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1369 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1370 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1376 // Allow one to see reply tweets from status.net even when
1377 // we don't have or can't see the original post.
1380 logger('item_store: $force_parent=true, reply converted to top-level post.');
1382 $arr['parent-uri'] = $arr['uri'];
1383 $arr['gravity'] = 0;
1386 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1390 $parent_deleted = 0;
1394 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1396 dbesc($arr['network']),
1399 if($r && count($r)) {
1400 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1404 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1405 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1406 dbesc($arr['body']),
1407 dbesc($arr['network']),
1408 dbesc($arr['created']),
1409 intval($arr['contact-id']),
1412 if($r && count($r)) {
1413 logger('duplicated item with the same body found. ' . print_r($arr,true));
1417 // Is this item available in the global items (with uid=0)?
1418 if ($arr["uid"] == 0) {
1419 $arr["global"] = true;
1421 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1423 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1425 $arr["global"] = (count($isglobal) > 0);
1428 // Fill the cache field
1429 put_item_in_cache($arr);
1432 call_hooks('post_local',$arr);
1434 call_hooks('post_remote',$arr);
1436 if(x($arr,'cancel')) {
1437 logger('item_store: post cancelled by plugin.');
1441 // Store the unescaped version
1446 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1448 $r = dbq("INSERT INTO `item` (`"
1449 . implode("`, `", array_keys($arr))
1451 . implode("', '", array_values($arr))
1457 // find the item we just created
1458 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1465 // Store the guid and other relevant data
1468 $current_post = $r[0]['id'];
1469 logger('item_store: created item ' . $current_post);
1471 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1472 // This can be used to filter for inactive contacts.
1473 // Only do this for public postings to avoid privacy problems, since poco data is public.
1474 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1476 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1478 // Is it a forum? Then we don't care about the rules from above
1479 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1480 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1481 intval($arr['contact-id']));
1487 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1488 dbesc($arr['received']),
1489 dbesc($arr['received']),
1490 intval($arr['contact-id'])
1493 logger('item_store: could not locate created item');
1497 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1498 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1500 intval($arr['uid']),
1501 intval($current_post)
1505 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1506 $parent_id = $current_post;
1508 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1511 $private = $arr['private'];
1513 // Set parent id - and also make sure to inherit the parent's ACLs.
1515 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1516 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1523 intval($parent_deleted),
1524 intval($current_post)
1527 $arr['id'] = $current_post;
1528 $arr['parent'] = $parent_id;
1529 $arr['allow_cid'] = $allow_cid;
1530 $arr['allow_gid'] = $allow_gid;
1531 $arr['deny_cid'] = $deny_cid;
1532 $arr['deny_gid'] = $deny_gid;
1533 $arr['private'] = $private;
1534 $arr['deleted'] = $parent_deleted;
1536 // update the commented timestamp on the parent
1537 // Only update "commented" if it is really a comment
1538 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1539 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1540 dbesc(datetime_convert()),
1541 dbesc(datetime_convert()),
1545 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1546 dbesc(datetime_convert()),
1551 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1552 intval($current_post),
1553 dbesc($dsprsig->signed_text),
1554 dbesc($dsprsig->signature),
1555 dbesc($dsprsig->signer)
1561 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1564 if($arr['last-child']) {
1565 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1567 intval($arr['uid']),
1568 intval($current_post)
1572 $deleted = tag_deliver($arr['uid'],$current_post);
1574 // current post can be deleted if is for a community page and no mention are
1576 if (!$deleted AND !$dontcache) {
1578 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1579 if (count($r) == 1) {
1581 call_hooks('post_local_end', $r[0]);
1583 call_hooks('post_remote_end', $r[0]);
1585 logger('item_store: new item not found in DB, id ' . $current_post);
1588 // Add every contact of the post to the global contact table
1591 create_tags_from_item($current_post);
1592 create_files_from_item($current_post);
1594 // Only check for notifications on start posts
1595 if ($arr['parent-uri'] === $arr['uri']) {
1596 add_thread($current_post);
1597 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1599 // Send a notification for every new post?
1600 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1601 intval($arr['contact-id']),
1604 $send_notification = count($r);
1606 if (!$send_notification) {
1607 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1608 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1611 foreach ($tags AS $tag) {
1612 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1613 normalise_link($tag["url"]), intval($arr['uid']));
1615 $send_notification = true;
1620 if ($send_notification) {
1621 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1622 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1623 intval($arr['uid']));
1625 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1626 intval($current_post),
1632 require_once('include/enotify.php');
1634 'type' => NOTIFY_SHARE,
1635 'notify_flags' => $u[0]['notify-flags'],
1636 'language' => $u[0]['language'],
1637 'to_name' => $u[0]['username'],
1638 'to_email' => $u[0]['email'],
1639 'uid' => $u[0]['uid'],
1641 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1642 'source_name' => $item[0]['author-name'],
1643 'source_link' => $item[0]['author-link'],
1644 'source_photo' => $item[0]['author-avatar'],
1645 'verb' => ACTIVITY_TAG,
1647 'parent' => $arr['parent']
1649 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1652 update_thread($parent_id);
1653 add_shadow_entry($arr);
1657 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1659 return $current_post;
1662 function item_body_set_hashtags(&$item) {
1664 $tags = get_tags($item["body"]);
1670 // This sorting is important when there are hashtags that are part of other hashtags
1671 // Otherwise there could be problems with hashtags like #test and #test2
1676 $URLSearchString = "^\[\]";
1678 // All hashtags should point to the home server
1679 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1680 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1682 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1683 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1685 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1686 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1688 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1691 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1693 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1696 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1698 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1701 // Repair recursive urls
1702 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1703 "#$2", $item["body"]);
1706 foreach($tags as $tag) {
1707 if(strpos($tag,'#') !== 0)
1710 if(strpos($tag,'[url='))
1713 $basetag = str_replace('_',' ',substr($tag,1));
1715 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1717 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1719 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1720 if(strlen($item["tag"]))
1721 $item["tag"] = ','.$item["tag"];
1722 $item["tag"] = $newtag.$item["tag"];
1726 // Convert back the masked hashtags
1727 $item["body"] = str_replace("#", "#", $item["body"]);
1730 function get_item_guid($id) {
1731 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1733 return($r[0]["guid"]);
1738 function get_item_id($guid, $uid = 0) {
1744 $uid == local_user();
1746 // Does the given user have this item?
1748 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1749 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1750 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1753 $nick = $r[0]["nickname"];
1757 // Or is it anywhere on the server?
1759 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1760 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1761 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1762 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1763 AND `item`.`private` = 0 AND `item`.`wall` = 1
1764 AND `item`.`guid` = '%s'", dbesc($guid));
1767 $nick = $r[0]["nickname"];
1770 return(array("nick" => $nick, "id" => $id));
1774 function get_item_contact($item,$contacts) {
1775 if(! count($contacts) || (! is_array($item)))
1777 foreach($contacts as $contact) {
1778 if($contact['id'] == $item['contact-id']) {
1780 break; // NOTREACHED
1787 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1789 * @param int $item_id
1790 * @return bool true if item was deleted, else false
1792 function tag_deliver($uid,$item_id) {
1800 $u = q("select * from user where uid = %d limit 1",
1806 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1807 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1810 $i = q("select * from item where id = %d and uid = %d limit 1",
1819 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1821 // Diaspora uses their own hardwired link URL in @-tags
1822 // instead of the one we supply with webfinger
1824 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1826 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1828 foreach($matches as $mtch) {
1829 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1831 logger('tag_deliver: mention found: ' . $mtch[2]);
1837 if ( ($community_page || $prvgroup) &&
1838 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1839 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1841 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1842 q("DELETE FROM item WHERE id = %d and uid = %d",
1852 // send a notification
1854 // use a local photo if we have one
1856 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1857 intval($u[0]['uid']),
1858 dbesc(normalise_link($item['author-link']))
1860 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1863 require_once('include/enotify.php');
1865 'type' => NOTIFY_TAGSELF,
1866 'notify_flags' => $u[0]['notify-flags'],
1867 'language' => $u[0]['language'],
1868 'to_name' => $u[0]['username'],
1869 'to_email' => $u[0]['email'],
1870 'uid' => $u[0]['uid'],
1872 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1873 'source_name' => $item['author-name'],
1874 'source_link' => $item['author-link'],
1875 'source_photo' => $photo,
1876 'verb' => ACTIVITY_TAG,
1878 'parent' => $item['parent']
1882 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1884 call_hooks('tagged', $arr);
1886 if((! $community_page) && (! $prvgroup))
1890 // tgroup delivery - setup a second delivery chain
1891 // prevent delivery looping - only proceed
1892 // if the message originated elsewhere and is a top-level post
1894 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1897 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1900 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1901 intval($u[0]['uid'])
1906 // also reset all the privacy bits to the forum default permissions
1908 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1910 $forum_mode = (($prvgroup) ? 2 : 1);
1912 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1913 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1914 intval($forum_mode),
1915 dbesc($c[0]['name']),
1916 dbesc($c[0]['url']),
1917 dbesc($c[0]['thumb']),
1919 dbesc($u[0]['allow_cid']),
1920 dbesc($u[0]['allow_gid']),
1921 dbesc($u[0]['deny_cid']),
1922 dbesc($u[0]['deny_gid']),
1925 update_thread($item_id);
1927 proc_run('php','include/notifier.php','tgroup',$item_id);
1933 function tgroup_check($uid,$item) {
1939 // check that the message originated elsewhere and is a top-level post
1941 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1945 $u = q("select * from user where uid = %d limit 1",
1951 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1952 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1955 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1957 // Diaspora uses their own hardwired link URL in @-tags
1958 // instead of the one we supply with webfinger
1960 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1962 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1964 foreach($matches as $mtch) {
1965 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1967 logger('tgroup_check: mention found: ' . $mtch[2]);
1975 if((! $community_page) && (! $prvgroup))
1989 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1993 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1995 if($contact['duplex'] && $contact['dfrn-id'])
1996 $idtosend = '0:' . $orig_id;
1997 if($contact['duplex'] && $contact['issued-id'])
1998 $idtosend = '1:' . $orig_id;
2001 $rino = get_config('system','rino_encrypt');
2002 $rino = intval($rino);
2003 // use RINO1 if mcrypt isn't installed and RINO2 was selected
2004 if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
2006 logger("Local rino version: ". $rino, LOGGER_DEBUG);
2008 $ssl_val = intval(get_config('system','ssl_policy'));
2012 case SSL_POLICY_FULL:
2013 $ssl_policy = 'full';
2015 case SSL_POLICY_SELFSIGN:
2016 $ssl_policy = 'self';
2018 case SSL_POLICY_NONE:
2020 $ssl_policy = 'none';
2024 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
2026 logger('dfrn_deliver: ' . $url);
2028 $xml = fetch_url($url);
2030 $curl_stat = $a->get_curl_code();
2032 return(-1); // timed out
2034 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2039 if(strpos($xml,'<?xml') === false) {
2040 logger('dfrn_deliver: no valid XML returned');
2041 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2045 $res = parse_xml_string($xml);
2047 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2048 return (($res->status) ? $res->status : 3);
2050 $postvars = array();
2051 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2052 $challenge = hex2bin((string) $res->challenge);
2053 $perm = (($res->perm) ? $res->perm : null);
2054 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2055 $rino_remote_version = intval($res->rino);
2056 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2058 logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG);
2060 if($owner['page-flags'] == PAGE_PRVGROUP)
2063 $final_dfrn_id = '';
2066 if((($perm == 'rw') && (! intval($contact['writable'])))
2067 || (($perm == 'r') && (intval($contact['writable'])))) {
2068 q("update contact set writable = %d where id = %d",
2069 intval(($perm == 'rw') ? 1 : 0),
2070 intval($contact['id'])
2072 $contact['writable'] = (string) 1 - intval($contact['writable']);
2076 if(($contact['duplex'] && strlen($contact['pubkey']))
2077 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2078 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2079 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2080 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2083 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2084 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2087 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2089 if(strpos($final_dfrn_id,':') == 1)
2090 $final_dfrn_id = substr($final_dfrn_id,2);
2092 if($final_dfrn_id != $orig_id) {
2093 logger('dfrn_deliver: wrong dfrn_id.');
2094 // did not decode properly - cannot trust this site
2098 $postvars['dfrn_id'] = $idtosend;
2099 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2101 $postvars['dissolve'] = '1';
2104 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2105 $postvars['data'] = $atom;
2106 $postvars['perm'] = 'rw';
2109 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2110 $postvars['perm'] = 'r';
2113 $postvars['ssl_policy'] = $ssl_policy;
2116 $postvars['page'] = $page;
2119 if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
2120 logger('rino version: '. $rino_remote_version);
2122 switch($rino_remote_version) {
2124 // Deprecated rino version!
2125 $key = substr(random_string(),0,16);
2126 $data = aes_encrypt($postvars['data'],$key);
2129 // RINO 2 based on php-encryption
2131 $key = Crypto::createNewRandomKey();
2132 } catch (CryptoTestFailed $ex) {
2133 logger('Cannot safely create a key');
2135 } catch (CannotPerformOperation $ex) {
2136 logger('Cannot safely create a key');
2140 $data = Crypto::encrypt($postvars['data'], $key);
2141 } catch (CryptoTestFailed $ex) {
2142 logger('Cannot safely perform encryption');
2144 } catch (CannotPerformOperation $ex) {
2145 logger('Cannot safely perform encryption');
2150 logger("rino: invalid requested verision '$rino_remote_version'");
2154 $postvars['rino'] = $rino_remote_version;
2155 $postvars['data'] = bin2hex($data);
2157 #logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2160 if($dfrn_version >= 2.1) {
2161 if(($contact['duplex'] && strlen($contact['pubkey']))
2162 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2163 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2165 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2168 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2172 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2173 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2176 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2180 logger('md5 rawkey ' . md5($postvars['key']));
2182 $postvars['key'] = bin2hex($postvars['key']);
2186 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2188 $xml = post_url($contact['notify'],$postvars);
2190 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2192 $curl_stat = $a->get_curl_code();
2193 if((! $curl_stat) || (! strlen($xml)))
2194 return(-1); // timed out
2196 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2199 if(strpos($xml,'<?xml') === false) {
2200 logger('dfrn_deliver: phase 2: no valid XML returned');
2201 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2205 if($contact['term-date'] != '0000-00-00 00:00:00') {
2206 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2207 require_once('include/Contact.php');
2208 unmark_for_death($contact);
2211 $res = parse_xml_string($xml);
2213 return $res->status;
2218 This function returns true if $update has an edited timestamp newer
2219 than $existing, i.e. $update contains new data which should override
2220 what's already there. If there is no timestamp yet, the update is
2221 assumed to be newer. If the update has no timestamp, the existing
2222 item is assumed to be up-to-date. If the timestamps are equal it
2223 assumes the update has been seen before and should be ignored.
2225 function edited_timestamp_is_newer($existing, $update) {
2226 if (!x($existing,'edited') || !$existing['edited']) {
2229 if (!x($update,'edited') || !$update['edited']) {
2232 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2233 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2234 return (strcmp($existing_edited, $update_edited) < 0);
2239 * consume_feed - process atom feed and update anything/everything we might need to update
2241 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2243 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2244 * It is this person's stuff that is going to be updated.
2245 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2246 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2247 * have a contact record.
2248 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2249 * might not) try and subscribe to it.
2250 * $datedir sorts in reverse order
2251 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2252 * imported prior to its children being seen in the stream unless we are certain
2253 * of how the feed is arranged/ordered.
2254 * With $pass = 1, we only pull parent items out of the stream.
2255 * With $pass = 2, we only pull children (comments/likes).
2257 * So running this twice, first with pass 1 and then with pass 2 will do the right
2258 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2259 * model where comments can have sub-threads. That would require some massive sorting
2260 * to get all the feed items into a mostly linear ordering, and might still require
2264 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2265 if ($contact['network'] === NETWORK_OSTATUS) {
2267 // Test - remove before flight
2268 //$tempfile = tempnam(get_temppath(), "ostatus");
2269 //file_put_contents($tempfile, $xml);
2271 logger("Consume OStatus messages ", LOGGER_DEBUG);
2272 ostatus_import($xml,$importer,$contact, $hub);
2277 require_once('library/simplepie/simplepie.inc');
2278 require_once('include/contact_selectors.php');
2280 if(! strlen($xml)) {
2281 logger('consume_feed: empty input');
2285 $feed = new SimplePie();
2286 $feed->set_raw_data($xml);
2288 $feed->enable_order_by_date(true);
2290 $feed->enable_order_by_date(false);
2294 logger('consume_feed: Error parsing XML: ' . $feed->error());
2296 $permalink = $feed->get_permalink();
2298 // Check at the feed level for updated contact name and/or photo
2302 $photo_timestamp = '';
2305 $contact_updated = '';
2307 $hubs = $feed->get_links('hub');
2308 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2311 $hub = implode(',', $hubs);
2313 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2315 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2317 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2318 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2319 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2320 $new_name = $elems['name'][0]['data'];
2322 // Manually checking for changed contact names
2323 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2324 $name_updated = date("c");
2325 $photo_timestamp = date("c");
2328 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2329 if ($photo_timestamp == "")
2330 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2331 $photo_url = $elems['link'][0]['attribs']['']['href'];
2334 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2335 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2339 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2340 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2342 $contact_updated = $photo_timestamp;
2344 require_once("include/Photo.php");
2345 $photo_failure = false;
2346 $have_photo = false;
2348 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2349 intval($contact['id']),
2350 intval($contact['uid'])
2353 $resource_id = $r[0]['resource-id'];
2357 $resource_id = photo_new_resource();
2360 $img_str = fetch_url($photo_url,true);
2361 // guess mimetype from headers or filename
2362 $type = guess_image_type($photo_url,true);
2365 $img = new Photo($img_str, $type);
2366 if($img->is_valid()) {
2368 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2369 dbesc($resource_id),
2370 intval($contact['id']),
2371 intval($contact['uid'])
2375 $img->scaleImageSquare(175);
2377 $hash = $resource_id;
2378 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2380 $img->scaleImage(80);
2381 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2383 $img->scaleImage(48);
2384 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2388 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2389 WHERE `uid` = %d AND `id` = %d",
2390 dbesc(datetime_convert()),
2391 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2392 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2393 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2394 intval($contact['uid']),
2395 intval($contact['id'])
2400 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2401 if ($name_updated > $contact_updated)
2402 $contact_updated = $name_updated;
2404 $r = q("select * from contact where uid = %d and id = %d limit 1",
2405 intval($contact['uid']),
2406 intval($contact['id'])
2409 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2410 dbesc(notags(trim($new_name))),
2411 dbesc(datetime_convert()),
2412 intval($contact['uid']),
2413 intval($contact['id'])
2416 // do our best to update the name on content items
2419 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2420 dbesc(notags(trim($new_name))),
2421 dbesc($r[0]['name']),
2422 dbesc($r[0]['url']),
2423 intval($contact['uid'])
2428 if ($contact_updated AND $new_name AND $photo_url)
2429 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2431 if(strlen($birthday)) {
2432 if(substr($birthday,0,4) != $contact['bdyear']) {
2433 logger('consume_feed: updating birthday: ' . $birthday);
2437 * Add new birthday event for this person
2439 * $bdtext is just a readable placeholder in case the event is shared
2440 * with others. We will replace it during presentation to our $importer
2441 * to contain a sparkle link and perhaps a photo.
2445 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2446 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2449 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2450 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2451 intval($contact['uid']),
2452 intval($contact['id']),
2453 dbesc(datetime_convert()),
2454 dbesc(datetime_convert()),
2455 dbesc(datetime_convert('UTC','UTC', $birthday)),
2456 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2465 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2466 dbesc(substr($birthday,0,4)),
2467 intval($contact['uid']),
2468 intval($contact['id'])
2471 // This function is called twice without reloading the contact
2472 // Make sure we only create one event. This is why &$contact
2473 // is a reference var in this function
2475 $contact['bdyear'] = substr($birthday,0,4);
2479 $community_page = 0;
2480 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2482 $community_page = intval($rawtags[0]['data']);
2484 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2485 q("update contact set forum = %d where id = %d",
2486 intval($community_page),
2487 intval($contact['id'])
2489 $contact['forum'] = (string) $community_page;
2493 // process any deleted entries
2495 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2496 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2497 foreach($del_entries as $dentry) {
2499 if(isset($dentry['attribs']['']['ref'])) {
2500 $uri = $dentry['attribs']['']['ref'];
2502 if(isset($dentry['attribs']['']['when'])) {
2503 $when = $dentry['attribs']['']['when'];
2504 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2507 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2509 if($deleted && is_array($contact)) {
2510 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2511 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2513 intval($importer['uid']),
2514 intval($contact['id'])
2519 if(! $item['deleted'])
2520 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2522 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2523 $xo = parse_xml_string($item['object'],false);
2524 $xt = parse_xml_string($item['target'],false);
2525 if($xt->type === ACTIVITY_OBJ_NOTE) {
2526 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2528 intval($importer['importer_uid'])
2532 // For tags, the owner cannot remove the tag on the author's copy of the post.
2534 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2535 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2536 $author_copy = (($item['origin']) ? true : false);
2538 if($owner_remove && $author_copy)
2540 if($author_remove || $owner_remove) {
2541 $tags = explode(',',$i[0]['tag']);
2544 foreach($tags as $tag)
2545 if(trim($tag) !== trim($xo->body))
2546 $newtags[] = trim($tag);
2548 q("update item set tag = '%s' where id = %d",
2549 dbesc(implode(',',$newtags)),
2552 create_tags_from_item($i[0]['id']);
2558 if($item['uri'] == $item['parent-uri']) {
2559 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2560 `body` = '', `title` = ''
2561 WHERE `parent-uri` = '%s' AND `uid` = %d",
2563 dbesc(datetime_convert()),
2564 dbesc($item['uri']),
2565 intval($importer['uid'])
2567 create_tags_from_itemuri($item['uri'], $importer['uid']);
2568 create_files_from_itemuri($item['uri'], $importer['uid']);
2569 update_thread_uri($item['uri'], $importer['uid']);
2572 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2573 `body` = '', `title` = ''
2574 WHERE `uri` = '%s' AND `uid` = %d",
2576 dbesc(datetime_convert()),
2578 intval($importer['uid'])
2580 create_tags_from_itemuri($uri, $importer['uid']);
2581 create_files_from_itemuri($uri, $importer['uid']);
2582 if($item['last-child']) {
2583 // ensure that last-child is set in case the comment that had it just got wiped.
2584 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2585 dbesc(datetime_convert()),
2586 dbesc($item['parent-uri']),
2587 intval($item['uid'])
2589 // who is the last child now?
2590 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2591 ORDER BY `created` DESC LIMIT 1",
2592 dbesc($item['parent-uri']),
2593 intval($importer['uid'])
2596 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2607 // Now process the feed
2609 if($feed->get_item_quantity()) {
2611 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2613 // in inverse date order
2615 $items = array_reverse($feed->get_items());
2617 $items = $feed->get_items();
2620 foreach($items as $item) {
2623 $item_id = $item->get_id();
2624 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2625 if(isset($rawthread[0]['attribs']['']['ref'])) {
2627 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2630 if(($is_reply) && is_array($contact)) {
2635 // not allowed to post
2637 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2641 // Have we seen it? If not, import it.
2643 $item_id = $item->get_id();
2644 $datarray = get_atom_elements($feed, $item, $contact);
2646 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2647 $datarray['author-name'] = $contact['name'];
2648 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2649 $datarray['author-link'] = $contact['url'];
2650 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2651 $datarray['author-avatar'] = $contact['thumb'];
2653 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2654 logger('consume_feed: no author information! ' . print_r($datarray,true));
2658 $force_parent = false;
2659 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2660 if($contact['network'] === NETWORK_OSTATUS)
2661 $force_parent = true;
2662 if(strlen($datarray['title']))
2663 unset($datarray['title']);
2664 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2665 dbesc(datetime_convert()),
2667 intval($importer['uid'])
2669 $datarray['last-child'] = 1;
2670 update_thread_uri($parent_uri, $importer['uid']);
2674 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2676 intval($importer['uid'])
2679 // Update content if 'updated' changes
2682 if (edited_timestamp_is_newer($r[0], $datarray)) {
2684 // do not accept (ignore) an earlier edit than one we currently have.
2685 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2688 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2689 dbesc($datarray['title']),
2690 dbesc($datarray['body']),
2691 dbesc($datarray['tag']),
2692 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2693 dbesc(datetime_convert()),
2695 intval($importer['uid'])
2697 create_tags_from_itemuri($item_id, $importer['uid']);
2698 update_thread_uri($item_id, $importer['uid']);
2701 // update last-child if it changes
2703 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2704 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2705 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2706 dbesc(datetime_convert()),
2708 intval($importer['uid'])
2710 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2711 intval($allow[0]['data']),
2712 dbesc(datetime_convert()),
2714 intval($importer['uid'])
2716 update_thread_uri($item_id, $importer['uid']);
2722 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2723 // one way feed - no remote comment ability
2724 $datarray['last-child'] = 0;
2726 $datarray['parent-uri'] = $parent_uri;
2727 $datarray['uid'] = $importer['uid'];
2728 $datarray['contact-id'] = $contact['id'];
2729 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2730 $datarray['type'] = 'activity';
2731 $datarray['gravity'] = GRAVITY_LIKE;
2732 // only one like or dislike per person
2733 // splitted into two queries for performance issues
2734 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s') limit 1",
2735 intval($datarray['uid']),
2736 intval($datarray['contact-id']),
2737 dbesc($datarray['verb']),
2743 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`thr-parent` = '%s') limit 1",
2744 intval($datarray['uid']),
2745 intval($datarray['contact-id']),
2746 dbesc($datarray['verb']),
2753 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2754 $xo = parse_xml_string($datarray['object'],false);
2755 $xt = parse_xml_string($datarray['target'],false);
2757 if($xt->type == ACTIVITY_OBJ_NOTE) {
2758 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2760 intval($importer['importer_uid'])
2765 // extract tag, if not duplicate, add to parent item
2766 if($xo->id && $xo->content) {
2767 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2768 if(! (stristr($r[0]['tag'],$newtag))) {
2769 q("UPDATE item SET tag = '%s' WHERE id = %d",
2770 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2773 create_tags_from_item($r[0]['id']);
2779 $r = item_store($datarray,$force_parent);
2785 // Head post of a conversation. Have we seen it? If not, import it.
2787 $item_id = $item->get_id();
2789 $datarray = get_atom_elements($feed, $item, $contact);
2791 if(is_array($contact)) {
2792 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2793 $datarray['author-name'] = $contact['name'];
2794 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2795 $datarray['author-link'] = $contact['url'];
2796 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2797 $datarray['author-avatar'] = $contact['thumb'];
2800 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2801 logger('consume_feed: no author information! ' . print_r($datarray,true));
2805 // special handling for events
2807 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2808 $ev = bbtoevent($datarray['body']);
2809 if(x($ev,'desc') && x($ev,'start')) {
2810 $ev['uid'] = $importer['uid'];
2811 $ev['uri'] = $item_id;
2812 $ev['edited'] = $datarray['edited'];
2813 $ev['private'] = $datarray['private'];
2815 if(is_array($contact))
2816 $ev['cid'] = $contact['id'];
2817 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2819 intval($importer['uid'])
2822 $ev['id'] = $r[0]['id'];
2823 $xyz = event_store($ev);
2828 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2829 if(strlen($datarray['title']))
2830 unset($datarray['title']);
2831 $datarray['last-child'] = 1;
2835 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2837 intval($importer['uid'])
2840 // Update content if 'updated' changes
2843 if (edited_timestamp_is_newer($r[0], $datarray)) {
2845 // do not accept (ignore) an earlier edit than one we currently have.
2846 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2849 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2850 dbesc($datarray['title']),
2851 dbesc($datarray['body']),
2852 dbesc($datarray['tag']),
2853 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2854 dbesc(datetime_convert()),
2856 intval($importer['uid'])
2858 create_tags_from_itemuri($item_id, $importer['uid']);
2859 update_thread_uri($item_id, $importer['uid']);
2862 // update last-child if it changes
2864 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2865 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2866 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2867 intval($allow[0]['data']),
2868 dbesc(datetime_convert()),
2870 intval($importer['uid'])
2872 update_thread_uri($item_id, $importer['uid']);
2877 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2878 logger('consume-feed: New follower');
2879 new_follower($importer,$contact,$datarray,$item);
2882 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2883 lose_follower($importer,$contact,$datarray,$item);
2887 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2888 logger('consume-feed: New friend request');
2889 new_follower($importer,$contact,$datarray,$item,true);
2892 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2893 lose_sharer($importer,$contact,$datarray,$item);
2898 if(! is_array($contact))
2902 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2903 // one way feed - no remote comment ability
2904 $datarray['last-child'] = 0;
2906 if($contact['network'] === NETWORK_FEED)
2907 $datarray['private'] = 2;
2909 $datarray['parent-uri'] = $item_id;
2910 $datarray['uid'] = $importer['uid'];
2911 $datarray['contact-id'] = $contact['id'];
2913 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2914 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2915 // but otherwise there's a possible data mixup on the sender's system.
2916 // the tgroup delivery code called from item_store will correct it if it's a forum,
2917 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2918 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2919 $datarray['owner-name'] = $contact['name'];
2920 $datarray['owner-link'] = $contact['url'];
2921 $datarray['owner-avatar'] = $contact['thumb'];
2924 // We've allowed "followers" to reach this point so we can decide if they are
2925 // posting an @-tag delivery, which followers are allowed to do for certain
2926 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2928 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2931 // This is my contact on another system, but it's really me.
2932 // Turn this into a wall post.
2933 $notify = item_is_remote_self($contact, $datarray);
2935 $r = item_store($datarray, false, $notify);
2936 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2944 function item_is_remote_self($contact, &$datarray) {
2947 if (!$contact['remote_self'])
2950 // Prevent the forwarding of posts that are forwarded
2951 if ($datarray["extid"] == NETWORK_DFRN)
2954 // Prevent to forward already forwarded posts
2955 if ($datarray["app"] == $a->get_hostname())
2958 // Only forward posts
2959 if ($datarray["verb"] != ACTIVITY_POST)
2962 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2965 $datarray2 = $datarray;
2966 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2967 if ($contact['remote_self'] == 2) {
2968 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2969 intval($contact['uid']));
2971 $datarray['contact-id'] = $r[0]["id"];
2973 $datarray['owner-name'] = $r[0]["name"];
2974 $datarray['owner-link'] = $r[0]["url"];
2975 $datarray['owner-avatar'] = $r[0]["thumb"];
2977 $datarray['author-name'] = $datarray['owner-name'];
2978 $datarray['author-link'] = $datarray['owner-link'];
2979 $datarray['author-avatar'] = $datarray['owner-avatar'];
2982 if ($contact['network'] != NETWORK_FEED) {
2983 $datarray["guid"] = get_guid(32);
2984 unset($datarray["plink"]);
2985 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
2986 $datarray["parent-uri"] = $datarray["uri"];
2987 $datarray["extid"] = $contact['network'];
2988 $urlpart = parse_url($datarray2['author-link']);
2989 $datarray["app"] = $urlpart["host"];
2991 $datarray['private'] = 0;
2994 if ($contact['network'] != NETWORK_FEED) {
2995 // Store the original post
2996 $r = item_store($datarray2, false, false);
2997 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2999 $datarray["app"] = "Feed";
3004 function local_delivery($importer,$data) {
3007 logger(__function__, LOGGER_TRACE);
3009 if($importer['readonly']) {
3010 // We aren't receiving stuff from this person. But we will quietly ignore them
3011 // rather than a blatant "go away" message.
3012 logger('local_delivery: ignoring');
3017 // Consume notification feed. This may differ from consuming a public feed in several ways
3018 // - might contain email or friend suggestions
3019 // - might contain remote followup to our message
3020 // - in which case we need to accept it and then notify other conversants
3021 // - we may need to send various email notifications
3023 $feed = new SimplePie();
3024 $feed->set_raw_data($data);
3025 $feed->enable_order_by_date(false);
3030 logger('local_delivery: Error parsing XML: ' . $feed->error());
3033 // Check at the feed level for updated contact name and/or photo
3037 $photo_timestamp = '';
3039 $contact_updated = '';
3042 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
3044 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
3046 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
3049 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
3050 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
3051 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
3052 $new_name = $elems['name'][0]['data'];
3054 // Manually checking for changed contact names
3055 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
3056 $name_updated = date("c");
3057 $photo_timestamp = date("c");
3060 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
3061 if ($photo_timestamp == "")
3062 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
3063 $photo_url = $elems['link'][0]['attribs']['']['href'];
3067 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3069 $contact_updated = $photo_timestamp;
3071 logger('local_delivery: Updating photo for ' . $importer['name']);
3072 require_once("include/Photo.php");
3073 $photo_failure = false;
3074 $have_photo = false;
3076 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
3077 intval($importer['id']),
3078 intval($importer['importer_uid'])
3081 $resource_id = $r[0]['resource-id'];
3085 $resource_id = photo_new_resource();
3088 $img_str = fetch_url($photo_url,true);
3089 // guess mimetype from headers or filename
3090 $type = guess_image_type($photo_url,true);
3093 $img = new Photo($img_str, $type);
3094 if($img->is_valid()) {
3096 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
3097 dbesc($resource_id),
3098 intval($importer['id']),
3099 intval($importer['importer_uid'])
3103 $img->scaleImageSquare(175);
3105 $hash = $resource_id;
3106 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
3108 $img->scaleImage(80);
3109 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
3111 $img->scaleImage(48);
3112 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3116 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3117 WHERE `uid` = %d AND `id` = %d",
3118 dbesc(datetime_convert()),
3119 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3120 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3121 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3122 intval($importer['importer_uid']),
3123 intval($importer['id'])
3128 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3129 if ($name_updated > $contact_updated)
3130 $contact_updated = $name_updated;
3132 $r = q("select * from contact where uid = %d and id = %d limit 1",
3133 intval($importer['importer_uid']),
3134 intval($importer['id'])
3137 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3138 dbesc(notags(trim($new_name))),
3139 dbesc(datetime_convert()),
3140 intval($importer['importer_uid']),
3141 intval($importer['id'])
3144 // do our best to update the name on content items
3147 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3148 dbesc(notags(trim($new_name))),
3149 dbesc($r[0]['name']),
3150 dbesc($r[0]['url']),
3151 intval($importer['importer_uid'])
3156 if ($contact_updated AND $new_name AND $photo_url)
3157 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3159 // Currently unsupported - needs a lot of work
3160 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3161 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3162 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3164 $newloc['uid'] = $importer['importer_uid'];
3165 $newloc['cid'] = $importer['id'];
3166 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3167 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3168 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3169 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3170 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3171 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3172 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3173 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3174 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3175 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3176 /** relocated user must have original key pair */
3177 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3178 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3180 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3183 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3184 intval($importer['id']),
3185 intval($importer['importer_uid']));
3190 $x = q("UPDATE contact SET
3201 `site-pubkey` = '%s'
3202 WHERE id=%d AND uid=%d;",
3203 dbesc($newloc['name']),
3204 dbesc($newloc['photo']),
3205 dbesc($newloc['thumb']),
3206 dbesc($newloc['micro']),
3207 dbesc($newloc['url']),
3208 dbesc(normalise_link($newloc['url'])),
3209 dbesc($newloc['request']),
3210 dbesc($newloc['confirm']),
3211 dbesc($newloc['notify']),
3212 dbesc($newloc['poll']),
3213 dbesc($newloc['sitepubkey']),
3214 intval($importer['id']),
3215 intval($importer['importer_uid']));
3221 'owner-link' => array($old['url'], $newloc['url']),
3222 'author-link' => array($old['url'], $newloc['url']),
3223 'owner-avatar' => array($old['photo'], $newloc['photo']),
3224 'author-avatar' => array($old['photo'], $newloc['photo']),
3226 foreach ($fields as $n=>$f){
3227 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3230 intval($importer['importer_uid']));
3236 // merge with current record, current contents have priority
3237 // update record, set url-updated
3238 // update profile photos
3244 // handle friend suggestion notification
3246 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3247 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3248 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3250 $fsugg['uid'] = $importer['importer_uid'];
3251 $fsugg['cid'] = $importer['id'];
3252 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3253 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3254 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3255 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3256 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3258 // Does our member already have a friend matching this description?
3260 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3261 dbesc($fsugg['name']),
3262 dbesc(normalise_link($fsugg['url'])),
3263 intval($fsugg['uid'])
3268 // Do we already have an fcontact record for this person?
3271 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3272 dbesc($fsugg['url']),
3273 dbesc($fsugg['name']),
3274 dbesc($fsugg['request'])
3279 // OK, we do. Do we already have an introduction for this person ?
3280 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3281 intval($fsugg['uid']),
3288 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3289 dbesc($fsugg['name']),
3290 dbesc($fsugg['url']),
3291 dbesc($fsugg['photo']),
3292 dbesc($fsugg['request'])
3294 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3295 dbesc($fsugg['url']),
3296 dbesc($fsugg['name']),
3297 dbesc($fsugg['request'])
3302 // database record did not get created. Quietly give up.
3307 $hash = random_string();
3309 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3310 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3311 intval($fsugg['uid']),
3313 intval($fsugg['cid']),
3314 dbesc($fsugg['body']),
3316 dbesc(datetime_convert()),
3321 'type' => NOTIFY_SUGGEST,
3322 'notify_flags' => $importer['notify-flags'],
3323 'language' => $importer['language'],
3324 'to_name' => $importer['username'],
3325 'to_email' => $importer['email'],
3326 'uid' => $importer['importer_uid'],
3328 'link' => $a->get_baseurl() . '/notifications/intros',
3329 'source_name' => $importer['name'],
3330 'source_link' => $importer['url'],
3331 'source_photo' => $importer['photo'],
3332 'verb' => ACTIVITY_REQ_FRIEND,
3341 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3342 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3344 logger('local_delivery: private message received');
3347 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3350 $msg['uid'] = $importer['importer_uid'];
3351 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3352 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3353 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3354 $msg['contact-id'] = $importer['id'];
3355 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3356 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3358 $msg['replied'] = 0;
3359 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3360 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3361 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3365 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3366 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3368 // send notifications.
3370 require_once('include/enotify.php');
3372 $notif_params = array(
3373 'type' => NOTIFY_MAIL,
3374 'notify_flags' => $importer['notify-flags'],
3375 'language' => $importer['language'],
3376 'to_name' => $importer['username'],
3377 'to_email' => $importer['email'],
3378 'uid' => $importer['importer_uid'],
3380 'source_name' => $msg['from-name'],
3381 'source_link' => $importer['url'],
3382 'source_photo' => $importer['thumb'],
3383 'verb' => ACTIVITY_POST,
3387 notification($notif_params);
3393 $community_page = 0;
3394 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3396 $community_page = intval($rawtags[0]['data']);
3398 if(intval($importer['forum']) != $community_page) {
3399 q("update contact set forum = %d where id = %d",
3400 intval($community_page),
3401 intval($importer['id'])
3403 $importer['forum'] = (string) $community_page;
3406 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3408 // process any deleted entries
3410 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3411 if(is_array($del_entries) && count($del_entries)) {
3412 foreach($del_entries as $dentry) {
3414 if(isset($dentry['attribs']['']['ref'])) {
3415 $uri = $dentry['attribs']['']['ref'];
3417 if(isset($dentry['attribs']['']['when'])) {
3418 $when = $dentry['attribs']['']['when'];
3419 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3422 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3426 // check for relayed deletes to our conversation
3429 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3431 intval($importer['importer_uid'])
3434 $parent_uri = $r[0]['parent-uri'];
3435 if($r[0]['id'] != $r[0]['parent'])
3442 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3445 logger('local_delivery: possible community delete');
3448 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3450 // was the top-level post for this reply written by somebody on this site?
3451 // Specifically, the recipient?
3453 $is_a_remote_delete = false;
3455 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3456 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3457 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3458 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3459 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3460 AND `item`.`uid` = %d
3466 intval($importer['importer_uid'])
3469 $is_a_remote_delete = true;
3471 // Does this have the characteristics of a community or private group comment?
3472 // If it's a reply to a wall post on a community/prvgroup page it's a
3473 // valid community comment. Also forum_mode makes it valid for sure.
3474 // If neither, it's not.
3476 if($is_a_remote_delete && $community) {
3477 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3478 $is_a_remote_delete = false;
3479 logger('local_delivery: not a community delete');
3483 if($is_a_remote_delete) {
3484 logger('local_delivery: received remote delete');
3488 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3489 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3491 intval($importer['importer_uid']),
3492 intval($importer['id'])
3498 if($item['deleted'])
3501 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3503 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3504 $xo = parse_xml_string($item['object'],false);
3505 $xt = parse_xml_string($item['target'],false);
3507 if($xt->type === ACTIVITY_OBJ_NOTE) {
3508 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3510 intval($importer['importer_uid'])
3514 // For tags, the owner cannot remove the tag on the author's copy of the post.
3516 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3517 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3518 $author_copy = (($item['origin']) ? true : false);
3520 if($owner_remove && $author_copy)
3522 if($author_remove || $owner_remove) {
3523 $tags = explode(',',$i[0]['tag']);
3526 foreach($tags as $tag)
3527 if(trim($tag) !== trim($xo->body))
3528 $newtags[] = trim($tag);
3530 q("update item set tag = '%s' where id = %d",
3531 dbesc(implode(',',$newtags)),
3534 create_tags_from_item($i[0]['id']);
3540 if($item['uri'] == $item['parent-uri']) {
3541 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3542 `body` = '', `title` = ''
3543 WHERE `parent-uri` = '%s' AND `uid` = %d",
3545 dbesc(datetime_convert()),
3546 dbesc($item['uri']),
3547 intval($importer['importer_uid'])
3549 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3550 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3551 update_thread_uri($item['uri'], $importer['importer_uid']);
3554 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3555 `body` = '', `title` = ''
3556 WHERE `uri` = '%s' AND `uid` = %d",
3558 dbesc(datetime_convert()),
3560 intval($importer['importer_uid'])
3562 create_tags_from_itemuri($uri, $importer['importer_uid']);
3563 create_files_from_itemuri($uri, $importer['importer_uid']);
3564 update_thread_uri($uri, $importer['importer_uid']);
3565 if($item['last-child']) {
3566 // ensure that last-child is set in case the comment that had it just got wiped.
3567 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3568 dbesc(datetime_convert()),
3569 dbesc($item['parent-uri']),
3570 intval($item['uid'])
3572 // who is the last child now?
3573 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3574 ORDER BY `created` DESC LIMIT 1",
3575 dbesc($item['parent-uri']),
3576 intval($importer['importer_uid'])
3579 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3584 // if this is a relayed delete, propagate it to other recipients
3586 if($is_a_remote_delete)
3587 proc_run('php',"include/notifier.php","drop",$item['id']);
3595 foreach($feed->get_items() as $item) {
3598 $item_id = $item->get_id();
3599 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3600 if(isset($rawthread[0]['attribs']['']['ref'])) {
3602 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3608 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3611 logger('local_delivery: possible community reply');
3614 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3616 // was the top-level post for this reply written by somebody on this site?
3617 // Specifically, the recipient?
3619 $is_a_remote_comment = false;
3620 $top_uri = $parent_uri;
3622 $r = q("select `item`.`parent-uri` from `item`
3623 WHERE `item`.`uri` = '%s'
3627 if($r && count($r)) {
3628 $top_uri = $r[0]['parent-uri'];
3630 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3631 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3632 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3633 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3634 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3635 AND `item`.`uid` = %d
3641 intval($importer['importer_uid'])
3644 $is_a_remote_comment = true;
3647 // Does this have the characteristics of a community or private group comment?
3648 // If it's a reply to a wall post on a community/prvgroup page it's a
3649 // valid community comment. Also forum_mode makes it valid for sure.
3650 // If neither, it's not.
3652 if($is_a_remote_comment && $community) {
3653 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3654 $is_a_remote_comment = false;
3655 logger('local_delivery: not a community reply');
3659 if($is_a_remote_comment) {
3660 logger('local_delivery: received remote comment');
3662 // remote reply to our post. Import and then notify everybody else.
3664 $datarray = get_atom_elements($feed, $item);
3666 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3668 intval($importer['importer_uid'])
3671 // Update content if 'updated' changes
3675 if (edited_timestamp_is_newer($r[0], $datarray)) {
3677 // do not accept (ignore) an earlier edit than one we currently have.
3678 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3681 logger('received updated comment' , LOGGER_DEBUG);
3682 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3683 dbesc($datarray['title']),
3684 dbesc($datarray['body']),
3685 dbesc($datarray['tag']),
3686 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3687 dbesc(datetime_convert()),
3689 intval($importer['importer_uid'])
3691 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3693 proc_run('php',"include/notifier.php","comment-import",$iid);
3702 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3703 intval($importer['importer_uid'])
3707 $datarray['type'] = 'remote-comment';
3708 $datarray['wall'] = 1;
3709 $datarray['parent-uri'] = $parent_uri;
3710 $datarray['uid'] = $importer['importer_uid'];
3711 $datarray['owner-name'] = $own[0]['name'];
3712 $datarray['owner-link'] = $own[0]['url'];
3713 $datarray['owner-avatar'] = $own[0]['thumb'];
3714 $datarray['contact-id'] = $importer['id'];
3716 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3718 $datarray['type'] = 'activity';
3719 $datarray['gravity'] = GRAVITY_LIKE;
3720 $datarray['last-child'] = 0;
3721 // only one like or dislike per person
3722 // splitted into two queries for performance issues
3723 $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`parent-uri` = '%s') and deleted = 0 limit 1",
3724 intval($datarray['uid']),
3725 intval($datarray['contact-id']),
3726 dbesc($datarray['verb']),
3727 dbesc($datarray['parent-uri'])
3733 $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`thr-parent` = '%s') and deleted = 0 limit 1",
3734 intval($datarray['uid']),
3735 intval($datarray['contact-id']),
3736 dbesc($datarray['verb']),
3737 dbesc($datarray['parent-uri'])
3744 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3746 $xo = parse_xml_string($datarray['object'],false);
3747 $xt = parse_xml_string($datarray['target'],false);
3749 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3751 // fetch the parent item
3753 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3755 intval($importer['importer_uid'])
3760 // extract tag, if not duplicate, and this user allows tags, add to parent item
3762 if($xo->id && $xo->content) {
3763 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3764 if(! (stristr($tagp[0]['tag'],$newtag))) {
3765 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3766 intval($importer['importer_uid'])
3768 if(count($i) && ! intval($i[0]['blocktags'])) {
3769 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3770 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3771 intval($tagp[0]['id']),
3772 dbesc(datetime_convert()),
3773 dbesc(datetime_convert())
3775 create_tags_from_item($tagp[0]['id']);
3783 $posted_id = item_store($datarray);
3788 $datarray["id"] = $posted_id;
3790 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3792 intval($importer['importer_uid'])
3795 $parent = $r[0]['parent'];
3796 $parent_uri = $r[0]['parent-uri'];
3800 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3801 dbesc(datetime_convert()),
3802 intval($importer['importer_uid']),
3803 intval($r[0]['parent'])
3806 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3807 dbesc(datetime_convert()),
3808 intval($importer['importer_uid']),
3813 if($posted_id && $parent) {
3815 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3817 if((! $is_like) && (! $importer['self'])) {
3819 require_once('include/enotify.php');
3822 'type' => NOTIFY_COMMENT,
3823 'notify_flags' => $importer['notify-flags'],
3824 'language' => $importer['language'],
3825 'to_name' => $importer['username'],
3826 'to_email' => $importer['email'],
3827 'uid' => $importer['importer_uid'],
3828 'item' => $datarray,
3829 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3830 'source_name' => stripslashes($datarray['author-name']),
3831 'source_link' => $datarray['author-link'],
3832 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3833 ? $importer['thumb'] : $datarray['author-avatar']),
3834 'verb' => ACTIVITY_POST,
3836 'parent' => $parent,
3837 'parent_uri' => $parent_uri,
3849 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3851 $item_id = $item->get_id();
3852 $datarray = get_atom_elements($feed,$item);
3854 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3857 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3859 intval($importer['importer_uid'])
3862 // Update content if 'updated' changes
3865 if (edited_timestamp_is_newer($r[0], $datarray)) {
3867 // do not accept (ignore) an earlier edit than one we currently have.
3868 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3871 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3872 dbesc($datarray['title']),
3873 dbesc($datarray['body']),
3874 dbesc($datarray['tag']),
3875 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3876 dbesc(datetime_convert()),
3878 intval($importer['importer_uid'])
3880 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3883 // update last-child if it changes
3885 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3886 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3887 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3888 dbesc(datetime_convert()),
3890 intval($importer['importer_uid'])
3892 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3893 intval($allow[0]['data']),
3894 dbesc(datetime_convert()),
3896 intval($importer['importer_uid'])
3902 $datarray['parent-uri'] = $parent_uri;
3903 $datarray['uid'] = $importer['importer_uid'];
3904 $datarray['contact-id'] = $importer['id'];
3905 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3906 $datarray['type'] = 'activity';
3907 $datarray['gravity'] = GRAVITY_LIKE;
3908 // only one like or dislike per person
3909 // splitted into two queries for performance issues
3910 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s') limit 1",
3911 intval($datarray['uid']),
3912 intval($datarray['contact-id']),
3913 dbesc($datarray['verb']),
3919 $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`thr-parent` = '%s') limit 1",
3920 intval($datarray['uid']),
3921 intval($datarray['contact-id']),
3922 dbesc($datarray['verb']),
3930 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3932 $xo = parse_xml_string($datarray['object'],false);
3933 $xt = parse_xml_string($datarray['target'],false);
3935 if($xt->type == ACTIVITY_OBJ_NOTE) {
3936 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3938 intval($importer['importer_uid'])
3943 // extract tag, if not duplicate, add to parent item
3945 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3946 q("UPDATE item SET tag = '%s' WHERE id = %d",
3947 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3950 create_tags_from_item($r[0]['id']);
3956 $posted_id = item_store($datarray);
3958 // find out if our user is involved in this conversation and wants to be notified.
3960 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3962 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3964 intval($importer['importer_uid'])
3967 if(count($myconv)) {
3968 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3970 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3971 if(! link_compare($datarray['author-link'],$importer_url)) {
3974 foreach($myconv as $conv) {
3976 // now if we find a match, it means we're in this conversation
3978 if(! link_compare($conv['author-link'],$importer_url))
3981 require_once('include/enotify.php');
3983 $conv_parent = $conv['parent'];
3986 'type' => NOTIFY_COMMENT,
3987 'notify_flags' => $importer['notify-flags'],
3988 'language' => $importer['language'],
3989 'to_name' => $importer['username'],
3990 'to_email' => $importer['email'],
3991 'uid' => $importer['importer_uid'],
3992 'item' => $datarray,
3993 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3994 'source_name' => stripslashes($datarray['author-name']),
3995 'source_link' => $datarray['author-link'],
3996 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3997 ? $importer['thumb'] : $datarray['author-avatar']),
3998 'verb' => ACTIVITY_POST,
4000 'parent' => $conv_parent,
4001 'parent_uri' => $parent_uri
4005 // only send one notification
4017 // Head post of a conversation. Have we seen it? If not, import it.
4020 $item_id = $item->get_id();
4021 $datarray = get_atom_elements($feed,$item);
4023 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
4024 $ev = bbtoevent($datarray['body']);
4025 if(x($ev,'desc') && x($ev,'start')) {
4026 $ev['cid'] = $importer['id'];
4027 $ev['uid'] = $importer['uid'];
4028 $ev['uri'] = $item_id;
4029 $ev['edited'] = $datarray['edited'];
4030 $ev['private'] = $datarray['private'];
4032 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4034 intval($importer['uid'])
4037 $ev['id'] = $r[0]['id'];
4038 $xyz = event_store($ev);
4043 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4045 intval($importer['importer_uid'])
4048 // Update content if 'updated' changes
4051 if (edited_timestamp_is_newer($r[0], $datarray)) {
4053 // do not accept (ignore) an earlier edit than one we currently have.
4054 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4057 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4058 dbesc($datarray['title']),
4059 dbesc($datarray['body']),
4060 dbesc($datarray['tag']),
4061 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4062 dbesc(datetime_convert()),
4064 intval($importer['importer_uid'])
4066 create_tags_from_itemuri($item_id, $importer['importer_uid']);
4067 update_thread_uri($item_id, $importer['importer_uid']);
4070 // update last-child if it changes
4072 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4073 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4074 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4075 intval($allow[0]['data']),
4076 dbesc(datetime_convert()),
4078 intval($importer['importer_uid'])
4084 $datarray['parent-uri'] = $item_id;
4085 $datarray['uid'] = $importer['importer_uid'];
4086 $datarray['contact-id'] = $importer['id'];
4089 if(! link_compare($datarray['owner-link'],$importer['url'])) {
4090 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4091 // but otherwise there's a possible data mixup on the sender's system.
4092 // the tgroup delivery code called from item_store will correct it if it's a forum,
4093 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4094 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4095 $datarray['owner-name'] = $importer['senderName'];
4096 $datarray['owner-link'] = $importer['url'];
4097 $datarray['owner-avatar'] = $importer['thumb'];
4100 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4103 // This is my contact on another system, but it's really me.
4104 // Turn this into a wall post.
4105 $notify = item_is_remote_self($importer, $datarray);
4107 $posted_id = item_store($datarray, false, $notify);
4109 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4110 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4113 $xo = parse_xml_string($datarray['object'],false);
4115 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4117 // somebody was poked/prodded. Was it me?
4119 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4121 foreach($links->link as $l) {
4122 $atts = $l->attributes();
4123 switch($atts['rel']) {
4125 $Blink = $atts['href'];
4131 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4133 // send a notification
4134 require_once('include/enotify.php');
4137 'type' => NOTIFY_POKE,
4138 'notify_flags' => $importer['notify-flags'],
4139 'language' => $importer['language'],
4140 'to_name' => $importer['username'],
4141 'to_email' => $importer['email'],
4142 'uid' => $importer['importer_uid'],
4143 'item' => $datarray,
4144 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4145 'source_name' => stripslashes($datarray['author-name']),
4146 'source_link' => $datarray['author-link'],
4147 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4148 ? $importer['thumb'] : $datarray['author-avatar']),
4149 'verb' => $datarray['verb'],
4150 'otype' => 'person',
4151 'activity' => $verb,
4152 'parent' => $datarray['parent']
4168 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4169 $url = notags(trim($datarray['author-link']));
4170 $name = notags(trim($datarray['author-name']));
4171 $photo = notags(trim($datarray['author-avatar']));
4173 if (is_object($item)) {
4174 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4175 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4176 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4180 if(is_array($contact)) {
4181 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4182 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4183 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4184 intval(CONTACT_IS_FRIEND),
4185 intval($contact['id']),
4186 intval($importer['uid'])
4189 // send email notification to owner?
4193 // create contact record
4195 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4196 `blocked`, `readonly`, `pending`, `writable` )
4197 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4198 intval($importer['uid']),
4199 dbesc(datetime_convert()),
4201 dbesc(normalise_link($url)),
4205 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4206 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4208 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4209 intval($importer['uid']),
4213 $contact_record = $r[0];
4215 // create notification
4216 $hash = random_string();
4218 if(is_array($contact_record)) {
4219 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4220 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4221 intval($importer['uid']),
4222 intval($contact_record['id']),
4224 dbesc(datetime_convert())
4228 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4229 intval($importer['uid'])
4234 if(intval($r[0]['def_gid'])) {
4235 require_once('include/group.php');
4236 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4239 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4240 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4243 'type' => NOTIFY_INTRO,
4244 'notify_flags' => $r[0]['notify-flags'],
4245 'language' => $r[0]['language'],
4246 'to_name' => $r[0]['username'],
4247 'to_email' => $r[0]['email'],
4248 'uid' => $r[0]['uid'],
4249 'link' => $a->get_baseurl() . '/notifications/intro',
4250 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4251 'source_link' => $contact_record['url'],
4252 'source_photo' => $contact_record['photo'],
4253 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4262 function lose_follower($importer,$contact,$datarray,$item) {
4264 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4265 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4266 intval(CONTACT_IS_SHARING),
4267 intval($contact['id'])
4271 contact_remove($contact['id']);
4275 function lose_sharer($importer,$contact,$datarray,$item) {
4277 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4278 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4279 intval(CONTACT_IS_FOLLOWER),
4280 intval($contact['id'])
4284 contact_remove($contact['id']);
4289 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4293 if(is_array($importer)) {
4294 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4295 intval($importer['uid'])
4299 // Diaspora has different message-ids in feeds than they do
4300 // through the direct Diaspora protocol. If we try and use
4301 // the feed, we'll get duplicates. So don't.
4303 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4306 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4308 // Use a single verify token, even if multiple hubs
4310 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4312 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4314 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4316 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
4317 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4318 dbesc($verify_token),
4319 intval($contact['id'])
4323 post_url($url,$params);
4325 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4332 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4336 $name = xmlify($name);
4337 $uri = xmlify($uri);
4340 $photo = xmlify($photo);
4344 $o .= "\t<name>$name</name>\r\n";
4345 $o .= "\t<uri>$uri</uri>\r\n";
4346 $o .= "\t".'<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4347 $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4349 if ($tag == "author") {
4350 $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
4351 `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
4352 `profile`.`homepage`,`contact`.`nick` FROM `profile`
4353 INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
4354 INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
4355 WHERE `profile`.`is-default` AND `contact`.`self` AND
4356 NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
4357 dbesc(normalise_link($uri)));
4360 if($r[0]['locality'])
4361 $location .= $r[0]['locality'];
4362 if($r[0]['region']) {
4365 $location .= $r[0]['region'];
4367 if($r[0]['country-name']) {
4370 $location .= $r[0]['country-name'];
4373 $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
4374 $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
4375 $o .= "\t<poco:note>".xmlify($r[0]["about"])."</poco:note>\r\n";
4376 $o .= "\t<poco:address>\r\n";
4377 $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
4378 $o .= "\t</poco:address>\r\n";
4379 $o .= "\t<poco:urls>\r\n";
4380 $o .= "\t<poco:type>homepage</poco:type>\r\n";
4381 $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
4382 $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
4383 $o .= "\t</poco:urls>\r\n";
4387 call_hooks('atom_author', $o);
4389 $o .= "</$tag>\r\n";
4393 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4397 if(! $item['parent'])
4400 if($item['deleted'])
4401 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4404 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4405 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4407 $body = $item['body'];
4410 $o = "\r\n\r\n<entry>\r\n";
4412 if(is_array($author))
4413 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4415 $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']));
4416 if(strlen($item['owner-name']))
4417 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4419 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4420 $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
4421 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4422 $o .= '<thr:in-reply-to ref="'.xmlify($parent_item).'" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$parent[0]['guid']).'" />'."\r\n";
4427 if ($item['title'] != "")
4428 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4430 $htmlbody = bbcode($htmlbody, false, false, 7);
4432 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4433 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4434 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4435 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4436 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4437 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4438 $o .= '<link rel="alternate" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$item['guid']).'" />'."\r\n";
4440 $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
4443 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4445 if($item['location']) {
4446 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4447 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4451 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4453 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4454 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4457 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4458 if($item['bookmark'])
4459 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4462 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4465 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4467 if($item['signed_text']) {
4468 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4469 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4472 $verb = construct_verb($item);
4473 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4474 $actobj = construct_activity_object($item);
4477 $actarg = construct_activity_target($item);
4481 $tags = item_getfeedtags($item);
4483 foreach($tags as $t)
4484 if (($type != 'html') OR ($t[0] != "@"))
4485 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4489 // To support these elements, the API needs to be enhanced
4490 //$o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
4491 //$o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4492 //$o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4494 $o .= item_get_attachment($item);
4496 $o .= item_getfeedattach($item);
4498 $mentioned = get_mentions($item);
4502 call_hooks('atom_entry', $o);
4504 $o .= '</entry>' . "\r\n";
4509 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4511 if(get_config('system','disable_embedded'))
4516 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4517 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4522 $img_start = strpos($orig_body, '[img');
4523 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4524 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4525 while( ($img_st_close !== false) && ($img_len !== false) ) {
4527 $img_st_close++; // make it point to AFTER the closing bracket
4528 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4530 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4533 if(stristr($image , $site . '/photo/')) {
4534 // Only embed locally hosted photos
4536 $i = basename($image);
4537 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4538 $x = strpos($i,'-');
4541 $res = substr($i,$x+1);
4542 $i = substr($i,0,$x);
4543 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4550 // Check to see if we should replace this photo link with an embedded image
4551 // 1. No need to do so if the photo is public
4552 // 2. If there's a contact-id provided, see if they're in the access list
4553 // for the photo. If so, embed it.
4554 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4555 // permissions, regardless of order but first check to see if they're an exact
4556 // match to save some processing overhead.
4558 if(has_permissions($r[0])) {
4560 $recips = enumerate_permissions($r[0]);
4561 if(in_array($cid, $recips)) {
4566 if(compare_permissions($item,$r[0]))
4571 $data = $r[0]['data'];
4572 $type = $r[0]['type'];
4574 // If a custom width and height were specified, apply before embedding
4575 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4576 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4578 $width = intval($match[1]);
4579 $height = intval($match[2]);
4581 $ph = new Photo($data, $type);
4582 if($ph->is_valid()) {
4583 $ph->scaleImage(max($width, $height));
4584 $data = $ph->imageString();
4585 $type = $ph->getType();
4589 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4590 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4591 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4597 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4598 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4599 if($orig_body === false)
4602 $img_start = strpos($orig_body, '[img');
4603 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4604 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4607 $new_body = $new_body . $orig_body;
4613 function has_permissions($obj) {
4614 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4619 function compare_permissions($obj1,$obj2) {
4620 // first part is easy. Check that these are exactly the same.
4621 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4622 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4623 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4624 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4627 // This is harder. Parse all the permissions and compare the resulting set.
4629 $recipients1 = enumerate_permissions($obj1);
4630 $recipients2 = enumerate_permissions($obj2);
4633 if($recipients1 == $recipients2)
4638 // returns an array of contact-ids that are allowed to see this object
4640 function enumerate_permissions($obj) {
4641 require_once('include/group.php');
4642 $allow_people = expand_acl($obj['allow_cid']);
4643 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4644 $deny_people = expand_acl($obj['deny_cid']);
4645 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4646 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4647 $deny = array_unique(array_merge($deny_people,$deny_groups));
4648 $recipients = array_diff($recipients,$deny);
4652 function item_getfeedtags($item) {
4655 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4657 for($x = 0; $x < $cnt; $x ++) {
4659 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4663 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4665 for($x = 0; $x < $cnt; $x ++) {
4667 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4673 function item_get_attachment($item) {
4675 $siteinfo = get_attached_data($item["body"]);
4677 switch($siteinfo["type"]) {
4679 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4682 $imgdata = get_photo_info($siteinfo["image"]);
4683 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
4686 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4695 function item_getfeedattach($item) {
4697 $arr = explode('[/attach],',$item['attach']);
4699 foreach($arr as $r) {
4701 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4703 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4704 if(intval($matches[2]))
4705 $ret .= 'length="' . intval($matches[2]) . '" ';
4706 if($matches[4] !== ' ')
4707 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4708 $ret .= ' />' . "\r\n";
4717 function item_expire($uid, $days, $network = "", $force = false) {
4719 if((! $uid) || ($days < 1))
4722 // $expire_network_only = save your own wall posts
4723 // and just expire conversations started by others
4725 $expire_network_only = get_pconfig($uid,'expire','network_only');
4726 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4728 if ($network != "") {
4729 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4730 // There is an index "uid_network_received" but not "uid_network_created"
4731 // This avoids the creation of another index just for one purpose.
4732 // And it doesn't really matter wether to look at "received" or "created"
4733 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4735 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4737 $r = q("SELECT * FROM `item`
4738 WHERE `uid` = %d $range
4749 $expire_items = get_pconfig($uid, 'expire','items');
4750 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4752 // Forcing expiring of items - but not notes and marked items
4754 $expire_items = true;
4756 $expire_notes = get_pconfig($uid, 'expire','notes');
4757 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4759 $expire_starred = get_pconfig($uid, 'expire','starred');
4760 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4762 $expire_photos = get_pconfig($uid, 'expire','photos');
4763 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4765 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4767 foreach($r as $item) {
4769 // don't expire filed items
4771 if(strpos($item['file'],'[') !== false)
4774 // Only expire posts, not photos and photo comments
4776 if($expire_photos==0 && strlen($item['resource-id']))
4778 if($expire_starred==0 && intval($item['starred']))
4780 if($expire_notes==0 && $item['type']=='note')
4782 if($expire_items==0 && $item['type']!='note')
4785 drop_item($item['id'],false);
4788 proc_run('php',"include/notifier.php","expire","$uid");
4793 function drop_items($items) {
4796 if(! local_user() && ! remote_user())
4800 foreach($items as $item) {
4801 $owner = drop_item($item,false);
4802 if($owner && ! $uid)
4807 // multiple threads may have been deleted, send an expire notification
4810 proc_run('php',"include/notifier.php","expire","$uid");
4814 function drop_item($id,$interactive = true) {
4818 // locate item to be deleted
4820 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4827 notice( t('Item not found.') . EOL);
4828 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4833 $owner = $item['uid'];
4837 // check if logged in user is either the author or owner of this item
4839 if(is_array($_SESSION['remote'])) {
4840 foreach($_SESSION['remote'] as $visitor) {
4841 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4842 $cid = $visitor['cid'];
4849 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4851 // Check if we should do HTML-based delete confirmation
4852 if($_REQUEST['confirm']) {
4853 // <form> can't take arguments in its "action" parameter
4854 // so add any arguments as hidden inputs
4855 $query = explode_querystring($a->query_string);
4857 foreach($query['args'] as $arg) {
4858 if(strpos($arg, 'confirm=') === false) {
4859 $arg_parts = explode('=', $arg);
4860 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4864 return replace_macros(get_markup_template('confirm.tpl'), array(
4866 '$message' => t('Do you really want to delete this item?'),
4867 '$extra_inputs' => $inputs,
4868 '$confirm' => t('Yes'),
4869 '$confirm_url' => $query['base'],
4870 '$confirm_name' => 'confirmed',
4871 '$cancel' => t('Cancel'),
4874 // Now check how the user responded to the confirmation query
4875 if($_REQUEST['canceled']) {
4876 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4879 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4882 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4883 dbesc(datetime_convert()),
4884 dbesc(datetime_convert()),
4887 create_tags_from_item($item['id']);
4888 create_files_from_item($item['id']);
4889 delete_thread($item['id'], $item['parent-uri']);
4891 // clean up categories and tags so they don't end up as orphans
4894 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4896 foreach($matches as $mtch) {
4897 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4903 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4905 foreach($matches as $mtch) {
4906 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4910 // If item is a link to a photo resource, nuke all the associated photos
4911 // (visitors will not have photo resources)
4912 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4913 // generate a resource-id and therefore aren't intimately linked to the item.
4915 if(strlen($item['resource-id'])) {
4916 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4917 dbesc($item['resource-id']),
4918 intval($item['uid'])
4920 // ignore the result
4923 // If item is a link to an event, nuke the event record.
4925 if(intval($item['event-id'])) {
4926 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4927 intval($item['event-id']),
4928 intval($item['uid'])
4930 // ignore the result
4933 // If item has attachments, drop them
4935 foreach(explode(",",$item['attach']) as $attach){
4936 preg_match("|attach/(\d+)|", $attach, $matches);
4937 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4938 intval($matches[1]),
4941 // ignore the result
4945 // clean up item_id and sign meta-data tables
4948 // Old code - caused very long queries and warning entries in the mysql logfiles:
4950 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4951 intval($item['id']),
4952 intval($item['uid'])
4955 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4956 intval($item['id']),
4957 intval($item['uid'])
4961 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4963 // Creating list of parents
4964 $r = q("select id from item where parent = %d and uid = %d",
4965 intval($item['id']),
4966 intval($item['uid'])
4971 foreach ($r AS $row) {
4972 if ($parentid != "")
4975 $parentid .= $row["id"];
4979 if ($parentid != "") {
4980 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4982 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4985 // If it's the parent of a comment thread, kill all the kids
4987 if($item['uri'] == $item['parent-uri']) {
4988 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4989 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4990 dbesc(datetime_convert()),
4991 dbesc(datetime_convert()),
4992 dbesc($item['parent-uri']),
4993 intval($item['uid'])
4995 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4996 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4997 delete_thread_uri($item['parent-uri'], $item['uid']);
4998 // ignore the result
5001 // ensure that last-child is set in case the comment that had it just got wiped.
5002 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
5003 dbesc(datetime_convert()),
5004 dbesc($item['parent-uri']),
5005 intval($item['uid'])
5007 // who is the last child now?
5008 $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",
5009 dbesc($item['parent-uri']),
5010 intval($item['uid'])
5013 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
5018 // Add a relayable_retraction signature for Diaspora.
5019 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
5022 $drop_id = intval($item['id']);
5024 // send the notification upstream/downstream as the case may be
5026 proc_run('php',"include/notifier.php","drop","$drop_id");
5030 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5036 notice( t('Permission denied.') . EOL);
5037 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5044 function first_post_date($uid,$wall = false) {
5045 $r = q("select id, created from item
5046 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
5048 order by created asc limit 1",
5050 intval($wall ? 1 : 0)
5053 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
5054 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
5059 /* modified posted_dates() {below} to arrange the list in years */
5060 function list_post_dates($uid, $wall) {
5061 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5063 $dthen = first_post_date($uid, $wall);
5067 // Set the start and end date to the beginning of the month
5068 $dnow = substr($dnow,0,8).'01';
5069 $dthen = substr($dthen,0,8).'01';
5073 // Starting with the current month, get the first and last days of every
5074 // month down to and including the month of the first post
5075 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5076 $dyear = intval(substr($dnow,0,4));
5077 $dstart = substr($dnow,0,8) . '01';
5078 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5079 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5080 $end_month = datetime_convert('','',$dend,'Y-m-d');
5081 $str = day_translate(datetime_convert('','',$dnow,'F'));
5083 $ret[$dyear] = array();
5084 $ret[$dyear][] = array($str,$end_month,$start_month);
5085 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5090 function posted_dates($uid,$wall) {
5091 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5093 $dthen = first_post_date($uid,$wall);
5097 // Set the start and end date to the beginning of the month
5098 $dnow = substr($dnow,0,8).'01';
5099 $dthen = substr($dthen,0,8).'01';
5102 // Starting with the current month, get the first and last days of every
5103 // month down to and including the month of the first post
5104 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5105 $dstart = substr($dnow,0,8) . '01';
5106 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5107 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5108 $end_month = datetime_convert('','',$dend,'Y-m-d');
5109 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5110 $ret[] = array($str,$end_month,$start_month);
5111 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5117 function posted_date_widget($url,$uid,$wall) {
5120 if(! feature_enabled($uid,'archives'))
5123 // For former Facebook folks that left because of "timeline"
5125 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5128 $visible_years = get_pconfig($uid,'system','archive_visible_years');
5129 if(! $visible_years)
5132 $ret = list_post_dates($uid,$wall);
5137 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5138 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5140 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5141 '$title' => t('Archives'),
5142 '$size' => $visible_years,
5143 '$cutoff_year' => $cutoff_year,
5144 '$cutoff' => $cutoff,
5147 '$showmore' => t('show more')
5153 function store_diaspora_retract_sig($item, $user, $baseurl) {
5154 // Note that we can't add a target_author_signature
5155 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5156 // the comment, that means we're the home of the post, and Diaspora will only
5157 // check the parent_author_signature of retractions that it doesn't have to relay further
5159 // I don't think this function gets called for an "unlike," but I'll check anyway
5161 $enabled = intval(get_config('system','diaspora_enabled'));
5163 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5167 logger('drop_item: storing diaspora retraction signature');
5169 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5171 if(local_user() == $item['uid']) {
5173 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5174 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5177 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5178 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5181 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5182 // only handles DFRN deletes
5183 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5184 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5185 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5191 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5192 intval($item['id']),
5193 dbesc($signed_text),