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 // Adds a "lang" specification in a "postopts" element of given $arr,
1100 // if possible and not already present.
1101 // Expects "body" element to exist in $arr.
1102 // TODO: add a parameter to request forcing override
1103 function item_add_language_opt(&$arr) {
1105 if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
1107 if ( $arr['postopts'] )
1109 if ( strstr($arr['postopts'], 'lang=') )
1112 // TODO: add parameter to request overriding
1115 $postopts = $arr['postopts'];
1122 require_once('library/langdet/Text/LanguageDetect.php');
1123 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1124 $l = new Text_LanguageDetect;
1125 //$lng = $l->detectConfidence($naked_body);
1126 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1127 $lng = $l->detect($naked_body, 3);
1129 if (sizeof($lng) > 0) {
1130 if ($postopts) $postopts .= '^'; // arbitrary separator, to be reviewed
1131 $postopts .= 'lang=';
1133 foreach ($lng as $language => $score) {
1134 $postopts .= $sep . $language.";".$score;
1137 $arr['postopts'] = $postopts;
1141 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1143 // If it is a posting where users should get notifications, then define it as wall posting
1146 $arr['type'] = 'wall';
1148 $arr['last-child'] = 1;
1149 $arr['network'] = NETWORK_DFRN;
1152 // If a Diaspora signature structure was passed in, pull it out of the
1153 // item array and set it aside for later storage.
1156 if(x($arr,'dsprsig')) {
1157 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1158 unset($arr['dsprsig']);
1161 // Converting the plink
1162 if ($arr['network'] == NETWORK_OSTATUS) {
1163 if (isset($arr['plink']))
1164 $arr['plink'] = ostatus_convert_href($arr['plink']);
1165 elseif (isset($arr['uri']))
1166 $arr['plink'] = ostatus_convert_href($arr['uri']);
1169 if(x($arr, 'gravity'))
1170 $arr['gravity'] = intval($arr['gravity']);
1171 elseif($arr['parent-uri'] === $arr['uri'])
1172 $arr['gravity'] = 0;
1173 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1174 $arr['gravity'] = 6;
1176 $arr['gravity'] = 6; // extensible catchall
1178 if(! x($arr,'type'))
1179 $arr['type'] = 'remote';
1183 /* check for create date and expire time */
1184 $uid = intval($arr['uid']);
1185 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
1187 $expire_interval = $r[0]['expire'];
1188 if ($expire_interval>0) {
1189 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1190 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1191 if ($created_date < $expire_date) {
1192 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1198 // If there is no guid then take the same guid that was taken before for the same uri
1199 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
1200 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1201 $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
1202 dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
1205 $arr['guid'] = $r[0]["guid"];
1206 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1210 // If there is no guid then take the same guid that was taken before for the same plink
1211 if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
1212 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
1213 $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
1214 dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
1217 $arr['guid'] = $r[0]["guid"];
1218 logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
1220 if ($r[0]["uri"] != $arr['uri'])
1221 logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
1225 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1226 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1227 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1228 // $arr['body'] = strip_tags($arr['body']);
1230 item_add_language_opt($arr);
1232 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1233 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $arr['network']));
1234 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
1235 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1236 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1237 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1238 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1239 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1240 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1241 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1242 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1243 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1244 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1245 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1246 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1247 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1248 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1249 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1250 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1251 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1252 $arr['deleted'] = 0;
1253 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1254 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1255 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1256 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1257 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1258 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1259 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1260 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1261 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1262 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1263 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1264 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1265 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1266 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1267 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1268 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1269 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1270 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1271 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1272 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1273 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1274 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1275 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1276 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1278 if ($arr['plink'] == "") {
1280 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1283 if ($arr['network'] == "") {
1284 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1285 intval($arr['contact-id']),
1290 $arr['network'] = $r[0]["network"];
1292 // Fallback to friendica (why is it empty in some cases?)
1293 if ($arr['network'] == "")
1294 $arr['network'] = NETWORK_DFRN;
1296 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1299 if ($arr['guid'] != "") {
1300 // Checking if there is already an item with the same guid
1301 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1302 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1303 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1306 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1311 // Check for hashtags in the body and repair or add hashtag links
1312 item_body_set_hashtags($arr);
1314 $arr['thr-parent'] = $arr['parent-uri'];
1315 if($arr['parent-uri'] === $arr['uri']) {
1317 $parent_deleted = 0;
1318 $allow_cid = $arr['allow_cid'];
1319 $allow_gid = $arr['allow_gid'];
1320 $deny_cid = $arr['deny_cid'];
1321 $deny_gid = $arr['deny_gid'];
1322 $notify_type = 'wall-new';
1326 // find the parent and snarf the item id and ACLs
1327 // and anything else we need to inherit
1329 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1330 dbesc($arr['parent-uri']),
1336 // is the new message multi-level threaded?
1337 // even though we don't support it now, preserve the info
1338 // and re-attach to the conversation parent.
1340 if($r[0]['uri'] != $r[0]['parent-uri']) {
1341 $arr['parent-uri'] = $r[0]['parent-uri'];
1342 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1343 ORDER BY `id` ASC LIMIT 1",
1344 dbesc($r[0]['parent-uri']),
1345 dbesc($r[0]['parent-uri']),
1352 $parent_id = $r[0]['id'];
1353 $parent_deleted = $r[0]['deleted'];
1354 $allow_cid = $r[0]['allow_cid'];
1355 $allow_gid = $r[0]['allow_gid'];
1356 $deny_cid = $r[0]['deny_cid'];
1357 $deny_gid = $r[0]['deny_gid'];
1358 $arr['wall'] = $r[0]['wall'];
1359 $notify_type = 'comment-new';
1361 // if the parent is private, force privacy for the entire conversation
1362 // This differs from the above settings as it subtly allows comments from
1363 // email correspondents to be private even if the overall thread is not.
1365 if($r[0]['private'])
1366 $arr['private'] = $r[0]['private'];
1368 // Edge case. We host a public forum that was originally posted to privately.
1369 // The original author commented, but as this is a comment, the permissions
1370 // weren't fixed up so it will still show the comment as private unless we fix it here.
1372 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1373 $arr['private'] = 0;
1376 // If its a post from myself then tag the thread as "mention"
1377 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1378 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1381 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1382 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1383 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1384 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1385 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1391 // Allow one to see reply tweets from status.net even when
1392 // we don't have or can't see the original post.
1395 logger('item_store: $force_parent=true, reply converted to top-level post.');
1397 $arr['parent-uri'] = $arr['uri'];
1398 $arr['gravity'] = 0;
1401 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1405 $parent_deleted = 0;
1409 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1411 dbesc($arr['network']),
1414 if($r && count($r)) {
1415 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1419 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1420 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1421 dbesc($arr['body']),
1422 dbesc($arr['network']),
1423 dbesc($arr['created']),
1424 intval($arr['contact-id']),
1427 if($r && count($r)) {
1428 logger('duplicated item with the same body found. ' . print_r($arr,true));
1432 // Is this item available in the global items (with uid=0)?
1433 if ($arr["uid"] == 0) {
1434 $arr["global"] = true;
1436 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1438 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1440 $arr["global"] = (count($isglobal) > 0);
1443 // Fill the cache field
1444 put_item_in_cache($arr);
1446 call_hooks('post_remote',$arr);
1448 if(x($arr,'cancel')) {
1449 logger('item_store: post cancelled by plugin.');
1453 // Store the unescaped version
1458 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1460 $r = dbq("INSERT INTO `item` (`"
1461 . implode("`, `", array_keys($arr))
1463 . implode("', '", array_values($arr))
1469 // find the item we just created
1470 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1477 // Store the guid and other relevant data
1480 $current_post = $r[0]['id'];
1481 logger('item_store: created item ' . $current_post);
1483 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1484 // This can be used to filter for inactive contacts.
1485 // Only do this for public postings to avoid privacy problems, since poco data is public.
1486 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1488 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1490 // Is it a forum? Then we don't care about the rules from above
1491 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1492 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1493 intval($arr['contact-id']));
1499 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1500 dbesc($arr['received']),
1501 dbesc($arr['received']),
1502 intval($arr['contact-id'])
1505 logger('item_store: could not locate created item');
1509 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1510 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1512 intval($arr['uid']),
1513 intval($current_post)
1517 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1518 $parent_id = $current_post;
1520 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1523 $private = $arr['private'];
1525 // Set parent id - and also make sure to inherit the parent's ACLs.
1527 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1528 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1535 intval($parent_deleted),
1536 intval($current_post)
1539 $arr['id'] = $current_post;
1540 $arr['parent'] = $parent_id;
1541 $arr['allow_cid'] = $allow_cid;
1542 $arr['allow_gid'] = $allow_gid;
1543 $arr['deny_cid'] = $deny_cid;
1544 $arr['deny_gid'] = $deny_gid;
1545 $arr['private'] = $private;
1546 $arr['deleted'] = $parent_deleted;
1548 // update the commented timestamp on the parent
1549 // Only update "commented" if it is really a comment
1550 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1551 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1552 dbesc(datetime_convert()),
1553 dbesc(datetime_convert()),
1557 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1558 dbesc(datetime_convert()),
1563 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1564 intval($current_post),
1565 dbesc($dsprsig->signed_text),
1566 dbesc($dsprsig->signature),
1567 dbesc($dsprsig->signer)
1573 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1576 if($arr['last-child']) {
1577 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1579 intval($arr['uid']),
1580 intval($current_post)
1584 $deleted = tag_deliver($arr['uid'],$current_post);
1586 // current post can be deleted if is for a community page and no mention are
1588 if (!$deleted AND !$dontcache) {
1590 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1591 if (count($r) == 1) {
1592 call_hooks('post_remote_end', $r[0]);
1594 logger('item_store: new item not found in DB, id ' . $current_post);
1597 // Add every contact of the post to the global contact table
1600 create_tags_from_item($current_post);
1601 create_files_from_item($current_post);
1603 // Only check for notifications on start posts
1604 if ($arr['parent-uri'] === $arr['uri']) {
1605 add_thread($current_post);
1606 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1608 // Send a notification for every new post?
1609 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1610 intval($arr['contact-id']),
1613 $send_notification = count($r);
1615 if (!$send_notification) {
1616 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1617 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1620 foreach ($tags AS $tag) {
1621 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1622 normalise_link($tag["url"]), intval($arr['uid']));
1624 $send_notification = true;
1629 if ($send_notification) {
1630 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1631 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1632 intval($arr['uid']));
1634 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1635 intval($current_post),
1641 require_once('include/enotify.php');
1643 'type' => NOTIFY_SHARE,
1644 'notify_flags' => $u[0]['notify-flags'],
1645 'language' => $u[0]['language'],
1646 'to_name' => $u[0]['username'],
1647 'to_email' => $u[0]['email'],
1648 'uid' => $u[0]['uid'],
1650 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1651 'source_name' => $item[0]['author-name'],
1652 'source_link' => $item[0]['author-link'],
1653 'source_photo' => $item[0]['author-avatar'],
1654 'verb' => ACTIVITY_TAG,
1656 'parent' => $arr['parent']
1658 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1661 update_thread($parent_id);
1662 add_shadow_entry($arr);
1666 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1668 return $current_post;
1671 function item_body_set_hashtags(&$item) {
1673 $tags = get_tags($item["body"]);
1679 // This sorting is important when there are hashtags that are part of other hashtags
1680 // Otherwise there could be problems with hashtags like #test and #test2
1685 $URLSearchString = "^\[\]";
1687 // All hashtags should point to the home server
1688 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1689 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1691 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1692 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1694 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1695 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1697 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1700 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1702 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1705 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1707 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1710 // Repair recursive urls
1711 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1712 "#$2", $item["body"]);
1715 foreach($tags as $tag) {
1716 if(strpos($tag,'#') !== 0)
1719 if(strpos($tag,'[url='))
1722 $basetag = str_replace('_',' ',substr($tag,1));
1724 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1726 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1728 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1729 if(strlen($item["tag"]))
1730 $item["tag"] = ','.$item["tag"];
1731 $item["tag"] = $newtag.$item["tag"];
1735 // Convert back the masked hashtags
1736 $item["body"] = str_replace("#", "#", $item["body"]);
1739 function get_item_guid($id) {
1740 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1742 return($r[0]["guid"]);
1747 function get_item_id($guid, $uid = 0) {
1753 $uid == local_user();
1755 // Does the given user have this item?
1757 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1758 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1759 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1762 $nick = $r[0]["nickname"];
1766 // Or is it anywhere on the server?
1768 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1769 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1770 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1771 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1772 AND `item`.`private` = 0 AND `item`.`wall` = 1
1773 AND `item`.`guid` = '%s'", dbesc($guid));
1776 $nick = $r[0]["nickname"];
1779 return(array("nick" => $nick, "id" => $id));
1783 function get_item_contact($item,$contacts) {
1784 if(! count($contacts) || (! is_array($item)))
1786 foreach($contacts as $contact) {
1787 if($contact['id'] == $item['contact-id']) {
1789 break; // NOTREACHED
1796 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1798 * @param int $item_id
1799 * @return bool true if item was deleted, else false
1801 function tag_deliver($uid,$item_id) {
1809 $u = q("select * from user where uid = %d limit 1",
1815 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1816 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1819 $i = q("select * from item where id = %d and uid = %d limit 1",
1828 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1830 // Diaspora uses their own hardwired link URL in @-tags
1831 // instead of the one we supply with webfinger
1833 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1835 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1837 foreach($matches as $mtch) {
1838 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1840 logger('tag_deliver: mention found: ' . $mtch[2]);
1846 if ( ($community_page || $prvgroup) &&
1847 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1848 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1850 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1851 q("DELETE FROM item WHERE id = %d and uid = %d",
1861 // send a notification
1863 // use a local photo if we have one
1865 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1866 intval($u[0]['uid']),
1867 dbesc(normalise_link($item['author-link']))
1869 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1872 require_once('include/enotify.php');
1874 'type' => NOTIFY_TAGSELF,
1875 'notify_flags' => $u[0]['notify-flags'],
1876 'language' => $u[0]['language'],
1877 'to_name' => $u[0]['username'],
1878 'to_email' => $u[0]['email'],
1879 'uid' => $u[0]['uid'],
1881 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1882 'source_name' => $item['author-name'],
1883 'source_link' => $item['author-link'],
1884 'source_photo' => $photo,
1885 'verb' => ACTIVITY_TAG,
1887 'parent' => $item['parent']
1891 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1893 call_hooks('tagged', $arr);
1895 if((! $community_page) && (! $prvgroup))
1899 // tgroup delivery - setup a second delivery chain
1900 // prevent delivery looping - only proceed
1901 // if the message originated elsewhere and is a top-level post
1903 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1906 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1909 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1910 intval($u[0]['uid'])
1915 // also reset all the privacy bits to the forum default permissions
1917 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1919 $forum_mode = (($prvgroup) ? 2 : 1);
1921 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1922 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1923 intval($forum_mode),
1924 dbesc($c[0]['name']),
1925 dbesc($c[0]['url']),
1926 dbesc($c[0]['thumb']),
1928 dbesc($u[0]['allow_cid']),
1929 dbesc($u[0]['allow_gid']),
1930 dbesc($u[0]['deny_cid']),
1931 dbesc($u[0]['deny_gid']),
1934 update_thread($item_id);
1936 proc_run('php','include/notifier.php','tgroup',$item_id);
1942 function tgroup_check($uid,$item) {
1948 // check that the message originated elsewhere and is a top-level post
1950 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1954 $u = q("select * from user where uid = %d limit 1",
1960 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1961 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1964 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1966 // Diaspora uses their own hardwired link URL in @-tags
1967 // instead of the one we supply with webfinger
1969 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1971 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1973 foreach($matches as $mtch) {
1974 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1976 logger('tgroup_check: mention found: ' . $mtch[2]);
1984 if((! $community_page) && (! $prvgroup))
1998 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
2002 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
2004 if($contact['duplex'] && $contact['dfrn-id'])
2005 $idtosend = '0:' . $orig_id;
2006 if($contact['duplex'] && $contact['issued-id'])
2007 $idtosend = '1:' . $orig_id;
2010 $rino = get_config('system','rino_encrypt');
2011 $rino = intval($rino);
2013 logger("Local rino version: ". $rino, LOGGER_DEBUG);
2015 $ssl_val = intval(get_config('system','ssl_policy'));
2019 case SSL_POLICY_FULL:
2020 $ssl_policy = 'full';
2022 case SSL_POLICY_SELFSIGN:
2023 $ssl_policy = 'self';
2025 case SSL_POLICY_NONE:
2027 $ssl_policy = 'none';
2031 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
2033 logger('dfrn_deliver: ' . $url);
2035 $xml = fetch_url($url);
2037 $curl_stat = $a->get_curl_code();
2039 return(-1); // timed out
2041 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2046 if(strpos($xml,'<?xml') === false) {
2047 logger('dfrn_deliver: no valid XML returned');
2048 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2052 $res = parse_xml_string($xml);
2054 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2055 return (($res->status) ? $res->status : 3);
2057 $postvars = array();
2058 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2059 $challenge = hex2bin((string) $res->challenge);
2060 $perm = (($res->perm) ? $res->perm : null);
2061 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2062 $rino_remote_version = intval($res->rino);
2063 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2065 logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG);
2067 if($owner['page-flags'] == PAGE_PRVGROUP)
2070 $final_dfrn_id = '';
2073 if((($perm == 'rw') && (! intval($contact['writable'])))
2074 || (($perm == 'r') && (intval($contact['writable'])))) {
2075 q("update contact set writable = %d where id = %d",
2076 intval(($perm == 'rw') ? 1 : 0),
2077 intval($contact['id'])
2079 $contact['writable'] = (string) 1 - intval($contact['writable']);
2083 if(($contact['duplex'] && strlen($contact['pubkey']))
2084 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2085 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2086 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2087 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2090 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2091 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2094 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2096 if(strpos($final_dfrn_id,':') == 1)
2097 $final_dfrn_id = substr($final_dfrn_id,2);
2099 if($final_dfrn_id != $orig_id) {
2100 logger('dfrn_deliver: wrong dfrn_id.');
2101 // did not decode properly - cannot trust this site
2105 $postvars['dfrn_id'] = $idtosend;
2106 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2108 $postvars['dissolve'] = '1';
2111 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2112 $postvars['data'] = $atom;
2113 $postvars['perm'] = 'rw';
2116 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2117 $postvars['perm'] = 'r';
2120 $postvars['ssl_policy'] = $ssl_policy;
2123 $postvars['page'] = $page;
2126 if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
2127 logger('rino version: '. $rino_remote_version);
2129 switch($rino_remote_version) {
2131 // Deprecated rino version!
2132 $key = substr(random_string(),0,16);
2133 $data = aes_encrypt($postvars['data'],$key);
2136 // RINO 2 based on php-encryption
2138 $key = Crypto::createNewRandomKey();
2139 } catch (CryptoTestFailed $ex) {
2140 logger('Cannot safely create a key');
2142 } catch (CannotPerformOperation $ex) {
2143 logger('Cannot safely create a key');
2147 $data = Crypto::encrypt($postvars['data'], $key);
2148 } catch (CryptoTestFailed $ex) {
2149 logger('Cannot safely perform encryption');
2151 } catch (CannotPerformOperation $ex) {
2152 logger('Cannot safely perform encryption');
2157 logger("rino: invalid requested verision '$rino_remote_version'");
2161 $postvars['rino'] = $rino_remote_version;
2162 $postvars['data'] = bin2hex($data);
2164 #logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2167 if($dfrn_version >= 2.1) {
2168 if(($contact['duplex'] && strlen($contact['pubkey']))
2169 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2170 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2172 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2175 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2179 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2180 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2183 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2187 logger('md5 rawkey ' . md5($postvars['key']));
2189 $postvars['key'] = bin2hex($postvars['key']);
2193 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2195 $xml = post_url($contact['notify'],$postvars);
2197 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2199 $curl_stat = $a->get_curl_code();
2200 if((! $curl_stat) || (! strlen($xml)))
2201 return(-1); // timed out
2203 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2206 if(strpos($xml,'<?xml') === false) {
2207 logger('dfrn_deliver: phase 2: no valid XML returned');
2208 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2212 if($contact['term-date'] != '0000-00-00 00:00:00') {
2213 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2214 require_once('include/Contact.php');
2215 unmark_for_death($contact);
2218 $res = parse_xml_string($xml);
2220 return $res->status;
2225 This function returns true if $update has an edited timestamp newer
2226 than $existing, i.e. $update contains new data which should override
2227 what's already there. If there is no timestamp yet, the update is
2228 assumed to be newer. If the update has no timestamp, the existing
2229 item is assumed to be up-to-date. If the timestamps are equal it
2230 assumes the update has been seen before and should be ignored.
2232 function edited_timestamp_is_newer($existing, $update) {
2233 if (!x($existing,'edited') || !$existing['edited']) {
2236 if (!x($update,'edited') || !$update['edited']) {
2239 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2240 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2241 return (strcmp($existing_edited, $update_edited) < 0);
2246 * consume_feed - process atom feed and update anything/everything we might need to update
2248 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2250 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2251 * It is this person's stuff that is going to be updated.
2252 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2253 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2254 * have a contact record.
2255 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2256 * might not) try and subscribe to it.
2257 * $datedir sorts in reverse order
2258 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2259 * imported prior to its children being seen in the stream unless we are certain
2260 * of how the feed is arranged/ordered.
2261 * With $pass = 1, we only pull parent items out of the stream.
2262 * With $pass = 2, we only pull children (comments/likes).
2264 * So running this twice, first with pass 1 and then with pass 2 will do the right
2265 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2266 * model where comments can have sub-threads. That would require some massive sorting
2267 * to get all the feed items into a mostly linear ordering, and might still require
2271 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2272 if ($contact['network'] === NETWORK_OSTATUS) {
2274 // Test - remove before flight
2275 //$tempfile = tempnam(get_temppath(), "ostatus");
2276 //file_put_contents($tempfile, $xml);
2278 logger("Consume OStatus messages ", LOGGER_DEBUG);
2279 ostatus_import($xml,$importer,$contact, $hub);
2284 require_once('library/simplepie/simplepie.inc');
2285 require_once('include/contact_selectors.php');
2287 if(! strlen($xml)) {
2288 logger('consume_feed: empty input');
2292 $feed = new SimplePie();
2293 $feed->set_raw_data($xml);
2295 $feed->enable_order_by_date(true);
2297 $feed->enable_order_by_date(false);
2301 logger('consume_feed: Error parsing XML: ' . $feed->error());
2303 $permalink = $feed->get_permalink();
2305 // Check at the feed level for updated contact name and/or photo
2309 $photo_timestamp = '';
2312 $contact_updated = '';
2314 $hubs = $feed->get_links('hub');
2315 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2318 $hub = implode(',', $hubs);
2320 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2322 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2324 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2325 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2326 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2327 $new_name = $elems['name'][0]['data'];
2329 // Manually checking for changed contact names
2330 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2331 $name_updated = date("c");
2332 $photo_timestamp = date("c");
2335 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2336 if ($photo_timestamp == "")
2337 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2338 $photo_url = $elems['link'][0]['attribs']['']['href'];
2341 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2342 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2346 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2347 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2349 $contact_updated = $photo_timestamp;
2351 require_once("include/Photo.php");
2352 $photo_failure = false;
2353 $have_photo = false;
2355 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2356 intval($contact['id']),
2357 intval($contact['uid'])
2360 $resource_id = $r[0]['resource-id'];
2364 $resource_id = photo_new_resource();
2367 $img_str = fetch_url($photo_url,true);
2368 // guess mimetype from headers or filename
2369 $type = guess_image_type($photo_url,true);
2372 $img = new Photo($img_str, $type);
2373 if($img->is_valid()) {
2375 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2376 dbesc($resource_id),
2377 intval($contact['id']),
2378 intval($contact['uid'])
2382 $img->scaleImageSquare(175);
2384 $hash = $resource_id;
2385 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2387 $img->scaleImage(80);
2388 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2390 $img->scaleImage(48);
2391 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2395 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2396 WHERE `uid` = %d AND `id` = %d",
2397 dbesc(datetime_convert()),
2398 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2399 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2400 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2401 intval($contact['uid']),
2402 intval($contact['id'])
2407 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2408 if ($name_updated > $contact_updated)
2409 $contact_updated = $name_updated;
2411 $r = q("select * from contact where uid = %d and id = %d limit 1",
2412 intval($contact['uid']),
2413 intval($contact['id'])
2416 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2417 dbesc(notags(trim($new_name))),
2418 dbesc(datetime_convert()),
2419 intval($contact['uid']),
2420 intval($contact['id'])
2423 // do our best to update the name on content items
2426 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2427 dbesc(notags(trim($new_name))),
2428 dbesc($r[0]['name']),
2429 dbesc($r[0]['url']),
2430 intval($contact['uid'])
2435 if ($contact_updated AND $new_name AND $photo_url)
2436 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2438 if(strlen($birthday)) {
2439 if(substr($birthday,0,4) != $contact['bdyear']) {
2440 logger('consume_feed: updating birthday: ' . $birthday);
2444 * Add new birthday event for this person
2446 * $bdtext is just a readable placeholder in case the event is shared
2447 * with others. We will replace it during presentation to our $importer
2448 * to contain a sparkle link and perhaps a photo.
2452 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2453 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2456 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2457 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2458 intval($contact['uid']),
2459 intval($contact['id']),
2460 dbesc(datetime_convert()),
2461 dbesc(datetime_convert()),
2462 dbesc(datetime_convert('UTC','UTC', $birthday)),
2463 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2472 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2473 dbesc(substr($birthday,0,4)),
2474 intval($contact['uid']),
2475 intval($contact['id'])
2478 // This function is called twice without reloading the contact
2479 // Make sure we only create one event. This is why &$contact
2480 // is a reference var in this function
2482 $contact['bdyear'] = substr($birthday,0,4);
2486 $community_page = 0;
2487 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2489 $community_page = intval($rawtags[0]['data']);
2491 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2492 q("update contact set forum = %d where id = %d",
2493 intval($community_page),
2494 intval($contact['id'])
2496 $contact['forum'] = (string) $community_page;
2500 // process any deleted entries
2502 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2503 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2504 foreach($del_entries as $dentry) {
2506 if(isset($dentry['attribs']['']['ref'])) {
2507 $uri = $dentry['attribs']['']['ref'];
2509 if(isset($dentry['attribs']['']['when'])) {
2510 $when = $dentry['attribs']['']['when'];
2511 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2514 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2516 if($deleted && is_array($contact)) {
2517 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2518 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2520 intval($importer['uid']),
2521 intval($contact['id'])
2526 if(! $item['deleted'])
2527 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2529 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2530 $xo = parse_xml_string($item['object'],false);
2531 $xt = parse_xml_string($item['target'],false);
2532 if($xt->type === ACTIVITY_OBJ_NOTE) {
2533 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2535 intval($importer['importer_uid'])
2539 // For tags, the owner cannot remove the tag on the author's copy of the post.
2541 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2542 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2543 $author_copy = (($item['origin']) ? true : false);
2545 if($owner_remove && $author_copy)
2547 if($author_remove || $owner_remove) {
2548 $tags = explode(',',$i[0]['tag']);
2551 foreach($tags as $tag)
2552 if(trim($tag) !== trim($xo->body))
2553 $newtags[] = trim($tag);
2555 q("update item set tag = '%s' where id = %d",
2556 dbesc(implode(',',$newtags)),
2559 create_tags_from_item($i[0]['id']);
2565 if($item['uri'] == $item['parent-uri']) {
2566 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2567 `body` = '', `title` = ''
2568 WHERE `parent-uri` = '%s' AND `uid` = %d",
2570 dbesc(datetime_convert()),
2571 dbesc($item['uri']),
2572 intval($importer['uid'])
2574 create_tags_from_itemuri($item['uri'], $importer['uid']);
2575 create_files_from_itemuri($item['uri'], $importer['uid']);
2576 update_thread_uri($item['uri'], $importer['uid']);
2579 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2580 `body` = '', `title` = ''
2581 WHERE `uri` = '%s' AND `uid` = %d",
2583 dbesc(datetime_convert()),
2585 intval($importer['uid'])
2587 create_tags_from_itemuri($uri, $importer['uid']);
2588 create_files_from_itemuri($uri, $importer['uid']);
2589 if($item['last-child']) {
2590 // ensure that last-child is set in case the comment that had it just got wiped.
2591 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2592 dbesc(datetime_convert()),
2593 dbesc($item['parent-uri']),
2594 intval($item['uid'])
2596 // who is the last child now?
2597 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2598 ORDER BY `created` DESC LIMIT 1",
2599 dbesc($item['parent-uri']),
2600 intval($importer['uid'])
2603 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2614 // Now process the feed
2616 if($feed->get_item_quantity()) {
2618 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2620 // in inverse date order
2622 $items = array_reverse($feed->get_items());
2624 $items = $feed->get_items();
2627 foreach($items as $item) {
2630 $item_id = $item->get_id();
2631 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2632 if(isset($rawthread[0]['attribs']['']['ref'])) {
2634 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2637 if(($is_reply) && is_array($contact)) {
2642 // not allowed to post
2644 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2648 // Have we seen it? If not, import it.
2650 $item_id = $item->get_id();
2651 $datarray = get_atom_elements($feed, $item, $contact);
2653 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2654 $datarray['author-name'] = $contact['name'];
2655 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2656 $datarray['author-link'] = $contact['url'];
2657 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2658 $datarray['author-avatar'] = $contact['thumb'];
2660 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2661 logger('consume_feed: no author information! ' . print_r($datarray,true));
2665 $force_parent = false;
2666 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2667 if($contact['network'] === NETWORK_OSTATUS)
2668 $force_parent = true;
2669 if(strlen($datarray['title']))
2670 unset($datarray['title']);
2671 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2672 dbesc(datetime_convert()),
2674 intval($importer['uid'])
2676 $datarray['last-child'] = 1;
2677 update_thread_uri($parent_uri, $importer['uid']);
2681 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2683 intval($importer['uid'])
2686 // Update content if 'updated' changes
2689 if (edited_timestamp_is_newer($r[0], $datarray)) {
2691 // do not accept (ignore) an earlier edit than one we currently have.
2692 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2695 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2696 dbesc($datarray['title']),
2697 dbesc($datarray['body']),
2698 dbesc($datarray['tag']),
2699 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2700 dbesc(datetime_convert()),
2702 intval($importer['uid'])
2704 create_tags_from_itemuri($item_id, $importer['uid']);
2705 update_thread_uri($item_id, $importer['uid']);
2708 // update last-child if it changes
2710 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2711 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2712 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2713 dbesc(datetime_convert()),
2715 intval($importer['uid'])
2717 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2718 intval($allow[0]['data']),
2719 dbesc(datetime_convert()),
2721 intval($importer['uid'])
2723 update_thread_uri($item_id, $importer['uid']);
2729 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2730 // one way feed - no remote comment ability
2731 $datarray['last-child'] = 0;
2733 $datarray['parent-uri'] = $parent_uri;
2734 $datarray['uid'] = $importer['uid'];
2735 $datarray['contact-id'] = $contact['id'];
2736 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2737 $datarray['type'] = 'activity';
2738 $datarray['gravity'] = GRAVITY_LIKE;
2739 // only one like or dislike per person
2740 // splitted into two queries for performance issues
2741 $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",
2742 intval($datarray['uid']),
2743 intval($datarray['contact-id']),
2744 dbesc($datarray['verb']),
2750 $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",
2751 intval($datarray['uid']),
2752 intval($datarray['contact-id']),
2753 dbesc($datarray['verb']),
2760 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2761 $xo = parse_xml_string($datarray['object'],false);
2762 $xt = parse_xml_string($datarray['target'],false);
2764 if($xt->type == ACTIVITY_OBJ_NOTE) {
2765 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2767 intval($importer['importer_uid'])
2772 // extract tag, if not duplicate, add to parent item
2773 if($xo->id && $xo->content) {
2774 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2775 if(! (stristr($r[0]['tag'],$newtag))) {
2776 q("UPDATE item SET tag = '%s' WHERE id = %d",
2777 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2780 create_tags_from_item($r[0]['id']);
2786 $r = item_store($datarray,$force_parent);
2792 // Head post of a conversation. Have we seen it? If not, import it.
2794 $item_id = $item->get_id();
2796 $datarray = get_atom_elements($feed, $item, $contact);
2798 if(is_array($contact)) {
2799 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2800 $datarray['author-name'] = $contact['name'];
2801 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2802 $datarray['author-link'] = $contact['url'];
2803 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2804 $datarray['author-avatar'] = $contact['thumb'];
2807 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2808 logger('consume_feed: no author information! ' . print_r($datarray,true));
2812 // special handling for events
2814 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2815 $ev = bbtoevent($datarray['body']);
2816 if(x($ev,'desc') && x($ev,'start')) {
2817 $ev['uid'] = $importer['uid'];
2818 $ev['uri'] = $item_id;
2819 $ev['edited'] = $datarray['edited'];
2820 $ev['private'] = $datarray['private'];
2822 if(is_array($contact))
2823 $ev['cid'] = $contact['id'];
2824 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2826 intval($importer['uid'])
2829 $ev['id'] = $r[0]['id'];
2830 $xyz = event_store($ev);
2835 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2836 if(strlen($datarray['title']))
2837 unset($datarray['title']);
2838 $datarray['last-child'] = 1;
2842 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2844 intval($importer['uid'])
2847 // Update content if 'updated' changes
2850 if (edited_timestamp_is_newer($r[0], $datarray)) {
2852 // do not accept (ignore) an earlier edit than one we currently have.
2853 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2856 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2857 dbesc($datarray['title']),
2858 dbesc($datarray['body']),
2859 dbesc($datarray['tag']),
2860 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2861 dbesc(datetime_convert()),
2863 intval($importer['uid'])
2865 create_tags_from_itemuri($item_id, $importer['uid']);
2866 update_thread_uri($item_id, $importer['uid']);
2869 // update last-child if it changes
2871 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2872 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2873 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2874 intval($allow[0]['data']),
2875 dbesc(datetime_convert()),
2877 intval($importer['uid'])
2879 update_thread_uri($item_id, $importer['uid']);
2884 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2885 logger('consume-feed: New follower');
2886 new_follower($importer,$contact,$datarray,$item);
2889 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2890 lose_follower($importer,$contact,$datarray,$item);
2894 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2895 logger('consume-feed: New friend request');
2896 new_follower($importer,$contact,$datarray,$item,true);
2899 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2900 lose_sharer($importer,$contact,$datarray,$item);
2905 if(! is_array($contact))
2909 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2910 // one way feed - no remote comment ability
2911 $datarray['last-child'] = 0;
2913 if($contact['network'] === NETWORK_FEED)
2914 $datarray['private'] = 2;
2916 $datarray['parent-uri'] = $item_id;
2917 $datarray['uid'] = $importer['uid'];
2918 $datarray['contact-id'] = $contact['id'];
2920 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2921 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2922 // but otherwise there's a possible data mixup on the sender's system.
2923 // the tgroup delivery code called from item_store will correct it if it's a forum,
2924 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2925 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2926 $datarray['owner-name'] = $contact['name'];
2927 $datarray['owner-link'] = $contact['url'];
2928 $datarray['owner-avatar'] = $contact['thumb'];
2931 // We've allowed "followers" to reach this point so we can decide if they are
2932 // posting an @-tag delivery, which followers are allowed to do for certain
2933 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2935 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2938 // This is my contact on another system, but it's really me.
2939 // Turn this into a wall post.
2940 $notify = item_is_remote_self($contact, $datarray);
2942 $r = item_store($datarray, false, $notify);
2943 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2951 function item_is_remote_self($contact, &$datarray) {
2954 if (!$contact['remote_self'])
2957 // Prevent the forwarding of posts that are forwarded
2958 if ($datarray["extid"] == NETWORK_DFRN)
2961 // Prevent to forward already forwarded posts
2962 if ($datarray["app"] == $a->get_hostname())
2965 // Only forward posts
2966 if ($datarray["verb"] != ACTIVITY_POST)
2969 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2972 $datarray2 = $datarray;
2973 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2974 if ($contact['remote_self'] == 2) {
2975 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2976 intval($contact['uid']));
2978 $datarray['contact-id'] = $r[0]["id"];
2980 $datarray['owner-name'] = $r[0]["name"];
2981 $datarray['owner-link'] = $r[0]["url"];
2982 $datarray['owner-avatar'] = $r[0]["thumb"];
2984 $datarray['author-name'] = $datarray['owner-name'];
2985 $datarray['author-link'] = $datarray['owner-link'];
2986 $datarray['author-avatar'] = $datarray['owner-avatar'];
2989 if ($contact['network'] != NETWORK_FEED) {
2990 $datarray["guid"] = get_guid(32);
2991 unset($datarray["plink"]);
2992 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
2993 $datarray["parent-uri"] = $datarray["uri"];
2994 $datarray["extid"] = $contact['network'];
2995 $urlpart = parse_url($datarray2['author-link']);
2996 $datarray["app"] = $urlpart["host"];
2998 $datarray['private'] = 0;
3001 if ($contact['network'] != NETWORK_FEED) {
3002 // Store the original post
3003 $r = item_store($datarray2, false, false);
3004 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
3006 $datarray["app"] = "Feed";
3011 function local_delivery($importer,$data) {
3014 logger(__function__, LOGGER_TRACE);
3016 if($importer['readonly']) {
3017 // We aren't receiving stuff from this person. But we will quietly ignore them
3018 // rather than a blatant "go away" message.
3019 logger('local_delivery: ignoring');
3024 // Consume notification feed. This may differ from consuming a public feed in several ways
3025 // - might contain email or friend suggestions
3026 // - might contain remote followup to our message
3027 // - in which case we need to accept it and then notify other conversants
3028 // - we may need to send various email notifications
3030 $feed = new SimplePie();
3031 $feed->set_raw_data($data);
3032 $feed->enable_order_by_date(false);
3037 logger('local_delivery: Error parsing XML: ' . $feed->error());
3040 // Check at the feed level for updated contact name and/or photo
3044 $photo_timestamp = '';
3046 $contact_updated = '';
3049 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
3051 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
3053 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
3056 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
3057 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
3058 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
3059 $new_name = $elems['name'][0]['data'];
3061 // Manually checking for changed contact names
3062 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
3063 $name_updated = date("c");
3064 $photo_timestamp = date("c");
3067 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
3068 if ($photo_timestamp == "")
3069 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
3070 $photo_url = $elems['link'][0]['attribs']['']['href'];
3074 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3076 $contact_updated = $photo_timestamp;
3078 logger('local_delivery: Updating photo for ' . $importer['name']);
3079 require_once("include/Photo.php");
3080 $photo_failure = false;
3081 $have_photo = false;
3083 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
3084 intval($importer['id']),
3085 intval($importer['importer_uid'])
3088 $resource_id = $r[0]['resource-id'];
3092 $resource_id = photo_new_resource();
3095 $img_str = fetch_url($photo_url,true);
3096 // guess mimetype from headers or filename
3097 $type = guess_image_type($photo_url,true);
3100 $img = new Photo($img_str, $type);
3101 if($img->is_valid()) {
3103 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
3104 dbesc($resource_id),
3105 intval($importer['id']),
3106 intval($importer['importer_uid'])
3110 $img->scaleImageSquare(175);
3112 $hash = $resource_id;
3113 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
3115 $img->scaleImage(80);
3116 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
3118 $img->scaleImage(48);
3119 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3123 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3124 WHERE `uid` = %d AND `id` = %d",
3125 dbesc(datetime_convert()),
3126 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3127 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3128 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3129 intval($importer['importer_uid']),
3130 intval($importer['id'])
3135 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3136 if ($name_updated > $contact_updated)
3137 $contact_updated = $name_updated;
3139 $r = q("select * from contact where uid = %d and id = %d limit 1",
3140 intval($importer['importer_uid']),
3141 intval($importer['id'])
3144 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3145 dbesc(notags(trim($new_name))),
3146 dbesc(datetime_convert()),
3147 intval($importer['importer_uid']),
3148 intval($importer['id'])
3151 // do our best to update the name on content items
3154 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3155 dbesc(notags(trim($new_name))),
3156 dbesc($r[0]['name']),
3157 dbesc($r[0]['url']),
3158 intval($importer['importer_uid'])
3163 if ($contact_updated AND $new_name AND $photo_url)
3164 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3166 // Currently unsupported - needs a lot of work
3167 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3168 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3169 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3171 $newloc['uid'] = $importer['importer_uid'];
3172 $newloc['cid'] = $importer['id'];
3173 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3174 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3175 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3176 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3177 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3178 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3179 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3180 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3181 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3182 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3183 /** relocated user must have original key pair */
3184 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3185 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3187 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3190 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3191 intval($importer['id']),
3192 intval($importer['importer_uid']));
3197 $x = q("UPDATE contact SET
3208 `site-pubkey` = '%s'
3209 WHERE id=%d AND uid=%d;",
3210 dbesc($newloc['name']),
3211 dbesc($newloc['photo']),
3212 dbesc($newloc['thumb']),
3213 dbesc($newloc['micro']),
3214 dbesc($newloc['url']),
3215 dbesc(normalise_link($newloc['url'])),
3216 dbesc($newloc['request']),
3217 dbesc($newloc['confirm']),
3218 dbesc($newloc['notify']),
3219 dbesc($newloc['poll']),
3220 dbesc($newloc['sitepubkey']),
3221 intval($importer['id']),
3222 intval($importer['importer_uid']));
3228 'owner-link' => array($old['url'], $newloc['url']),
3229 'author-link' => array($old['url'], $newloc['url']),
3230 'owner-avatar' => array($old['photo'], $newloc['photo']),
3231 'author-avatar' => array($old['photo'], $newloc['photo']),
3233 foreach ($fields as $n=>$f){
3234 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3237 intval($importer['importer_uid']));
3243 // merge with current record, current contents have priority
3244 // update record, set url-updated
3245 // update profile photos
3251 // handle friend suggestion notification
3253 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3254 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3255 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3257 $fsugg['uid'] = $importer['importer_uid'];
3258 $fsugg['cid'] = $importer['id'];
3259 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3260 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3261 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3262 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3263 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3265 // Does our member already have a friend matching this description?
3267 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3268 dbesc($fsugg['name']),
3269 dbesc(normalise_link($fsugg['url'])),
3270 intval($fsugg['uid'])
3275 // Do we already have an fcontact record for this person?
3278 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3279 dbesc($fsugg['url']),
3280 dbesc($fsugg['name']),
3281 dbesc($fsugg['request'])
3286 // OK, we do. Do we already have an introduction for this person ?
3287 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3288 intval($fsugg['uid']),
3295 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3296 dbesc($fsugg['name']),
3297 dbesc($fsugg['url']),
3298 dbesc($fsugg['photo']),
3299 dbesc($fsugg['request'])
3301 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3302 dbesc($fsugg['url']),
3303 dbesc($fsugg['name']),
3304 dbesc($fsugg['request'])
3309 // database record did not get created. Quietly give up.
3314 $hash = random_string();
3316 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3317 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3318 intval($fsugg['uid']),
3320 intval($fsugg['cid']),
3321 dbesc($fsugg['body']),
3323 dbesc(datetime_convert()),
3328 'type' => NOTIFY_SUGGEST,
3329 'notify_flags' => $importer['notify-flags'],
3330 'language' => $importer['language'],
3331 'to_name' => $importer['username'],
3332 'to_email' => $importer['email'],
3333 'uid' => $importer['importer_uid'],
3335 'link' => $a->get_baseurl() . '/notifications/intros',
3336 'source_name' => $importer['name'],
3337 'source_link' => $importer['url'],
3338 'source_photo' => $importer['photo'],
3339 'verb' => ACTIVITY_REQ_FRIEND,
3348 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3349 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3351 logger('local_delivery: private message received');
3354 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3357 $msg['uid'] = $importer['importer_uid'];
3358 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3359 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3360 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3361 $msg['contact-id'] = $importer['id'];
3362 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3363 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3365 $msg['replied'] = 0;
3366 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3367 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3368 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3372 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3373 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3375 // send notifications.
3377 require_once('include/enotify.php');
3379 $notif_params = array(
3380 'type' => NOTIFY_MAIL,
3381 'notify_flags' => $importer['notify-flags'],
3382 'language' => $importer['language'],
3383 'to_name' => $importer['username'],
3384 'to_email' => $importer['email'],
3385 'uid' => $importer['importer_uid'],
3387 'source_name' => $msg['from-name'],
3388 'source_link' => $importer['url'],
3389 'source_photo' => $importer['thumb'],
3390 'verb' => ACTIVITY_POST,
3394 notification($notif_params);
3400 $community_page = 0;
3401 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3403 $community_page = intval($rawtags[0]['data']);
3405 if(intval($importer['forum']) != $community_page) {
3406 q("update contact set forum = %d where id = %d",
3407 intval($community_page),
3408 intval($importer['id'])
3410 $importer['forum'] = (string) $community_page;
3413 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3415 // process any deleted entries
3417 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3418 if(is_array($del_entries) && count($del_entries)) {
3419 foreach($del_entries as $dentry) {
3421 if(isset($dentry['attribs']['']['ref'])) {
3422 $uri = $dentry['attribs']['']['ref'];
3424 if(isset($dentry['attribs']['']['when'])) {
3425 $when = $dentry['attribs']['']['when'];
3426 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3429 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3433 // check for relayed deletes to our conversation
3436 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3438 intval($importer['importer_uid'])
3441 $parent_uri = $r[0]['parent-uri'];
3442 if($r[0]['id'] != $r[0]['parent'])
3449 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3452 logger('local_delivery: possible community delete');
3455 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3457 // was the top-level post for this reply written by somebody on this site?
3458 // Specifically, the recipient?
3460 $is_a_remote_delete = false;
3462 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3463 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3464 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3465 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3466 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3467 AND `item`.`uid` = %d
3473 intval($importer['importer_uid'])
3476 $is_a_remote_delete = true;
3478 // Does this have the characteristics of a community or private group comment?
3479 // If it's a reply to a wall post on a community/prvgroup page it's a
3480 // valid community comment. Also forum_mode makes it valid for sure.
3481 // If neither, it's not.
3483 if($is_a_remote_delete && $community) {
3484 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3485 $is_a_remote_delete = false;
3486 logger('local_delivery: not a community delete');
3490 if($is_a_remote_delete) {
3491 logger('local_delivery: received remote delete');
3495 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3496 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3498 intval($importer['importer_uid']),
3499 intval($importer['id'])
3505 if($item['deleted'])
3508 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3510 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3511 $xo = parse_xml_string($item['object'],false);
3512 $xt = parse_xml_string($item['target'],false);
3514 if($xt->type === ACTIVITY_OBJ_NOTE) {
3515 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3517 intval($importer['importer_uid'])
3521 // For tags, the owner cannot remove the tag on the author's copy of the post.
3523 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3524 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3525 $author_copy = (($item['origin']) ? true : false);
3527 if($owner_remove && $author_copy)
3529 if($author_remove || $owner_remove) {
3530 $tags = explode(',',$i[0]['tag']);
3533 foreach($tags as $tag)
3534 if(trim($tag) !== trim($xo->body))
3535 $newtags[] = trim($tag);
3537 q("update item set tag = '%s' where id = %d",
3538 dbesc(implode(',',$newtags)),
3541 create_tags_from_item($i[0]['id']);
3547 if($item['uri'] == $item['parent-uri']) {
3548 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3549 `body` = '', `title` = ''
3550 WHERE `parent-uri` = '%s' AND `uid` = %d",
3552 dbesc(datetime_convert()),
3553 dbesc($item['uri']),
3554 intval($importer['importer_uid'])
3556 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3557 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3558 update_thread_uri($item['uri'], $importer['importer_uid']);
3561 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3562 `body` = '', `title` = ''
3563 WHERE `uri` = '%s' AND `uid` = %d",
3565 dbesc(datetime_convert()),
3567 intval($importer['importer_uid'])
3569 create_tags_from_itemuri($uri, $importer['importer_uid']);
3570 create_files_from_itemuri($uri, $importer['importer_uid']);
3571 update_thread_uri($uri, $importer['importer_uid']);
3572 if($item['last-child']) {
3573 // ensure that last-child is set in case the comment that had it just got wiped.
3574 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3575 dbesc(datetime_convert()),
3576 dbesc($item['parent-uri']),
3577 intval($item['uid'])
3579 // who is the last child now?
3580 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3581 ORDER BY `created` DESC LIMIT 1",
3582 dbesc($item['parent-uri']),
3583 intval($importer['importer_uid'])
3586 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3591 // if this is a relayed delete, propagate it to other recipients
3593 if($is_a_remote_delete)
3594 proc_run('php',"include/notifier.php","drop",$item['id']);
3602 foreach($feed->get_items() as $item) {
3605 $item_id = $item->get_id();
3606 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3607 if(isset($rawthread[0]['attribs']['']['ref'])) {
3609 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3615 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3618 logger('local_delivery: possible community reply');
3621 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3623 // was the top-level post for this reply written by somebody on this site?
3624 // Specifically, the recipient?
3626 $is_a_remote_comment = false;
3627 $top_uri = $parent_uri;
3629 $r = q("select `item`.`parent-uri` from `item`
3630 WHERE `item`.`uri` = '%s'
3634 if($r && count($r)) {
3635 $top_uri = $r[0]['parent-uri'];
3637 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3638 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3639 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3640 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3641 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3642 AND `item`.`uid` = %d
3648 intval($importer['importer_uid'])
3651 $is_a_remote_comment = true;
3654 // Does this have the characteristics of a community or private group comment?
3655 // If it's a reply to a wall post on a community/prvgroup page it's a
3656 // valid community comment. Also forum_mode makes it valid for sure.
3657 // If neither, it's not.
3659 if($is_a_remote_comment && $community) {
3660 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3661 $is_a_remote_comment = false;
3662 logger('local_delivery: not a community reply');
3666 if($is_a_remote_comment) {
3667 logger('local_delivery: received remote comment');
3669 // remote reply to our post. Import and then notify everybody else.
3671 $datarray = get_atom_elements($feed, $item);
3673 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3675 intval($importer['importer_uid'])
3678 // Update content if 'updated' changes
3682 if (edited_timestamp_is_newer($r[0], $datarray)) {
3684 // do not accept (ignore) an earlier edit than one we currently have.
3685 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3688 logger('received updated comment' , LOGGER_DEBUG);
3689 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3690 dbesc($datarray['title']),
3691 dbesc($datarray['body']),
3692 dbesc($datarray['tag']),
3693 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3694 dbesc(datetime_convert()),
3696 intval($importer['importer_uid'])
3698 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3700 proc_run('php',"include/notifier.php","comment-import",$iid);
3709 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3710 intval($importer['importer_uid'])
3714 $datarray['type'] = 'remote-comment';
3715 $datarray['wall'] = 1;
3716 $datarray['parent-uri'] = $parent_uri;
3717 $datarray['uid'] = $importer['importer_uid'];
3718 $datarray['owner-name'] = $own[0]['name'];
3719 $datarray['owner-link'] = $own[0]['url'];
3720 $datarray['owner-avatar'] = $own[0]['thumb'];
3721 $datarray['contact-id'] = $importer['id'];
3723 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3725 $datarray['type'] = 'activity';
3726 $datarray['gravity'] = GRAVITY_LIKE;
3727 $datarray['last-child'] = 0;
3728 // only one like or dislike per person
3729 // splitted into two queries for performance issues
3730 $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",
3731 intval($datarray['uid']),
3732 intval($datarray['contact-id']),
3733 dbesc($datarray['verb']),
3734 dbesc($datarray['parent-uri'])
3740 $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",
3741 intval($datarray['uid']),
3742 intval($datarray['contact-id']),
3743 dbesc($datarray['verb']),
3744 dbesc($datarray['parent-uri'])
3751 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3753 $xo = parse_xml_string($datarray['object'],false);
3754 $xt = parse_xml_string($datarray['target'],false);
3756 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3758 // fetch the parent item
3760 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3762 intval($importer['importer_uid'])
3767 // extract tag, if not duplicate, and this user allows tags, add to parent item
3769 if($xo->id && $xo->content) {
3770 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3771 if(! (stristr($tagp[0]['tag'],$newtag))) {
3772 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3773 intval($importer['importer_uid'])
3775 if(count($i) && ! intval($i[0]['blocktags'])) {
3776 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3777 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3778 intval($tagp[0]['id']),
3779 dbesc(datetime_convert()),
3780 dbesc(datetime_convert())
3782 create_tags_from_item($tagp[0]['id']);
3790 $posted_id = item_store($datarray);
3795 $datarray["id"] = $posted_id;
3797 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3799 intval($importer['importer_uid'])
3802 $parent = $r[0]['parent'];
3803 $parent_uri = $r[0]['parent-uri'];
3807 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3808 dbesc(datetime_convert()),
3809 intval($importer['importer_uid']),
3810 intval($r[0]['parent'])
3813 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3814 dbesc(datetime_convert()),
3815 intval($importer['importer_uid']),
3820 if($posted_id && $parent) {
3822 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3824 if((! $is_like) && (! $importer['self'])) {
3826 require_once('include/enotify.php');
3829 'type' => NOTIFY_COMMENT,
3830 'notify_flags' => $importer['notify-flags'],
3831 'language' => $importer['language'],
3832 'to_name' => $importer['username'],
3833 'to_email' => $importer['email'],
3834 'uid' => $importer['importer_uid'],
3835 'item' => $datarray,
3836 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3837 'source_name' => stripslashes($datarray['author-name']),
3838 'source_link' => $datarray['author-link'],
3839 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3840 ? $importer['thumb'] : $datarray['author-avatar']),
3841 'verb' => ACTIVITY_POST,
3843 'parent' => $parent,
3844 'parent_uri' => $parent_uri,
3856 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3858 $item_id = $item->get_id();
3859 $datarray = get_atom_elements($feed,$item);
3861 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3864 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3866 intval($importer['importer_uid'])
3869 // Update content if 'updated' changes
3872 if (edited_timestamp_is_newer($r[0], $datarray)) {
3874 // do not accept (ignore) an earlier edit than one we currently have.
3875 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3878 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3879 dbesc($datarray['title']),
3880 dbesc($datarray['body']),
3881 dbesc($datarray['tag']),
3882 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3883 dbesc(datetime_convert()),
3885 intval($importer['importer_uid'])
3887 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3890 // update last-child if it changes
3892 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3893 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3894 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3895 dbesc(datetime_convert()),
3897 intval($importer['importer_uid'])
3899 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3900 intval($allow[0]['data']),
3901 dbesc(datetime_convert()),
3903 intval($importer['importer_uid'])
3909 $datarray['parent-uri'] = $parent_uri;
3910 $datarray['uid'] = $importer['importer_uid'];
3911 $datarray['contact-id'] = $importer['id'];
3912 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3913 $datarray['type'] = 'activity';
3914 $datarray['gravity'] = GRAVITY_LIKE;
3915 // only one like or dislike per person
3916 // splitted into two queries for performance issues
3917 $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",
3918 intval($datarray['uid']),
3919 intval($datarray['contact-id']),
3920 dbesc($datarray['verb']),
3926 $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",
3927 intval($datarray['uid']),
3928 intval($datarray['contact-id']),
3929 dbesc($datarray['verb']),
3937 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3939 $xo = parse_xml_string($datarray['object'],false);
3940 $xt = parse_xml_string($datarray['target'],false);
3942 if($xt->type == ACTIVITY_OBJ_NOTE) {
3943 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3945 intval($importer['importer_uid'])
3950 // extract tag, if not duplicate, add to parent item
3952 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3953 q("UPDATE item SET tag = '%s' WHERE id = %d",
3954 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3957 create_tags_from_item($r[0]['id']);
3963 $posted_id = item_store($datarray);
3965 // find out if our user is involved in this conversation and wants to be notified.
3967 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3969 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3971 intval($importer['importer_uid'])
3974 if(count($myconv)) {
3975 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3977 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3978 if(! link_compare($datarray['author-link'],$importer_url)) {
3981 foreach($myconv as $conv) {
3983 // now if we find a match, it means we're in this conversation
3985 if(! link_compare($conv['author-link'],$importer_url))
3988 require_once('include/enotify.php');
3990 $conv_parent = $conv['parent'];
3993 'type' => NOTIFY_COMMENT,
3994 'notify_flags' => $importer['notify-flags'],
3995 'language' => $importer['language'],
3996 'to_name' => $importer['username'],
3997 'to_email' => $importer['email'],
3998 'uid' => $importer['importer_uid'],
3999 'item' => $datarray,
4000 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4001 'source_name' => stripslashes($datarray['author-name']),
4002 'source_link' => $datarray['author-link'],
4003 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4004 ? $importer['thumb'] : $datarray['author-avatar']),
4005 'verb' => ACTIVITY_POST,
4007 'parent' => $conv_parent,
4008 'parent_uri' => $parent_uri
4012 // only send one notification
4024 // Head post of a conversation. Have we seen it? If not, import it.
4027 $item_id = $item->get_id();
4028 $datarray = get_atom_elements($feed,$item);
4030 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
4031 $ev = bbtoevent($datarray['body']);
4032 if(x($ev,'desc') && x($ev,'start')) {
4033 $ev['cid'] = $importer['id'];
4034 $ev['uid'] = $importer['uid'];
4035 $ev['uri'] = $item_id;
4036 $ev['edited'] = $datarray['edited'];
4037 $ev['private'] = $datarray['private'];
4039 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4041 intval($importer['uid'])
4044 $ev['id'] = $r[0]['id'];
4045 $xyz = event_store($ev);
4050 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4052 intval($importer['importer_uid'])
4055 // Update content if 'updated' changes
4058 if (edited_timestamp_is_newer($r[0], $datarray)) {
4060 // do not accept (ignore) an earlier edit than one we currently have.
4061 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4064 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4065 dbesc($datarray['title']),
4066 dbesc($datarray['body']),
4067 dbesc($datarray['tag']),
4068 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4069 dbesc(datetime_convert()),
4071 intval($importer['importer_uid'])
4073 create_tags_from_itemuri($item_id, $importer['importer_uid']);
4074 update_thread_uri($item_id, $importer['importer_uid']);
4077 // update last-child if it changes
4079 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4080 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4081 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4082 intval($allow[0]['data']),
4083 dbesc(datetime_convert()),
4085 intval($importer['importer_uid'])
4091 $datarray['parent-uri'] = $item_id;
4092 $datarray['uid'] = $importer['importer_uid'];
4093 $datarray['contact-id'] = $importer['id'];
4096 if(! link_compare($datarray['owner-link'],$importer['url'])) {
4097 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4098 // but otherwise there's a possible data mixup on the sender's system.
4099 // the tgroup delivery code called from item_store will correct it if it's a forum,
4100 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4101 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4102 $datarray['owner-name'] = $importer['senderName'];
4103 $datarray['owner-link'] = $importer['url'];
4104 $datarray['owner-avatar'] = $importer['thumb'];
4107 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4110 // This is my contact on another system, but it's really me.
4111 // Turn this into a wall post.
4112 $notify = item_is_remote_self($importer, $datarray);
4114 $posted_id = item_store($datarray, false, $notify);
4116 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4117 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4120 $xo = parse_xml_string($datarray['object'],false);
4122 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4124 // somebody was poked/prodded. Was it me?
4126 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4128 foreach($links->link as $l) {
4129 $atts = $l->attributes();
4130 switch($atts['rel']) {
4132 $Blink = $atts['href'];
4138 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4140 // send a notification
4141 require_once('include/enotify.php');
4144 'type' => NOTIFY_POKE,
4145 'notify_flags' => $importer['notify-flags'],
4146 'language' => $importer['language'],
4147 'to_name' => $importer['username'],
4148 'to_email' => $importer['email'],
4149 'uid' => $importer['importer_uid'],
4150 'item' => $datarray,
4151 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4152 'source_name' => stripslashes($datarray['author-name']),
4153 'source_link' => $datarray['author-link'],
4154 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4155 ? $importer['thumb'] : $datarray['author-avatar']),
4156 'verb' => $datarray['verb'],
4157 'otype' => 'person',
4158 'activity' => $verb,
4159 'parent' => $datarray['parent']
4175 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4176 $url = notags(trim($datarray['author-link']));
4177 $name = notags(trim($datarray['author-name']));
4178 $photo = notags(trim($datarray['author-avatar']));
4180 if (is_object($item)) {
4181 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4182 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4183 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4187 if(is_array($contact)) {
4188 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4189 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4190 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4191 intval(CONTACT_IS_FRIEND),
4192 intval($contact['id']),
4193 intval($importer['uid'])
4196 // send email notification to owner?
4200 // create contact record
4202 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4203 `blocked`, `readonly`, `pending`, `writable` )
4204 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4205 intval($importer['uid']),
4206 dbesc(datetime_convert()),
4208 dbesc(normalise_link($url)),
4212 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4213 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4215 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4216 intval($importer['uid']),
4220 $contact_record = $r[0];
4222 // create notification
4223 $hash = random_string();
4225 if(is_array($contact_record)) {
4226 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4227 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4228 intval($importer['uid']),
4229 intval($contact_record['id']),
4231 dbesc(datetime_convert())
4235 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4236 intval($importer['uid'])
4241 if(intval($r[0]['def_gid'])) {
4242 require_once('include/group.php');
4243 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4246 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4247 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4250 'type' => NOTIFY_INTRO,
4251 'notify_flags' => $r[0]['notify-flags'],
4252 'language' => $r[0]['language'],
4253 'to_name' => $r[0]['username'],
4254 'to_email' => $r[0]['email'],
4255 'uid' => $r[0]['uid'],
4256 'link' => $a->get_baseurl() . '/notifications/intro',
4257 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4258 'source_link' => $contact_record['url'],
4259 'source_photo' => $contact_record['photo'],
4260 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4269 function lose_follower($importer,$contact,$datarray,$item) {
4271 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4272 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4273 intval(CONTACT_IS_SHARING),
4274 intval($contact['id'])
4278 contact_remove($contact['id']);
4282 function lose_sharer($importer,$contact,$datarray,$item) {
4284 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4285 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4286 intval(CONTACT_IS_FOLLOWER),
4287 intval($contact['id'])
4291 contact_remove($contact['id']);
4296 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4300 if(is_array($importer)) {
4301 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4302 intval($importer['uid'])
4306 // Diaspora has different message-ids in feeds than they do
4307 // through the direct Diaspora protocol. If we try and use
4308 // the feed, we'll get duplicates. So don't.
4310 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4313 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4315 // Use a single verify token, even if multiple hubs
4317 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4319 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4321 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4323 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
4324 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4325 dbesc($verify_token),
4326 intval($contact['id'])
4330 post_url($url,$params);
4332 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4339 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4343 $name = xmlify($name);
4344 $uri = xmlify($uri);
4347 $photo = xmlify($photo);
4351 $o .= "\t<name>$name</name>\r\n";
4352 $o .= "\t<uri>$uri</uri>\r\n";
4353 $o .= "\t".'<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4354 $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4356 if ($tag == "author") {
4357 $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
4358 `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
4359 `profile`.`homepage`,`contact`.`nick` FROM `profile`
4360 INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
4361 INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
4362 WHERE `profile`.`is-default` AND `contact`.`self` AND
4363 NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
4364 dbesc(normalise_link($uri)));
4367 if($r[0]['locality'])
4368 $location .= $r[0]['locality'];
4369 if($r[0]['region']) {
4372 $location .= $r[0]['region'];
4374 if($r[0]['country-name']) {
4377 $location .= $r[0]['country-name'];
4380 $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
4381 $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
4382 $o .= "\t<poco:note>".xmlify($r[0]["about"])."</poco:note>\r\n";
4383 $o .= "\t<poco:address>\r\n";
4384 $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
4385 $o .= "\t</poco:address>\r\n";
4386 $o .= "\t<poco:urls>\r\n";
4387 $o .= "\t<poco:type>homepage</poco:type>\r\n";
4388 $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
4389 $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
4390 $o .= "\t</poco:urls>\r\n";
4394 call_hooks('atom_author', $o);
4396 $o .= "</$tag>\r\n";
4400 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4404 if(! $item['parent'])
4407 if($item['deleted'])
4408 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4411 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4412 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4414 $body = $item['body'];
4417 $o = "\r\n\r\n<entry>\r\n";
4419 if(is_array($author))
4420 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4422 $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']));
4423 if(strlen($item['owner-name']))
4424 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4426 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4427 $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
4428 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4429 $o .= '<thr:in-reply-to ref="'.xmlify($parent_item).'" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$parent[0]['guid']).'" />'."\r\n";
4434 if ($item['title'] != "")
4435 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4437 $htmlbody = bbcode($htmlbody, false, false, 7);
4439 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4440 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4441 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4442 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4443 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4444 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4445 $o .= '<link rel="alternate" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$item['guid']).'" />'."\r\n";
4447 $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
4450 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4452 if($item['location']) {
4453 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4454 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4458 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4460 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4461 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4464 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4465 if($item['bookmark'])
4466 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4469 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4472 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4474 if($item['signed_text']) {
4475 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4476 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4479 $verb = construct_verb($item);
4480 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4481 $actobj = construct_activity_object($item);
4484 $actarg = construct_activity_target($item);
4488 $tags = item_getfeedtags($item);
4490 foreach($tags as $t)
4491 if (($type != 'html') OR ($t[0] != "@"))
4492 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4496 // To support these elements, the API needs to be enhanced
4497 //$o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
4498 //$o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4499 //$o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4501 $o .= item_get_attachment($item);
4503 $o .= item_getfeedattach($item);
4505 $mentioned = get_mentions($item);
4509 call_hooks('atom_entry', $o);
4511 $o .= '</entry>' . "\r\n";
4516 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4518 if(get_config('system','disable_embedded'))
4523 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4524 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4529 $img_start = strpos($orig_body, '[img');
4530 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4531 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4532 while( ($img_st_close !== false) && ($img_len !== false) ) {
4534 $img_st_close++; // make it point to AFTER the closing bracket
4535 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4537 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4540 if(stristr($image , $site . '/photo/')) {
4541 // Only embed locally hosted photos
4543 $i = basename($image);
4544 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4545 $x = strpos($i,'-');
4548 $res = substr($i,$x+1);
4549 $i = substr($i,0,$x);
4550 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4557 // Check to see if we should replace this photo link with an embedded image
4558 // 1. No need to do so if the photo is public
4559 // 2. If there's a contact-id provided, see if they're in the access list
4560 // for the photo. If so, embed it.
4561 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4562 // permissions, regardless of order but first check to see if they're an exact
4563 // match to save some processing overhead.
4565 if(has_permissions($r[0])) {
4567 $recips = enumerate_permissions($r[0]);
4568 if(in_array($cid, $recips)) {
4573 if(compare_permissions($item,$r[0]))
4578 $data = $r[0]['data'];
4579 $type = $r[0]['type'];
4581 // If a custom width and height were specified, apply before embedding
4582 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4583 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4585 $width = intval($match[1]);
4586 $height = intval($match[2]);
4588 $ph = new Photo($data, $type);
4589 if($ph->is_valid()) {
4590 $ph->scaleImage(max($width, $height));
4591 $data = $ph->imageString();
4592 $type = $ph->getType();
4596 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4597 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4598 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4604 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4605 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4606 if($orig_body === false)
4609 $img_start = strpos($orig_body, '[img');
4610 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4611 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4614 $new_body = $new_body . $orig_body;
4620 function has_permissions($obj) {
4621 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4626 function compare_permissions($obj1,$obj2) {
4627 // first part is easy. Check that these are exactly the same.
4628 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4629 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4630 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4631 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4634 // This is harder. Parse all the permissions and compare the resulting set.
4636 $recipients1 = enumerate_permissions($obj1);
4637 $recipients2 = enumerate_permissions($obj2);
4640 if($recipients1 == $recipients2)
4645 // returns an array of contact-ids that are allowed to see this object
4647 function enumerate_permissions($obj) {
4648 require_once('include/group.php');
4649 $allow_people = expand_acl($obj['allow_cid']);
4650 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4651 $deny_people = expand_acl($obj['deny_cid']);
4652 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4653 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4654 $deny = array_unique(array_merge($deny_people,$deny_groups));
4655 $recipients = array_diff($recipients,$deny);
4659 function item_getfeedtags($item) {
4662 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4664 for($x = 0; $x < $cnt; $x ++) {
4666 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4670 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4672 for($x = 0; $x < $cnt; $x ++) {
4674 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4680 function item_get_attachment($item) {
4682 $siteinfo = get_attached_data($item["body"]);
4684 switch($siteinfo["type"]) {
4686 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4689 $imgdata = get_photo_info($siteinfo["image"]);
4690 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
4693 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4702 function item_getfeedattach($item) {
4704 $arr = explode('[/attach],',$item['attach']);
4706 foreach($arr as $r) {
4708 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4710 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4711 if(intval($matches[2]))
4712 $ret .= 'length="' . intval($matches[2]) . '" ';
4713 if($matches[4] !== ' ')
4714 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4715 $ret .= ' />' . "\r\n";
4724 function item_expire($uid, $days, $network = "", $force = false) {
4726 if((! $uid) || ($days < 1))
4729 // $expire_network_only = save your own wall posts
4730 // and just expire conversations started by others
4732 $expire_network_only = get_pconfig($uid,'expire','network_only');
4733 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4735 if ($network != "") {
4736 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4737 // There is an index "uid_network_received" but not "uid_network_created"
4738 // This avoids the creation of another index just for one purpose.
4739 // And it doesn't really matter wether to look at "received" or "created"
4740 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4742 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4744 $r = q("SELECT * FROM `item`
4745 WHERE `uid` = %d $range
4756 $expire_items = get_pconfig($uid, 'expire','items');
4757 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4759 // Forcing expiring of items - but not notes and marked items
4761 $expire_items = true;
4763 $expire_notes = get_pconfig($uid, 'expire','notes');
4764 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4766 $expire_starred = get_pconfig($uid, 'expire','starred');
4767 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4769 $expire_photos = get_pconfig($uid, 'expire','photos');
4770 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4772 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4774 foreach($r as $item) {
4776 // don't expire filed items
4778 if(strpos($item['file'],'[') !== false)
4781 // Only expire posts, not photos and photo comments
4783 if($expire_photos==0 && strlen($item['resource-id']))
4785 if($expire_starred==0 && intval($item['starred']))
4787 if($expire_notes==0 && $item['type']=='note')
4789 if($expire_items==0 && $item['type']!='note')
4792 drop_item($item['id'],false);
4795 proc_run('php',"include/notifier.php","expire","$uid");
4800 function drop_items($items) {
4803 if(! local_user() && ! remote_user())
4807 foreach($items as $item) {
4808 $owner = drop_item($item,false);
4809 if($owner && ! $uid)
4814 // multiple threads may have been deleted, send an expire notification
4817 proc_run('php',"include/notifier.php","expire","$uid");
4821 function drop_item($id,$interactive = true) {
4825 // locate item to be deleted
4827 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4834 notice( t('Item not found.') . EOL);
4835 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4840 $owner = $item['uid'];
4844 // check if logged in user is either the author or owner of this item
4846 if(is_array($_SESSION['remote'])) {
4847 foreach($_SESSION['remote'] as $visitor) {
4848 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4849 $cid = $visitor['cid'];
4856 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4858 // Check if we should do HTML-based delete confirmation
4859 if($_REQUEST['confirm']) {
4860 // <form> can't take arguments in its "action" parameter
4861 // so add any arguments as hidden inputs
4862 $query = explode_querystring($a->query_string);
4864 foreach($query['args'] as $arg) {
4865 if(strpos($arg, 'confirm=') === false) {
4866 $arg_parts = explode('=', $arg);
4867 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4871 return replace_macros(get_markup_template('confirm.tpl'), array(
4873 '$message' => t('Do you really want to delete this item?'),
4874 '$extra_inputs' => $inputs,
4875 '$confirm' => t('Yes'),
4876 '$confirm_url' => $query['base'],
4877 '$confirm_name' => 'confirmed',
4878 '$cancel' => t('Cancel'),
4881 // Now check how the user responded to the confirmation query
4882 if($_REQUEST['canceled']) {
4883 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4886 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4889 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4890 dbesc(datetime_convert()),
4891 dbesc(datetime_convert()),
4894 create_tags_from_item($item['id']);
4895 create_files_from_item($item['id']);
4896 delete_thread($item['id'], $item['parent-uri']);
4898 // clean up categories and tags so they don't end up as orphans
4901 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4903 foreach($matches as $mtch) {
4904 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4910 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4912 foreach($matches as $mtch) {
4913 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4917 // If item is a link to a photo resource, nuke all the associated photos
4918 // (visitors will not have photo resources)
4919 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4920 // generate a resource-id and therefore aren't intimately linked to the item.
4922 if(strlen($item['resource-id'])) {
4923 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4924 dbesc($item['resource-id']),
4925 intval($item['uid'])
4927 // ignore the result
4930 // If item is a link to an event, nuke the event record.
4932 if(intval($item['event-id'])) {
4933 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4934 intval($item['event-id']),
4935 intval($item['uid'])
4937 // ignore the result
4940 // If item has attachments, drop them
4942 foreach(explode(",",$item['attach']) as $attach){
4943 preg_match("|attach/(\d+)|", $attach, $matches);
4944 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4945 intval($matches[1]),
4948 // ignore the result
4952 // clean up item_id and sign meta-data tables
4955 // Old code - caused very long queries and warning entries in the mysql logfiles:
4957 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4958 intval($item['id']),
4959 intval($item['uid'])
4962 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4963 intval($item['id']),
4964 intval($item['uid'])
4968 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4970 // Creating list of parents
4971 $r = q("select id from item where parent = %d and uid = %d",
4972 intval($item['id']),
4973 intval($item['uid'])
4978 foreach ($r AS $row) {
4979 if ($parentid != "")
4982 $parentid .= $row["id"];
4986 if ($parentid != "") {
4987 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4989 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4992 // If it's the parent of a comment thread, kill all the kids
4994 if($item['uri'] == $item['parent-uri']) {
4995 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4996 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4997 dbesc(datetime_convert()),
4998 dbesc(datetime_convert()),
4999 dbesc($item['parent-uri']),
5000 intval($item['uid'])
5002 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
5003 create_files_from_itemuri($item['parent-uri'], $item['uid']);
5004 delete_thread_uri($item['parent-uri'], $item['uid']);
5005 // ignore the result
5008 // ensure that last-child is set in case the comment that had it just got wiped.
5009 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
5010 dbesc(datetime_convert()),
5011 dbesc($item['parent-uri']),
5012 intval($item['uid'])
5014 // who is the last child now?
5015 $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",
5016 dbesc($item['parent-uri']),
5017 intval($item['uid'])
5020 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
5025 // Add a relayable_retraction signature for Diaspora.
5026 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
5029 $drop_id = intval($item['id']);
5031 // send the notification upstream/downstream as the case may be
5033 proc_run('php',"include/notifier.php","drop","$drop_id");
5037 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5043 notice( t('Permission denied.') . EOL);
5044 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5051 function first_post_date($uid,$wall = false) {
5052 $r = q("select id, created from item
5053 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
5055 order by created asc limit 1",
5057 intval($wall ? 1 : 0)
5060 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
5061 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
5066 /* modified posted_dates() {below} to arrange the list in years */
5067 function list_post_dates($uid, $wall) {
5068 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5070 $dthen = first_post_date($uid, $wall);
5074 // Set the start and end date to the beginning of the month
5075 $dnow = substr($dnow,0,8).'01';
5076 $dthen = substr($dthen,0,8).'01';
5080 // Starting with the current month, get the first and last days of every
5081 // month down to and including the month of the first post
5082 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5083 $dyear = intval(substr($dnow,0,4));
5084 $dstart = substr($dnow,0,8) . '01';
5085 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5086 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5087 $end_month = datetime_convert('','',$dend,'Y-m-d');
5088 $str = day_translate(datetime_convert('','',$dnow,'F'));
5090 $ret[$dyear] = array();
5091 $ret[$dyear][] = array($str,$end_month,$start_month);
5092 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5097 function posted_dates($uid,$wall) {
5098 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5100 $dthen = first_post_date($uid,$wall);
5104 // Set the start and end date to the beginning of the month
5105 $dnow = substr($dnow,0,8).'01';
5106 $dthen = substr($dthen,0,8).'01';
5109 // Starting with the current month, get the first and last days of every
5110 // month down to and including the month of the first post
5111 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5112 $dstart = substr($dnow,0,8) . '01';
5113 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5114 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5115 $end_month = datetime_convert('','',$dend,'Y-m-d');
5116 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5117 $ret[] = array($str,$end_month,$start_month);
5118 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5124 function posted_date_widget($url,$uid,$wall) {
5127 if(! feature_enabled($uid,'archives'))
5130 // For former Facebook folks that left because of "timeline"
5132 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5135 $visible_years = get_pconfig($uid,'system','archive_visible_years');
5136 if(! $visible_years)
5139 $ret = list_post_dates($uid,$wall);
5144 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5145 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5147 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5148 '$title' => t('Archives'),
5149 '$size' => $visible_years,
5150 '$cutoff_year' => $cutoff_year,
5151 '$cutoff' => $cutoff,
5154 '$showmore' => t('show more')
5160 function store_diaspora_retract_sig($item, $user, $baseurl) {
5161 // Note that we can't add a target_author_signature
5162 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5163 // the comment, that means we're the home of the post, and Diaspora will only
5164 // check the parent_author_signature of retractions that it doesn't have to relay further
5166 // I don't think this function gets called for an "unlike," but I'll check anyway
5168 $enabled = intval(get_config('system','diaspora_enabled'));
5170 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5174 logger('drop_item: storing diaspora retraction signature');
5176 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5178 if(local_user() == $item['uid']) {
5180 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5181 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5184 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5185 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5188 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5189 // only handles DFRN deletes
5190 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5191 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5192 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5198 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5199 intval($item['id']),
5200 dbesc($signed_text),