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/ostatus_conversation.php');
13 require_once('include/threads.php');
14 require_once('include/socgraph.php');
16 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
19 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
20 $public_feed = (($dfrn_id) ? false : true);
21 $starred = false; // not yet implemented, possible security issues
24 if($public_feed && $a->argc > 2) {
25 for($x = 2; $x < $a->argc; $x++) {
26 if($a->argv[$x] == 'converse')
28 if($a->argv[$x] == 'starred')
30 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
31 $category = $a->argv[$x+1];
37 // default permissions - anonymous user
39 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
41 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
42 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
43 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
51 $owner_id = $owner['user_uid'];
52 $owner_nick = $owner['nickname'];
54 $birthday = feed_birthday($owner_id,$owner['timezone']);
63 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
67 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
68 $my_id = '1:' . $dfrn_id;
71 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
72 $my_id = '0:' . $dfrn_id;
79 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
87 require_once('include/security.php');
88 $groups = init_groups_visitor($contact['id']);
91 for($x = 0; $x < count($groups); $x ++)
92 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
93 $gs = implode('|', $groups);
96 $gs = '<<>>' ; // Impossible to match
98 $sql_extra = sprintf("
99 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
100 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
101 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
102 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
104 intval($contact['id']),
105 intval($contact['id']),
116 if(! strlen($last_update))
117 $last_update = 'now -30 days';
119 if(isset($category)) {
120 $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` ",
121 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
122 //$sql_extra .= file_tag_file_query('item',$category,'category');
127 $sql_extra .= " AND `contact`.`self` = 1 ";
130 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
132 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
133 // dbesc($check_date),
135 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
136 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
137 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
138 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
139 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
140 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
141 FROM `item` $sql_post_table
142 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
143 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
144 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
145 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
146 AND `item`.`wall` = 1 AND `item`.`changed` > '%s'
148 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
154 // Will check further below if this actually returned results.
155 // We will provide an empty feed if that is the case.
159 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
163 $hubxml = feed_hublinks();
165 $salmon = feed_salmonlinks($owner_nick);
167 $alternatelink = $owner['url'];
170 $alternatelink .= "/category/".$category;
172 $atom .= replace_macros($feed_template, array(
173 '$version' => xmlify(FRIENDICA_VERSION),
174 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
175 '$feed_title' => xmlify($owner['name']),
176 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
178 '$salmon' => $salmon,
179 '$alternatelink' => xmlify($alternatelink),
180 '$name' => xmlify($owner['name']),
181 '$profile_page' => xmlify($owner['url']),
182 '$photo' => xmlify($owner['photo']),
183 '$thumb' => xmlify($owner['thumb']),
184 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
185 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
186 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
187 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
188 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
191 call_hooks('atom_feed', $atom);
193 if(! count($items)) {
195 call_hooks('atom_feed_end', $atom);
197 $atom .= '</feed>' . "\r\n";
201 foreach($items as $item) {
203 // prevent private email from leaking.
204 if($item['network'] === NETWORK_MAIL)
207 // public feeds get html, our own nodes use bbcode
211 // catch any email that's in a public conversation and make sure it doesn't leak
219 $atom .= atom_entry($item,$type,null,$owner,true);
222 call_hooks('atom_feed_end', $atom);
224 $atom .= '</feed>' . "\r\n";
230 function construct_verb($item) {
232 return $item['verb'];
233 return ACTIVITY_POST;
236 function construct_activity_object($item) {
238 if($item['object']) {
239 $o = '<as:object>' . "\r\n";
240 $r = parse_xml_string($item['object'],false);
246 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
248 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
250 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
252 if(substr($r->link,0,1) === '<') {
253 // patch up some facebook "like" activity objects that got stored incorrectly
254 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
255 // we can probably remove this hack here and in the following function in a few months time.
256 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
257 $r->link = str_replace('&','&', $r->link);
258 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
262 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
265 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
266 $o .= '</as:object>' . "\r\n";
273 function construct_activity_target($item) {
275 if($item['target']) {
276 $o = '<as:target>' . "\r\n";
277 $r = parse_xml_string($item['target'],false);
281 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
283 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
285 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
287 if(substr($r->link,0,1) === '<') {
288 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
289 $r->link = str_replace('&','&', $r->link);
290 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
294 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
297 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
298 $o .= '</as:target>' . "\r\n";
307 * The purpose of this function is to apply system message length limits to
308 * imported messages without including any embedded photos in the length
310 if(! function_exists('limit_body_size')) {
311 function limit_body_size($body) {
313 // logger('limit_body_size: start', LOGGER_DEBUG);
315 $maxlen = get_max_import_size();
317 // If the length of the body, including the embedded images, is smaller
318 // than the maximum, then don't waste time looking for the images
319 if($maxlen && (strlen($body) > $maxlen)) {
321 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
328 $img_start = strpos($orig_body, '[img');
329 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
330 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
331 while(($img_st_close !== false) && ($img_end !== false)) {
333 $img_st_close++; // make it point to AFTER the closing bracket
334 $img_end += $img_start;
335 $img_end += strlen('[/img]');
337 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
338 // This is an embedded image
340 if( ($textlen + $img_start) > $maxlen ) {
341 if($textlen < $maxlen) {
342 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
343 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
348 $new_body = $new_body . substr($orig_body, 0, $img_start);
349 $textlen += $img_start;
352 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
356 if( ($textlen + $img_end) > $maxlen ) {
357 if($textlen < $maxlen) {
358 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
359 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
364 $new_body = $new_body . substr($orig_body, 0, $img_end);
365 $textlen += $img_end;
368 $orig_body = substr($orig_body, $img_end);
370 if($orig_body === false) // in case the body ends on a closing image tag
373 $img_start = strpos($orig_body, '[img');
374 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
375 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
378 if( ($textlen + strlen($orig_body)) > $maxlen) {
379 if($textlen < $maxlen) {
380 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
381 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
386 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
387 $new_body = $new_body . $orig_body;
388 $textlen += strlen($orig_body);
397 function title_is_body($title, $body) {
399 $title = strip_tags($title);
400 $title = trim($title);
401 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
402 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
404 $body = strip_tags($body);
406 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
407 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
409 if (strlen($title) < strlen($body))
410 $body = substr($body, 0, strlen($title));
412 if (($title != $body) and (substr($title, -3) == "...")) {
413 $pos = strrpos($title, "...");
415 $title = substr($title, 0, $pos);
416 $body = substr($body, 0, $pos);
420 return($title == $body);
425 function get_atom_elements($feed, $item, $contact = array()) {
427 require_once('library/HTMLPurifier.auto.php');
428 require_once('include/html2bbcode.php');
430 $best_photo = array();
434 $author = $item->get_author();
436 $res['author-name'] = unxmlify($author->get_name());
437 $res['author-link'] = unxmlify($author->get_link());
440 $res['author-name'] = unxmlify($feed->get_title());
441 $res['author-link'] = unxmlify($feed->get_permalink());
443 $res['uri'] = unxmlify($item->get_id());
444 $res['title'] = unxmlify($item->get_title());
445 $res['body'] = unxmlify($item->get_content());
446 $res['plink'] = unxmlify($item->get_link(0));
448 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
449 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
451 $res['body'] = nl2br($res['body']);
454 // removing the content of the title if its identically to the body
455 // This helps with auto generated titles e.g. from tumblr
456 if (title_is_body($res["title"], $res["body"]))
460 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
464 // look for a photo. We should check media size and find the best one,
465 // but for now let's just find any author photo
467 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
469 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
470 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
471 foreach($base as $link) {
472 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
473 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
474 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
479 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
481 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
482 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
483 if($base && count($base)) {
484 foreach($base as $link) {
485 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
486 $res['author-link'] = unxmlify($link['attribs']['']['href']);
487 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
488 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
489 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
495 // No photo/profile-link on the item - look at the feed level
497 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
498 $rawauthor = $feed->get_feed_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' && (! $res['author-link']))
503 $res['author-link'] = unxmlify($link['attribs']['']['href']);
504 if(! $res['author-avatar']) {
505 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
506 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
511 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
513 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
514 $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'))) {
521 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
522 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
529 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
530 if($apps && $apps[0]['attribs']['']['source']) {
531 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
532 if($res['app'] === 'web')
533 $res['app'] = 'OStatus';
536 // base64 encoded json structure representing Diaspora signature
538 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
540 $res['dsprsig'] = unxmlify($dsig[0]['data']);
543 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
545 $res['guid'] = unxmlify($dguid[0]['data']);
547 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
549 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
553 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
556 $have_real_body = false;
558 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
560 $have_real_body = true;
561 $res['body'] = $rawenv[0]['data'];
562 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
563 // make sure nobody is trying to sneak some html tags by us
564 $res['body'] = notags(base64url_decode($res['body']));
568 $res['body'] = limit_body_size($res['body']);
570 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
571 // the content type. Our own network only emits text normally, though it might have been converted to
572 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
573 // have to assume it is all html and needs to be purified.
575 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
576 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
577 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
580 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
582 $res['body'] = reltoabs($res['body'],$base_url);
584 $res['body'] = html2bb_video($res['body']);
586 $res['body'] = oembed_html2bbcode($res['body']);
588 $config = HTMLPurifier_Config::createDefault();
589 $config->set('Cache.DefinitionImpl', null);
591 // we shouldn't need a whitelist, because the bbcode converter
592 // will strip out any unsupported tags.
594 $purifier = new HTMLPurifier($config);
595 $res['body'] = $purifier->purify($res['body']);
597 $res['body'] = @html2bbcode($res['body']);
601 elseif(! $have_real_body) {
603 // it's not one of our messages and it has no tags
604 // so it's probably just text. We'll escape it just to be safe.
606 $res['body'] = escape_tags($res['body']);
610 // this tag is obsolete but we keep it for really old sites
612 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
613 if($allow && $allow[0]['data'] == 1)
614 $res['last-child'] = 1;
616 $res['last-child'] = 0;
618 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
619 if($private && intval($private[0]['data']) > 0)
620 $res['private'] = intval($private[0]['data']);
624 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
625 if($extid && $extid[0]['data'])
626 $res['extid'] = $extid[0]['data'];
628 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
630 $res['location'] = unxmlify($rawlocation[0]['data']);
633 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
635 $res['created'] = unxmlify($rawcreated[0]['data']);
638 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
640 $res['edited'] = unxmlify($rawedited[0]['data']);
642 if((x($res,'edited')) && (! (x($res,'created'))))
643 $res['created'] = $res['edited'];
645 if(! $res['created'])
646 $res['created'] = $item->get_date('c');
649 $res['edited'] = $item->get_date('c');
652 // Disallow time travelling posts
654 $d1 = strtotime($res['created']);
655 $d2 = strtotime($res['edited']);
656 $d3 = strtotime('now');
659 $res['created'] = datetime_convert();
661 $res['edited'] = datetime_convert();
663 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
664 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
665 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
666 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
667 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
668 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
669 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
670 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
671 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
673 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
674 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
676 foreach($base as $link) {
677 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
678 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
679 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
684 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
686 $res['coord'] = unxmlify($rawgeo[0]['data']);
688 if ($contact["network"] == NETWORK_FEED) {
689 $res['verb'] = ACTIVITY_POST;
690 $res['object-type'] = ACTIVITY_OBJ_NOTE;
693 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
695 // select between supported verbs
698 $res['verb'] = unxmlify($rawverb[0]['data']);
701 // translate OStatus unfollow to activity streams if it happened to get selected
703 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
704 $res['verb'] = ACTIVITY_UNFOLLOW;
706 $cats = $item->get_categories();
709 foreach($cats as $cat) {
710 $term = $cat->get_term();
712 $term = $cat->get_label();
713 $scheme = $cat->get_scheme();
714 if($scheme && $term && stristr($scheme,'X-DFRN:'))
715 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
717 $tag_arr[] = notags(trim($term));
719 $res['tag'] = implode(',', $tag_arr);
722 $attach = $item->get_enclosures();
725 foreach($attach as $att) {
726 $len = intval($att->get_length());
727 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
728 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
729 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
730 if(strpos($type,';'))
731 $type = substr($type,0,strpos($type,';'));
732 if((! $link) || (strpos($link,'http') !== 0))
738 $type = 'application/octet-stream';
740 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
742 $res['attach'] = implode(',', $att_arr);
745 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
748 $res['object'] = '<object>' . "\n";
749 $child = $rawobj[0]['child'];
750 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
751 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
752 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
754 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
755 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
756 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
757 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
758 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
759 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
760 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
761 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
763 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
764 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
765 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
766 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
768 $body = html2bb_video($body);
770 $config = HTMLPurifier_Config::createDefault();
771 $config->set('Cache.DefinitionImpl', null);
773 $purifier = new HTMLPurifier($config);
774 $body = $purifier->purify($body);
775 $body = html2bbcode($body);
778 $res['object'] .= '<content>' . $body . '</content>' . "\n";
781 $res['object'] .= '</object>' . "\n";
784 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
787 $res['target'] = '<target>' . "\n";
788 $child = $rawobj[0]['child'];
789 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
790 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
792 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
793 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
794 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
795 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
796 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
797 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
798 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
799 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
801 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
802 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
803 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
804 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
806 $body = html2bb_video($body);
808 $config = HTMLPurifier_Config::createDefault();
809 $config->set('Cache.DefinitionImpl', null);
811 $purifier = new HTMLPurifier($config);
812 $body = $purifier->purify($body);
813 $body = html2bbcode($body);
816 $res['target'] .= '<content>' . $body . '</content>' . "\n";
819 $res['target'] .= '</target>' . "\n";
822 // This is some experimental stuff. By now retweets are shown with "RT:"
823 // But: There is data so that the message could be shown similar to native retweets
824 // There is some better way to parse this array - but it didn't worked for me.
825 $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"];
826 if (is_array($child)) {
827 logger('get_atom_elements: Looking for status.net repeated message');
829 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
830 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
831 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
832 $uri = $author["uri"][0]["data"];
833 $name = $author["name"][0]["data"];
834 $avatar = @array_shift($author["link"][2]["attribs"]);
835 $avatar = $avatar["href"];
837 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
838 logger('get_atom_elements: fixing sender of repeated message.');
840 if (!intval(get_config('system','wall-to-wall_share'))) {
841 $prefix = "[share author='".str_replace("'", "'",$name).
843 "' avatar='".$avatar.
844 "' link='".$orig_uri."']";
846 $res["body"] = $prefix.html2bbcode($message)."[/share]";
848 $res["owner-name"] = $res["author-name"];
849 $res["owner-link"] = $res["author-link"];
850 $res["owner-avatar"] = $res["author-avatar"];
852 $res["author-name"] = $name;
853 $res["author-link"] = $uri;
854 $res["author-avatar"] = $avatar;
856 $res["body"] = html2bbcode($message);
861 // Search for ostatus conversation url
862 $links = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
864 if (is_array($links)) {
865 foreach ($links as $link) {
866 $conversation = array_shift($link["attribs"]);
868 if ($conversation["rel"] == "ostatus:conversation") {
869 $res["ostatus_conversation"] = $conversation["href"];
870 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
875 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
878 // Handle enclosures and treat them as preview picture
880 foreach ($attach AS $attachment)
881 if ($attachment->type == "image/jpeg")
882 $preview = $attachment->link;
884 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
885 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
887 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
888 unset($res["attach"]);
889 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
890 $res["body"] = add_page_info_to_body($res["body"]);
891 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
892 $res["body"] = add_page_info_to_body($res["body"]);
895 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
897 call_hooks('parse_atom', $arr);
902 function add_page_info_data($data) {
903 call_hooks('page_info_data', $data);
905 // It maybe is a rich content, but if it does have everything that a link has,
906 // then treat it that way
907 if (($data["type"] == "rich") AND is_string($data["title"]) AND
908 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
909 $data["type"] = "link";
911 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
914 if ($no_photos AND ($data["type"] == "photo"))
917 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
918 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
919 require_once("include/network.php");
920 $data["url"] = short_link($data["url"]);
923 if (($data["type"] != "photo") AND is_string($data["title"]))
924 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
926 if (($data["type"] != "video") AND ($photo != ""))
927 $text .= '[img]'.$photo.'[/img]';
928 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
929 $imagedata = $data["images"][0];
930 $text .= '[img]'.$imagedata["src"].'[/img]';
933 if (($data["type"] != "photo") AND is_string($data["text"]))
934 $text .= "[quote]".$data["text"]."[/quote]";
937 if (isset($data["keywords"]) AND count($data["keywords"])) {
940 foreach ($data["keywords"] AS $keyword) {
941 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
942 array("","", "", "", "", ""), $keyword);
943 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
947 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
950 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
951 require_once("mod/parse_url.php");
953 $data = Cache::get("parse_url:".$url);
955 $data = parseurl_getsiteinfo($url, true);
956 Cache::set("parse_url:".$url,serialize($data));
958 $data = unserialize($data);
961 $data["images"][0]["src"] = $photo;
963 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
965 if (!$keywords AND isset($data["keywords"]))
966 unset($data["keywords"]);
968 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
969 $list = explode(",", $keyword_blacklist);
970 foreach ($list AS $keyword) {
971 $keyword = trim($keyword);
972 $index = array_search($keyword, $data["keywords"]);
973 if ($index !== false)
974 unset($data["keywords"][$index]);
981 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
982 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
985 if (isset($data["keywords"]) AND count($data["keywords"])) {
987 foreach ($data["keywords"] AS $keyword) {
988 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
989 array("","", "", "", "", ""), $keyword);
994 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
1001 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1002 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1004 $text = add_page_info_data($data);
1009 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1011 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1013 $URLSearchString = "^\[\]";
1015 // Adding these spaces is a quick hack due to my problems with regular expressions :)
1016 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1019 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1021 // Convert urls without bbcode elements
1022 if (!$matches AND $texturl) {
1023 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1025 // Yeah, a hack. I really hate regular expressions :)
1027 $matches[1] = $matches[2];
1031 $footer = add_page_info($matches[1], $no_photos);
1033 // Remove the link from the body if the link is attached at the end of the post
1034 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1035 $removedlink = trim(str_replace($matches[1], "", $body));
1036 if (($removedlink == "") OR strstr($body, $removedlink))
1037 $body = $removedlink;
1039 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1040 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1041 if (($removedlink == "") OR strstr($body, $removedlink))
1042 $body = $removedlink;
1045 // Add the page information to the bottom
1046 if (isset($footer) AND (trim($footer) != ""))
1052 function encode_rel_links($links) {
1054 if(! ((is_array($links)) && (count($links))))
1056 foreach($links as $link) {
1058 if($link['attribs']['']['rel'])
1059 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1060 if($link['attribs']['']['type'])
1061 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1062 if($link['attribs']['']['href'])
1063 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1064 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1065 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1066 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1067 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1068 $o .= ' />' . "\n" ;
1075 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1077 // If it is a posting where users should get notifications, then define it as wall posting
1080 $arr['type'] = 'wall';
1082 $arr['last-child'] = 1;
1083 $arr['network'] = NETWORK_DFRN;
1086 // If a Diaspora signature structure was passed in, pull it out of the
1087 // item array and set it aside for later storage.
1090 if(x($arr,'dsprsig')) {
1091 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1092 unset($arr['dsprsig']);
1095 // if an OStatus conversation url was passed in, it is stored and then
1096 // removed from the array.
1097 $ostatus_conversation = null;
1099 if (isset($arr["ostatus_conversation"])) {
1100 $ostatus_conversation = $arr["ostatus_conversation"];
1101 unset($arr["ostatus_conversation"]);
1104 if(x($arr, 'gravity'))
1105 $arr['gravity'] = intval($arr['gravity']);
1106 elseif($arr['parent-uri'] === $arr['uri'])
1107 $arr['gravity'] = 0;
1108 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1109 $arr['gravity'] = 6;
1111 $arr['gravity'] = 6; // extensible catchall
1113 if(! x($arr,'type'))
1114 $arr['type'] = 'remote';
1118 /* check for create date and expire time */
1119 $uid = intval($arr['uid']);
1120 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1122 $expire_interval = $r[0]['expire'];
1123 if ($expire_interval>0) {
1124 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1125 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1126 if ($created_date < $expire_date) {
1127 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1133 // If there is no guid then take the same guid that was taken before for the same uri
1134 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1135 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1136 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1137 dbesc(trim($arr['uri']))
1141 $arr['guid'] = $r[0]["guid"];
1142 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1146 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1147 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1148 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1149 // $arr['body'] = strip_tags($arr['body']);
1152 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1153 require_once('library/langdet/Text/LanguageDetect.php');
1154 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1155 $l = new Text_LanguageDetect;
1156 //$lng = $l->detectConfidence($naked_body);
1157 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1158 $lng = $l->detect($naked_body, 3);
1160 if (sizeof($lng) > 0) {
1163 foreach ($lng as $language => $score) {
1164 if ($postopts == "")
1165 $postopts = "lang=";
1169 $postopts .= $language.";".$score;
1171 $arr['postopts'] = $postopts;
1175 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1176 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1177 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1178 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1179 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1180 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1181 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1182 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1183 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1184 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1185 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1186 $arr['commented'] = datetime_convert();
1187 $arr['received'] = datetime_convert();
1188 $arr['changed'] = datetime_convert();
1189 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1190 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1191 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1192 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1193 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1194 $arr['deleted'] = 0;
1195 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1196 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1197 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1198 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1199 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1200 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1201 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1202 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1203 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1204 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1205 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1206 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1207 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1208 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1209 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1210 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1211 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1212 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1213 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(30));
1214 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1215 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1216 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1217 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1218 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1219 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1221 if ($arr['plink'] == "") {
1223 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1226 if ($arr['network'] == "") {
1227 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1228 intval($arr['contact-id']),
1233 $arr['network'] = $r[0]["network"];
1235 // Fallback to friendica (why is it empty in some cases?)
1236 if ($arr['network'] == "")
1237 $arr['network'] = NETWORK_DFRN;
1239 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1242 // Check for hashtags in the body and repair or add hashtag links
1243 item_body_set_hashtags($arr);
1245 $arr['thr-parent'] = $arr['parent-uri'];
1246 if($arr['parent-uri'] === $arr['uri']) {
1248 $parent_deleted = 0;
1249 $allow_cid = $arr['allow_cid'];
1250 $allow_gid = $arr['allow_gid'];
1251 $deny_cid = $arr['deny_cid'];
1252 $deny_gid = $arr['deny_gid'];
1253 $notify_type = 'wall-new';
1257 // find the parent and snarf the item id and ACLs
1258 // and anything else we need to inherit
1260 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1261 dbesc($arr['parent-uri']),
1267 // is the new message multi-level threaded?
1268 // even though we don't support it now, preserve the info
1269 // and re-attach to the conversation parent.
1271 if($r[0]['uri'] != $r[0]['parent-uri']) {
1272 $arr['parent-uri'] = $r[0]['parent-uri'];
1273 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1274 ORDER BY `id` ASC LIMIT 1",
1275 dbesc($r[0]['parent-uri']),
1276 dbesc($r[0]['parent-uri']),
1283 $parent_id = $r[0]['id'];
1284 $parent_deleted = $r[0]['deleted'];
1285 $allow_cid = $r[0]['allow_cid'];
1286 $allow_gid = $r[0]['allow_gid'];
1287 $deny_cid = $r[0]['deny_cid'];
1288 $deny_gid = $r[0]['deny_gid'];
1289 $arr['wall'] = $r[0]['wall'];
1290 $notify_type = 'comment-new';
1292 // if the parent is private, force privacy for the entire conversation
1293 // This differs from the above settings as it subtly allows comments from
1294 // email correspondents to be private even if the overall thread is not.
1296 if($r[0]['private'])
1297 $arr['private'] = $r[0]['private'];
1299 // Edge case. We host a public forum that was originally posted to privately.
1300 // The original author commented, but as this is a comment, the permissions
1301 // weren't fixed up so it will still show the comment as private unless we fix it here.
1303 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1304 $arr['private'] = 0;
1307 // If its a post from myself then tag the thread as "mention"
1308 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1309 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1312 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1313 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1314 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1315 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1316 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1322 // Allow one to see reply tweets from status.net even when
1323 // we don't have or can't see the original post.
1326 logger('item_store: $force_parent=true, reply converted to top-level post.');
1328 $arr['parent-uri'] = $arr['uri'];
1329 $arr['gravity'] = 0;
1332 logger('item_store: item parent was not found - ignoring item');
1336 $parent_deleted = 0;
1340 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1344 if($r && count($r)) {
1345 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1349 call_hooks('post_remote',$arr);
1351 if(x($arr,'cancel')) {
1352 logger('item_store: post cancelled by plugin.');
1356 // Store the unescaped version
1361 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1363 $r = dbq("INSERT INTO `item` (`"
1364 . implode("`, `", array_keys($arr))
1366 . implode("', '", array_values($arr))
1372 // find the item we just created
1373 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1379 $current_post = $r[0]['id'];
1380 logger('item_store: created item ' . $current_post);
1382 // Set "success_update" to the date of the last time we heard from this contact
1383 // This can be used to filter for inactive contacts and poco.
1384 // Only do this for public postings to avoid privacy problems, since poco data is public.
1385 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1386 if (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])))
1387 q("UPDATE `contact` SET `success_update` = '%s' WHERE `id` = %d",
1388 dbesc($arr['received']),
1389 intval($arr['contact-id'])
1392 logger('item_store: could not locate created item');
1396 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1397 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1399 intval($arr['uid']),
1400 intval($current_post)
1404 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1405 $parent_id = $current_post;
1407 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1410 $private = $arr['private'];
1412 // Set parent id - and also make sure to inherit the parent's ACLs.
1414 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1415 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1422 intval($parent_deleted),
1423 intval($current_post)
1426 // Complete ostatus threads
1427 if ($ostatus_conversation)
1428 complete_conversation($current_post, $ostatus_conversation);
1430 $arr['id'] = $current_post;
1431 $arr['parent'] = $parent_id;
1432 $arr['allow_cid'] = $allow_cid;
1433 $arr['allow_gid'] = $allow_gid;
1434 $arr['deny_cid'] = $deny_cid;
1435 $arr['deny_gid'] = $deny_gid;
1436 $arr['private'] = $private;
1437 $arr['deleted'] = $parent_deleted;
1439 // update the commented timestamp on the parent
1440 // Only update "commented" if it is really a comment
1441 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1442 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1443 dbesc(datetime_convert()),
1444 dbesc(datetime_convert()),
1448 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1449 dbesc(datetime_convert()),
1454 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1455 intval($current_post),
1456 dbesc($dsprsig->signed_text),
1457 dbesc($dsprsig->signature),
1458 dbesc($dsprsig->signer)
1464 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1467 if($arr['last-child']) {
1468 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1470 intval($arr['uid']),
1471 intval($current_post)
1475 $deleted = tag_deliver($arr['uid'],$current_post);
1477 // current post can be deleted if is for a community page and no mention are
1479 if (!$deleted AND !$dontcache) {
1481 // Store the fresh generated item into the cache
1482 put_item_in_cache($arr);
1484 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1485 if (count($r) == 1) {
1486 call_hooks('post_remote_end', $r[0]);
1488 logger('item_store: new item not found in DB, id ' . $current_post);
1491 // Add every contact of the post to the global contact table
1494 create_tags_from_item($current_post);
1495 create_files_from_item($current_post);
1497 // Only check for notifications on start posts
1498 if ($arr['parent-uri'] === $arr['uri']) {
1499 add_thread($current_post);
1500 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1502 // Send a notification for every new post?
1503 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1504 intval($arr['contact-id']),
1507 $send_notification = count($r);
1509 if (!$send_notification) {
1510 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1511 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1514 foreach ($tags AS $tag) {
1515 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1516 normalise_link($tag["url"]), intval($arr['uid']));
1518 $send_notification = true;
1523 if ($send_notification) {
1524 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1525 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1526 intval($arr['uid']));
1528 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1529 intval($current_post),
1535 require_once('include/enotify.php');
1537 'type' => NOTIFY_SHARE,
1538 'notify_flags' => $u[0]['notify-flags'],
1539 'language' => $u[0]['language'],
1540 'to_name' => $u[0]['username'],
1541 'to_email' => $u[0]['email'],
1542 'uid' => $u[0]['uid'],
1544 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1545 'source_name' => $item[0]['author-name'],
1546 'source_link' => $item[0]['author-link'],
1547 'source_photo' => $item[0]['author-avatar'],
1548 'verb' => ACTIVITY_TAG,
1551 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1554 update_thread($parent_id);
1557 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1559 return $current_post;
1562 function item_body_set_hashtags(&$item) {
1564 $tags = get_tags($item["body"]);
1570 // This sorting is important when there are hashtags that are part of other hashtags
1571 // Otherwise there could be problems with hashtags like #test and #test2
1576 $URLSearchString = "^\[\]";
1578 // All hashtags should point to the home server
1579 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1580 "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1582 $item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1583 "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1585 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1586 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1588 return("[url=".$match[1]."]".str_replace("#", "#", $match[2])."[/url]");
1591 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1593 return("[bookmark=".$match[1]."]".str_replace("#", "#", $match[2])."[/bookmark]");
1596 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1598 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1601 // Repair recursive urls
1602 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1603 "#$2", $item["body"]);
1605 foreach($tags as $tag) {
1606 if(strpos($tag,'#') !== 0)
1609 if(strpos($tag,'[url='))
1612 $basetag = str_replace('_',' ',substr($tag,1));
1613 $item["body"] = str_replace($tag,'#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]', $item["body"]);
1616 // Convert back the masked hashtags
1617 $item["body"] = str_replace("#", "#", $item["body"]);
1620 function get_item_guid($id) {
1621 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1623 return($r[0]["guid"]);
1628 function get_item_id($guid, $uid = 0) {
1634 $uid == local_user();
1636 // Does the given user have this item?
1638 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1639 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1640 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1643 $nick = $r[0]["nickname"];
1647 // Or is it anywhere on the server?
1649 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1650 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1651 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1652 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1653 AND `item`.`private` = 0 AND `item`.`wall` = 1
1654 AND `item`.`guid` = '%s'", dbesc($guid));
1657 $nick = $r[0]["nickname"];
1660 return(array("nick" => $nick, "id" => $id));
1664 function get_item_contact($item,$contacts) {
1665 if(! count($contacts) || (! is_array($item)))
1667 foreach($contacts as $contact) {
1668 if($contact['id'] == $item['contact-id']) {
1670 break; // NOTREACHED
1677 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1679 * @param int $item_id
1680 * @return bool true if item was deleted, else false
1682 function tag_deliver($uid,$item_id) {
1690 $u = q("select * from user where uid = %d limit 1",
1696 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1697 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1700 $i = q("select * from item where id = %d and uid = %d limit 1",
1709 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1711 // Diaspora uses their own hardwired link URL in @-tags
1712 // instead of the one we supply with webfinger
1714 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1716 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1718 foreach($matches as $mtch) {
1719 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1721 logger('tag_deliver: mention found: ' . $mtch[2]);
1727 if ( ($community_page || $prvgroup) &&
1728 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1729 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1731 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1732 q("DELETE FROM item WHERE id = %d and uid = %d",
1742 // send a notification
1744 // use a local photo if we have one
1746 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1747 intval($u[0]['uid']),
1748 dbesc(normalise_link($item['author-link']))
1750 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1753 require_once('include/enotify.php');
1755 'type' => NOTIFY_TAGSELF,
1756 'notify_flags' => $u[0]['notify-flags'],
1757 'language' => $u[0]['language'],
1758 'to_name' => $u[0]['username'],
1759 'to_email' => $u[0]['email'],
1760 'uid' => $u[0]['uid'],
1762 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1763 'source_name' => $item['author-name'],
1764 'source_link' => $item['author-link'],
1765 'source_photo' => $photo,
1766 'verb' => ACTIVITY_TAG,
1768 'parent' => $item['parent']
1772 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1774 call_hooks('tagged', $arr);
1776 if((! $community_page) && (! $prvgroup))
1780 // tgroup delivery - setup a second delivery chain
1781 // prevent delivery looping - only proceed
1782 // if the message originated elsewhere and is a top-level post
1784 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1787 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1790 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1791 intval($u[0]['uid'])
1796 // also reset all the privacy bits to the forum default permissions
1798 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1800 $forum_mode = (($prvgroup) ? 2 : 1);
1802 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1803 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1804 intval($forum_mode),
1805 dbesc($c[0]['name']),
1806 dbesc($c[0]['url']),
1807 dbesc($c[0]['thumb']),
1809 dbesc($u[0]['allow_cid']),
1810 dbesc($u[0]['allow_gid']),
1811 dbesc($u[0]['deny_cid']),
1812 dbesc($u[0]['deny_gid']),
1815 update_thread($item_id);
1817 proc_run('php','include/notifier.php','tgroup',$item_id);
1823 function tgroup_check($uid,$item) {
1829 // check that the message originated elsewhere and is a top-level post
1831 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1835 $u = q("select * from user where uid = %d limit 1",
1841 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1842 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1845 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1847 // Diaspora uses their own hardwired link URL in @-tags
1848 // instead of the one we supply with webfinger
1850 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1852 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1854 foreach($matches as $mtch) {
1855 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1857 logger('tgroup_check: mention found: ' . $mtch[2]);
1865 if((! $community_page) && (! $prvgroup))
1879 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1883 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1885 if($contact['duplex'] && $contact['dfrn-id'])
1886 $idtosend = '0:' . $orig_id;
1887 if($contact['duplex'] && $contact['issued-id'])
1888 $idtosend = '1:' . $orig_id;
1890 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1892 $rino_enable = get_config('system','rino_encrypt');
1897 $ssl_val = intval(get_config('system','ssl_policy'));
1901 case SSL_POLICY_FULL:
1902 $ssl_policy = 'full';
1904 case SSL_POLICY_SELFSIGN:
1905 $ssl_policy = 'self';
1907 case SSL_POLICY_NONE:
1909 $ssl_policy = 'none';
1913 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1915 logger('dfrn_deliver: ' . $url);
1917 $xml = fetch_url($url);
1919 $curl_stat = $a->get_curl_code();
1921 return(-1); // timed out
1923 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1928 if(strpos($xml,'<?xml') === false) {
1929 logger('dfrn_deliver: no valid XML returned');
1930 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1934 $res = parse_xml_string($xml);
1936 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1937 return (($res->status) ? $res->status : 3);
1939 $postvars = array();
1940 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1941 $challenge = hex2bin((string) $res->challenge);
1942 $perm = (($res->perm) ? $res->perm : null);
1943 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1944 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1945 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1947 if($owner['page-flags'] == PAGE_PRVGROUP)
1950 $final_dfrn_id = '';
1953 if((($perm == 'rw') && (! intval($contact['writable'])))
1954 || (($perm == 'r') && (intval($contact['writable'])))) {
1955 q("update contact set writable = %d where id = %d",
1956 intval(($perm == 'rw') ? 1 : 0),
1957 intval($contact['id'])
1959 $contact['writable'] = (string) 1 - intval($contact['writable']);
1963 if(($contact['duplex'] && strlen($contact['pubkey']))
1964 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1965 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1966 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1967 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1970 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1971 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1974 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1976 if(strpos($final_dfrn_id,':') == 1)
1977 $final_dfrn_id = substr($final_dfrn_id,2);
1979 if($final_dfrn_id != $orig_id) {
1980 logger('dfrn_deliver: wrong dfrn_id.');
1981 // did not decode properly - cannot trust this site
1985 $postvars['dfrn_id'] = $idtosend;
1986 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1988 $postvars['dissolve'] = '1';
1991 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1992 $postvars['data'] = $atom;
1993 $postvars['perm'] = 'rw';
1996 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1997 $postvars['perm'] = 'r';
2000 $postvars['ssl_policy'] = $ssl_policy;
2003 $postvars['page'] = $page;
2005 if($rino && $rino_allowed && (! $dissolve)) {
2006 $key = substr(random_string(),0,16);
2007 $data = bin2hex(aes_encrypt($postvars['data'],$key));
2008 $postvars['data'] = $data;
2009 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2012 if($dfrn_version >= 2.1) {
2013 if(($contact['duplex'] && strlen($contact['pubkey']))
2014 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2015 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2017 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2020 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2024 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2025 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2028 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2032 logger('md5 rawkey ' . md5($postvars['key']));
2034 $postvars['key'] = bin2hex($postvars['key']);
2037 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2039 $xml = post_url($contact['notify'],$postvars);
2041 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2043 $curl_stat = $a->get_curl_code();
2044 if((! $curl_stat) || (! strlen($xml)))
2045 return(-1); // timed out
2047 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2050 if(strpos($xml,'<?xml') === false) {
2051 logger('dfrn_deliver: phase 2: no valid XML returned');
2052 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2056 if($contact['term-date'] != '0000-00-00 00:00:00') {
2057 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2058 require_once('include/Contact.php');
2059 unmark_for_death($contact);
2062 $res = parse_xml_string($xml);
2064 return $res->status;
2069 This function returns true if $update has an edited timestamp newer
2070 than $existing, i.e. $update contains new data which should override
2071 what's already there. If there is no timestamp yet, the update is
2072 assumed to be newer. If the update has no timestamp, the existing
2073 item is assumed to be up-to-date. If the timestamps are equal it
2074 assumes the update has been seen before and should be ignored.
2076 function edited_timestamp_is_newer($existing, $update) {
2077 if (!x($existing,'edited') || !$existing['edited']) {
2080 if (!x($update,'edited') || !$update['edited']) {
2083 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2084 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2085 return (strcmp($existing_edited, $update_edited) < 0);
2090 * consume_feed - process atom feed and update anything/everything we might need to update
2092 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2094 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2095 * It is this person's stuff that is going to be updated.
2096 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2097 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2098 * have a contact record.
2099 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2100 * might not) try and subscribe to it.
2101 * $datedir sorts in reverse order
2102 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2103 * imported prior to its children being seen in the stream unless we are certain
2104 * of how the feed is arranged/ordered.
2105 * With $pass = 1, we only pull parent items out of the stream.
2106 * With $pass = 2, we only pull children (comments/likes).
2108 * So running this twice, first with pass 1 and then with pass 2 will do the right
2109 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2110 * model where comments can have sub-threads. That would require some massive sorting
2111 * to get all the feed items into a mostly linear ordering, and might still require
2115 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2117 require_once('library/simplepie/simplepie.inc');
2118 require_once('include/contact_selectors.php');
2120 if(! strlen($xml)) {
2121 logger('consume_feed: empty input');
2125 $feed = new SimplePie();
2126 $feed->set_raw_data($xml);
2128 $feed->enable_order_by_date(true);
2130 $feed->enable_order_by_date(false);
2134 logger('consume_feed: Error parsing XML: ' . $feed->error());
2136 $permalink = $feed->get_permalink();
2138 // Check at the feed level for updated contact name and/or photo
2142 $photo_timestamp = '';
2145 $contact_updated = '';
2147 $hubs = $feed->get_links('hub');
2148 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2151 $hub = implode(',', $hubs);
2153 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2155 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2157 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2158 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2159 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2160 $new_name = $elems['name'][0]['data'];
2162 // Manually checking for changed contact names
2163 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2164 $name_updated = date("c");
2165 $photo_timestamp = date("c");
2168 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2169 if ($photo_timestamp == "")
2170 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2171 $photo_url = $elems['link'][0]['attribs']['']['href'];
2174 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2175 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2179 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2180 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2182 $contact_updated = $photo_timestamp;
2184 require_once("include/Photo.php");
2185 $photo_failure = false;
2186 $have_photo = false;
2188 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2189 intval($contact['id']),
2190 intval($contact['uid'])
2193 $resource_id = $r[0]['resource-id'];
2197 $resource_id = photo_new_resource();
2200 $img_str = fetch_url($photo_url,true);
2201 // guess mimetype from headers or filename
2202 $type = guess_image_type($photo_url,true);
2205 $img = new Photo($img_str, $type);
2206 if($img->is_valid()) {
2208 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2209 dbesc($resource_id),
2210 intval($contact['id']),
2211 intval($contact['uid'])
2215 $img->scaleImageSquare(175);
2217 $hash = $resource_id;
2218 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2220 $img->scaleImage(80);
2221 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2223 $img->scaleImage(48);
2224 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2228 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2229 WHERE `uid` = %d AND `id` = %d",
2230 dbesc(datetime_convert()),
2231 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2232 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2233 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2234 intval($contact['uid']),
2235 intval($contact['id'])
2240 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2241 if ($name_updated > $contact_updated)
2242 $contact_updated = $name_updated;
2244 $r = q("select * from contact where uid = %d and id = %d limit 1",
2245 intval($contact['uid']),
2246 intval($contact['id'])
2249 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2250 dbesc(notags(trim($new_name))),
2251 dbesc(datetime_convert()),
2252 intval($contact['uid']),
2253 intval($contact['id'])
2256 // do our best to update the name on content items
2259 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2260 dbesc(notags(trim($new_name))),
2261 dbesc($r[0]['name']),
2262 dbesc($r[0]['url']),
2263 intval($contact['uid'])
2268 if ($contact_updated AND $new_name AND $photo_url)
2269 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2271 if(strlen($birthday)) {
2272 if(substr($birthday,0,4) != $contact['bdyear']) {
2273 logger('consume_feed: updating birthday: ' . $birthday);
2277 * Add new birthday event for this person
2279 * $bdtext is just a readable placeholder in case the event is shared
2280 * with others. We will replace it during presentation to our $importer
2281 * to contain a sparkle link and perhaps a photo.
2285 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2286 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2289 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2290 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2291 intval($contact['uid']),
2292 intval($contact['id']),
2293 dbesc(datetime_convert()),
2294 dbesc(datetime_convert()),
2295 dbesc(datetime_convert('UTC','UTC', $birthday)),
2296 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2305 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2306 dbesc(substr($birthday,0,4)),
2307 intval($contact['uid']),
2308 intval($contact['id'])
2311 // This function is called twice without reloading the contact
2312 // Make sure we only create one event. This is why &$contact
2313 // is a reference var in this function
2315 $contact['bdyear'] = substr($birthday,0,4);
2319 $community_page = 0;
2320 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2322 $community_page = intval($rawtags[0]['data']);
2324 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2325 q("update contact set forum = %d where id = %d",
2326 intval($community_page),
2327 intval($contact['id'])
2329 $contact['forum'] = (string) $community_page;
2333 // process any deleted entries
2335 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2336 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2337 foreach($del_entries as $dentry) {
2339 if(isset($dentry['attribs']['']['ref'])) {
2340 $uri = $dentry['attribs']['']['ref'];
2342 if(isset($dentry['attribs']['']['when'])) {
2343 $when = $dentry['attribs']['']['when'];
2344 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2347 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2349 if($deleted && is_array($contact)) {
2350 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2351 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2353 intval($importer['uid']),
2354 intval($contact['id'])
2359 if(! $item['deleted'])
2360 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2362 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2363 $xo = parse_xml_string($item['object'],false);
2364 $xt = parse_xml_string($item['target'],false);
2365 if($xt->type === ACTIVITY_OBJ_NOTE) {
2366 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2368 intval($importer['importer_uid'])
2372 // For tags, the owner cannot remove the tag on the author's copy of the post.
2374 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2375 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2376 $author_copy = (($item['origin']) ? true : false);
2378 if($owner_remove && $author_copy)
2380 if($author_remove || $owner_remove) {
2381 $tags = explode(',',$i[0]['tag']);
2384 foreach($tags as $tag)
2385 if(trim($tag) !== trim($xo->body))
2386 $newtags[] = trim($tag);
2388 q("update item set tag = '%s' where id = %d",
2389 dbesc(implode(',',$newtags)),
2392 create_tags_from_item($i[0]['id']);
2398 if($item['uri'] == $item['parent-uri']) {
2399 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2400 `body` = '', `title` = ''
2401 WHERE `parent-uri` = '%s' AND `uid` = %d",
2403 dbesc(datetime_convert()),
2404 dbesc($item['uri']),
2405 intval($importer['uid'])
2407 create_tags_from_itemuri($item['uri'], $importer['uid']);
2408 create_files_from_itemuri($item['uri'], $importer['uid']);
2409 update_thread_uri($item['uri'], $importer['uid']);
2412 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2413 `body` = '', `title` = ''
2414 WHERE `uri` = '%s' AND `uid` = %d",
2416 dbesc(datetime_convert()),
2418 intval($importer['uid'])
2420 create_tags_from_itemuri($uri, $importer['uid']);
2421 create_files_from_itemuri($uri, $importer['uid']);
2422 if($item['last-child']) {
2423 // ensure that last-child is set in case the comment that had it just got wiped.
2424 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2425 dbesc(datetime_convert()),
2426 dbesc($item['parent-uri']),
2427 intval($item['uid'])
2429 // who is the last child now?
2430 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2431 ORDER BY `created` DESC LIMIT 1",
2432 dbesc($item['parent-uri']),
2433 intval($importer['uid'])
2436 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2447 // Now process the feed
2449 if($feed->get_item_quantity()) {
2451 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2453 // in inverse date order
2455 $items = array_reverse($feed->get_items());
2457 $items = $feed->get_items();
2460 foreach($items as $item) {
2463 $item_id = $item->get_id();
2464 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2465 if(isset($rawthread[0]['attribs']['']['ref'])) {
2467 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2470 if(($is_reply) && is_array($contact)) {
2475 // not allowed to post
2477 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2481 // Have we seen it? If not, import it.
2483 $item_id = $item->get_id();
2484 $datarray = get_atom_elements($feed, $item, $contact);
2486 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2487 $datarray['author-name'] = $contact['name'];
2488 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2489 $datarray['author-link'] = $contact['url'];
2490 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2491 $datarray['author-avatar'] = $contact['thumb'];
2493 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2494 logger('consume_feed: no author information! ' . print_r($datarray,true));
2498 $force_parent = false;
2499 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2500 if($contact['network'] === NETWORK_OSTATUS)
2501 $force_parent = true;
2502 if(strlen($datarray['title']))
2503 unset($datarray['title']);
2504 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2505 dbesc(datetime_convert()),
2507 intval($importer['uid'])
2509 $datarray['last-child'] = 1;
2510 update_thread_uri($parent_uri, $importer['uid']);
2514 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2516 intval($importer['uid'])
2519 // Update content if 'updated' changes
2522 if (edited_timestamp_is_newer($r[0], $datarray)) {
2524 // do not accept (ignore) an earlier edit than one we currently have.
2525 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2528 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2529 dbesc($datarray['title']),
2530 dbesc($datarray['body']),
2531 dbesc($datarray['tag']),
2532 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2533 dbesc(datetime_convert()),
2535 intval($importer['uid'])
2537 create_tags_from_itemuri($item_id, $importer['uid']);
2538 update_thread_uri($item_id, $importer['uid']);
2541 // update last-child if it changes
2543 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2544 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2545 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2546 dbesc(datetime_convert()),
2548 intval($importer['uid'])
2550 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2551 intval($allow[0]['data']),
2552 dbesc(datetime_convert()),
2554 intval($importer['uid'])
2556 update_thread_uri($item_id, $importer['uid']);
2562 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2563 // one way feed - no remote comment ability
2564 $datarray['last-child'] = 0;
2566 $datarray['parent-uri'] = $parent_uri;
2567 $datarray['uid'] = $importer['uid'];
2568 $datarray['contact-id'] = $contact['id'];
2569 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2570 $datarray['type'] = 'activity';
2571 $datarray['gravity'] = GRAVITY_LIKE;
2572 // only one like or dislike per person
2573 // splitted into two queries for performance issues
2574 $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",
2575 intval($datarray['uid']),
2576 intval($datarray['contact-id']),
2577 dbesc($datarray['verb']),
2583 $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",
2584 intval($datarray['uid']),
2585 intval($datarray['contact-id']),
2586 dbesc($datarray['verb']),
2593 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2594 $xo = parse_xml_string($datarray['object'],false);
2595 $xt = parse_xml_string($datarray['target'],false);
2597 if($xt->type == ACTIVITY_OBJ_NOTE) {
2598 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2600 intval($importer['importer_uid'])
2605 // extract tag, if not duplicate, add to parent item
2606 if($xo->id && $xo->content) {
2607 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2608 if(! (stristr($r[0]['tag'],$newtag))) {
2609 q("UPDATE item SET tag = '%s' WHERE id = %d",
2610 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2613 create_tags_from_item($r[0]['id']);
2619 $r = item_store($datarray,$force_parent);
2625 // Head post of a conversation. Have we seen it? If not, import it.
2627 $item_id = $item->get_id();
2629 $datarray = get_atom_elements($feed, $item, $contact);
2631 if(is_array($contact)) {
2632 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2633 $datarray['author-name'] = $contact['name'];
2634 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2635 $datarray['author-link'] = $contact['url'];
2636 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2637 $datarray['author-avatar'] = $contact['thumb'];
2640 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2641 logger('consume_feed: no author information! ' . print_r($datarray,true));
2645 // special handling for events
2647 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2648 $ev = bbtoevent($datarray['body']);
2649 if(x($ev,'desc') && x($ev,'start')) {
2650 $ev['uid'] = $importer['uid'];
2651 $ev['uri'] = $item_id;
2652 $ev['edited'] = $datarray['edited'];
2653 $ev['private'] = $datarray['private'];
2655 if(is_array($contact))
2656 $ev['cid'] = $contact['id'];
2657 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2659 intval($importer['uid'])
2662 $ev['id'] = $r[0]['id'];
2663 $xyz = event_store($ev);
2668 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2669 if(strlen($datarray['title']))
2670 unset($datarray['title']);
2671 $datarray['last-child'] = 1;
2675 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2677 intval($importer['uid'])
2680 // Update content if 'updated' changes
2683 if (edited_timestamp_is_newer($r[0], $datarray)) {
2685 // do not accept (ignore) an earlier edit than one we currently have.
2686 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2689 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2690 dbesc($datarray['title']),
2691 dbesc($datarray['body']),
2692 dbesc($datarray['tag']),
2693 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2694 dbesc(datetime_convert()),
2696 intval($importer['uid'])
2698 create_tags_from_itemuri($item_id, $importer['uid']);
2699 update_thread_uri($item_id, $importer['uid']);
2702 // update last-child if it changes
2704 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2705 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2706 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2707 intval($allow[0]['data']),
2708 dbesc(datetime_convert()),
2710 intval($importer['uid'])
2712 update_thread_uri($item_id, $importer['uid']);
2717 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2718 logger('consume-feed: New follower');
2719 new_follower($importer,$contact,$datarray,$item);
2722 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2723 lose_follower($importer,$contact,$datarray,$item);
2727 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2728 logger('consume-feed: New friend request');
2729 new_follower($importer,$contact,$datarray,$item,true);
2732 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2733 lose_sharer($importer,$contact,$datarray,$item);
2738 if(! is_array($contact))
2742 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2743 // one way feed - no remote comment ability
2744 $datarray['last-child'] = 0;
2746 if($contact['network'] === NETWORK_FEED)
2747 $datarray['private'] = 2;
2749 $datarray['parent-uri'] = $item_id;
2750 $datarray['uid'] = $importer['uid'];
2751 $datarray['contact-id'] = $contact['id'];
2753 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2754 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2755 // but otherwise there's a possible data mixup on the sender's system.
2756 // the tgroup delivery code called from item_store will correct it if it's a forum,
2757 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2758 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2759 $datarray['owner-name'] = $contact['name'];
2760 $datarray['owner-link'] = $contact['url'];
2761 $datarray['owner-avatar'] = $contact['thumb'];
2764 // We've allowed "followers" to reach this point so we can decide if they are
2765 // posting an @-tag delivery, which followers are allowed to do for certain
2766 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2768 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2771 // This is my contact on another system, but it's really me.
2772 // Turn this into a wall post.
2773 $notify = item_is_remote_self($contact, $datarray);
2775 $r = item_store($datarray, false, $notify);
2776 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2784 function item_is_remote_self($contact, &$datarray) {
2787 if (!$contact['remote_self'])
2790 // Prevent the forwarding of posts that are forwarded
2791 if ($datarray["extid"] == NETWORK_DFRN)
2794 // Prevent to forward already forwarded posts
2795 if ($datarray["app"] == $a->get_hostname())
2798 // Only forward posts
2799 if ($datarray["verb"] != ACTIVITY_POST)
2802 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2805 $datarray2 = $datarray;
2806 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2807 if ($contact['remote_self'] == 2) {
2808 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2809 intval($contact['uid']));
2811 $datarray['contact-id'] = $r[0]["id"];
2813 $datarray['owner-name'] = $r[0]["name"];
2814 $datarray['owner-link'] = $r[0]["url"];
2815 $datarray['owner-avatar'] = $r[0]["thumb"];
2817 $datarray['author-name'] = $datarray['owner-name'];
2818 $datarray['author-link'] = $datarray['owner-link'];
2819 $datarray['author-avatar'] = $datarray['owner-avatar'];
2822 if ($contact['network'] != NETWORK_FEED) {
2823 $datarray["guid"] = get_guid(32);
2824 unset($datarray["plink"]);
2825 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2826 $datarray["parent-uri"] = $datarray["uri"];
2827 $datarray["extid"] = $contact['network'];
2828 $urlpart = parse_url($datarray2['author-link']);
2829 $datarray["app"] = $urlpart["host"];
2831 $datarray['private'] = 0;
2834 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2835 // $datarray["app"] = network_to_name($contact['network']);
2837 if ($contact['network'] != NETWORK_FEED) {
2838 // Store the original post
2839 $r = item_store($datarray2, false, false);
2840 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2842 $datarray["app"] = "Feed";
2847 function local_delivery($importer,$data) {
2850 logger(__function__, LOGGER_TRACE);
2852 if($importer['readonly']) {
2853 // We aren't receiving stuff from this person. But we will quietly ignore them
2854 // rather than a blatant "go away" message.
2855 logger('local_delivery: ignoring');
2860 // Consume notification feed. This may differ from consuming a public feed in several ways
2861 // - might contain email or friend suggestions
2862 // - might contain remote followup to our message
2863 // - in which case we need to accept it and then notify other conversants
2864 // - we may need to send various email notifications
2866 $feed = new SimplePie();
2867 $feed->set_raw_data($data);
2868 $feed->enable_order_by_date(false);
2873 logger('local_delivery: Error parsing XML: ' . $feed->error());
2876 // Check at the feed level for updated contact name and/or photo
2880 $photo_timestamp = '';
2882 $contact_updated = '';
2885 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2887 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2889 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2892 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2893 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2894 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2895 $new_name = $elems['name'][0]['data'];
2897 // Manually checking for changed contact names
2898 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2899 $name_updated = date("c");
2900 $photo_timestamp = date("c");
2903 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2904 if ($photo_timestamp == "")
2905 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2906 $photo_url = $elems['link'][0]['attribs']['']['href'];
2910 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2912 $contact_updated = $photo_timestamp;
2914 logger('local_delivery: Updating photo for ' . $importer['name']);
2915 require_once("include/Photo.php");
2916 $photo_failure = false;
2917 $have_photo = false;
2919 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2920 intval($importer['id']),
2921 intval($importer['importer_uid'])
2924 $resource_id = $r[0]['resource-id'];
2928 $resource_id = photo_new_resource();
2931 $img_str = fetch_url($photo_url,true);
2932 // guess mimetype from headers or filename
2933 $type = guess_image_type($photo_url,true);
2936 $img = new Photo($img_str, $type);
2937 if($img->is_valid()) {
2939 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2940 dbesc($resource_id),
2941 intval($importer['id']),
2942 intval($importer['importer_uid'])
2946 $img->scaleImageSquare(175);
2948 $hash = $resource_id;
2949 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2951 $img->scaleImage(80);
2952 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2954 $img->scaleImage(48);
2955 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2959 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2960 WHERE `uid` = %d AND `id` = %d",
2961 dbesc(datetime_convert()),
2962 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2963 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2964 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2965 intval($importer['importer_uid']),
2966 intval($importer['id'])
2971 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2972 if ($name_updated > $contact_updated)
2973 $contact_updated = $name_updated;
2975 $r = q("select * from contact where uid = %d and id = %d limit 1",
2976 intval($importer['importer_uid']),
2977 intval($importer['id'])
2980 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2981 dbesc(notags(trim($new_name))),
2982 dbesc(datetime_convert()),
2983 intval($importer['importer_uid']),
2984 intval($importer['id'])
2987 // do our best to update the name on content items
2990 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2991 dbesc(notags(trim($new_name))),
2992 dbesc($r[0]['name']),
2993 dbesc($r[0]['url']),
2994 intval($importer['importer_uid'])
2999 if ($contact_updated AND $new_name AND $photo_url)
3000 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3002 // Currently unsupported - needs a lot of work
3003 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3004 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3005 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3007 $newloc['uid'] = $importer['importer_uid'];
3008 $newloc['cid'] = $importer['id'];
3009 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3010 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3011 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3012 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3013 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3014 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3015 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3016 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3017 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3018 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3019 /** relocated user must have original key pair */
3020 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3021 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3023 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3026 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3027 intval($importer['id']),
3028 intval($importer['importer_uid']));
3033 $x = q("UPDATE contact SET
3044 `site-pubkey` = '%s'
3045 WHERE id=%d AND uid=%d;",
3046 dbesc($newloc['name']),
3047 dbesc($newloc['photo']),
3048 dbesc($newloc['thumb']),
3049 dbesc($newloc['micro']),
3050 dbesc($newloc['url']),
3051 dbesc(normalise_link($newloc['url'])),
3052 dbesc($newloc['request']),
3053 dbesc($newloc['confirm']),
3054 dbesc($newloc['notify']),
3055 dbesc($newloc['poll']),
3056 dbesc($newloc['sitepubkey']),
3057 intval($importer['id']),
3058 intval($importer['importer_uid']));
3064 'owner-link' => array($old['url'], $newloc['url']),
3065 'author-link' => array($old['url'], $newloc['url']),
3066 'owner-avatar' => array($old['photo'], $newloc['photo']),
3067 'author-avatar' => array($old['photo'], $newloc['photo']),
3069 foreach ($fields as $n=>$f){
3070 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3073 intval($importer['importer_uid']));
3079 // merge with current record, current contents have priority
3080 // update record, set url-updated
3081 // update profile photos
3087 // handle friend suggestion notification
3089 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3090 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3091 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3093 $fsugg['uid'] = $importer['importer_uid'];
3094 $fsugg['cid'] = $importer['id'];
3095 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3096 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3097 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3098 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3099 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3101 // Does our member already have a friend matching this description?
3103 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3104 dbesc($fsugg['name']),
3105 dbesc(normalise_link($fsugg['url'])),
3106 intval($fsugg['uid'])
3111 // Do we already have an fcontact record for this person?
3114 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3115 dbesc($fsugg['url']),
3116 dbesc($fsugg['name']),
3117 dbesc($fsugg['request'])
3122 // OK, we do. Do we already have an introduction for this person ?
3123 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3124 intval($fsugg['uid']),
3131 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3132 dbesc($fsugg['name']),
3133 dbesc($fsugg['url']),
3134 dbesc($fsugg['photo']),
3135 dbesc($fsugg['request'])
3137 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3138 dbesc($fsugg['url']),
3139 dbesc($fsugg['name']),
3140 dbesc($fsugg['request'])
3145 // database record did not get created. Quietly give up.
3150 $hash = random_string();
3152 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3153 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3154 intval($fsugg['uid']),
3156 intval($fsugg['cid']),
3157 dbesc($fsugg['body']),
3159 dbesc(datetime_convert()),
3164 'type' => NOTIFY_SUGGEST,
3165 'notify_flags' => $importer['notify-flags'],
3166 'language' => $importer['language'],
3167 'to_name' => $importer['username'],
3168 'to_email' => $importer['email'],
3169 'uid' => $importer['importer_uid'],
3171 'link' => $a->get_baseurl() . '/notifications/intros',
3172 'source_name' => $importer['name'],
3173 'source_link' => $importer['url'],
3174 'source_photo' => $importer['photo'],
3175 'verb' => ACTIVITY_REQ_FRIEND,
3184 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3185 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3187 logger('local_delivery: private message received');
3190 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3193 $msg['uid'] = $importer['importer_uid'];
3194 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3195 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3196 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3197 $msg['contact-id'] = $importer['id'];
3198 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3199 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3201 $msg['replied'] = 0;
3202 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3203 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3204 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3208 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3209 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3211 // send notifications.
3213 require_once('include/enotify.php');
3215 $notif_params = array(
3216 'type' => NOTIFY_MAIL,
3217 'notify_flags' => $importer['notify-flags'],
3218 'language' => $importer['language'],
3219 'to_name' => $importer['username'],
3220 'to_email' => $importer['email'],
3221 'uid' => $importer['importer_uid'],
3223 'source_name' => $msg['from-name'],
3224 'source_link' => $importer['url'],
3225 'source_photo' => $importer['thumb'],
3226 'verb' => ACTIVITY_POST,
3230 notification($notif_params);
3236 $community_page = 0;
3237 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3239 $community_page = intval($rawtags[0]['data']);
3241 if(intval($importer['forum']) != $community_page) {
3242 q("update contact set forum = %d where id = %d",
3243 intval($community_page),
3244 intval($importer['id'])
3246 $importer['forum'] = (string) $community_page;
3249 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3251 // process any deleted entries
3253 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3254 if(is_array($del_entries) && count($del_entries)) {
3255 foreach($del_entries as $dentry) {
3257 if(isset($dentry['attribs']['']['ref'])) {
3258 $uri = $dentry['attribs']['']['ref'];
3260 if(isset($dentry['attribs']['']['when'])) {
3261 $when = $dentry['attribs']['']['when'];
3262 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3265 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3269 // check for relayed deletes to our conversation
3272 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3274 intval($importer['importer_uid'])
3277 $parent_uri = $r[0]['parent-uri'];
3278 if($r[0]['id'] != $r[0]['parent'])
3285 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3288 logger('local_delivery: possible community delete');
3291 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3293 // was the top-level post for this reply written by somebody on this site?
3294 // Specifically, the recipient?
3296 $is_a_remote_delete = false;
3298 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3299 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3300 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3301 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3302 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3303 AND `item`.`uid` = %d
3309 intval($importer['importer_uid'])
3312 $is_a_remote_delete = true;
3314 // Does this have the characteristics of a community or private group comment?
3315 // If it's a reply to a wall post on a community/prvgroup page it's a
3316 // valid community comment. Also forum_mode makes it valid for sure.
3317 // If neither, it's not.
3319 if($is_a_remote_delete && $community) {
3320 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3321 $is_a_remote_delete = false;
3322 logger('local_delivery: not a community delete');
3326 if($is_a_remote_delete) {
3327 logger('local_delivery: received remote delete');
3331 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3332 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3334 intval($importer['importer_uid']),
3335 intval($importer['id'])
3341 if($item['deleted'])
3344 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3346 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3347 $xo = parse_xml_string($item['object'],false);
3348 $xt = parse_xml_string($item['target'],false);
3350 if($xt->type === ACTIVITY_OBJ_NOTE) {
3351 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3353 intval($importer['importer_uid'])
3357 // For tags, the owner cannot remove the tag on the author's copy of the post.
3359 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3360 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3361 $author_copy = (($item['origin']) ? true : false);
3363 if($owner_remove && $author_copy)
3365 if($author_remove || $owner_remove) {
3366 $tags = explode(',',$i[0]['tag']);
3369 foreach($tags as $tag)
3370 if(trim($tag) !== trim($xo->body))
3371 $newtags[] = trim($tag);
3373 q("update item set tag = '%s' where id = %d",
3374 dbesc(implode(',',$newtags)),
3377 create_tags_from_item($i[0]['id']);
3383 if($item['uri'] == $item['parent-uri']) {
3384 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3385 `body` = '', `title` = ''
3386 WHERE `parent-uri` = '%s' AND `uid` = %d",
3388 dbesc(datetime_convert()),
3389 dbesc($item['uri']),
3390 intval($importer['importer_uid'])
3392 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3393 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3394 update_thread_uri($item['uri'], $importer['importer_uid']);
3397 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3398 `body` = '', `title` = ''
3399 WHERE `uri` = '%s' AND `uid` = %d",
3401 dbesc(datetime_convert()),
3403 intval($importer['importer_uid'])
3405 create_tags_from_itemuri($uri, $importer['importer_uid']);
3406 create_files_from_itemuri($uri, $importer['importer_uid']);
3407 update_thread_uri($uri, $importer['importer_uid']);
3408 if($item['last-child']) {
3409 // ensure that last-child is set in case the comment that had it just got wiped.
3410 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3411 dbesc(datetime_convert()),
3412 dbesc($item['parent-uri']),
3413 intval($item['uid'])
3415 // who is the last child now?
3416 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3417 ORDER BY `created` DESC LIMIT 1",
3418 dbesc($item['parent-uri']),
3419 intval($importer['importer_uid'])
3422 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3427 // if this is a relayed delete, propagate it to other recipients
3429 if($is_a_remote_delete)
3430 proc_run('php',"include/notifier.php","drop",$item['id']);
3438 foreach($feed->get_items() as $item) {
3441 $item_id = $item->get_id();
3442 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3443 if(isset($rawthread[0]['attribs']['']['ref'])) {
3445 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3451 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3454 logger('local_delivery: possible community reply');
3457 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3459 // was the top-level post for this reply written by somebody on this site?
3460 // Specifically, the recipient?
3462 $is_a_remote_comment = false;
3463 $top_uri = $parent_uri;
3465 $r = q("select `item`.`parent-uri` from `item`
3466 WHERE `item`.`uri` = '%s'
3470 if($r && count($r)) {
3471 $top_uri = $r[0]['parent-uri'];
3473 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3474 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3475 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3476 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3477 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3478 AND `item`.`uid` = %d
3484 intval($importer['importer_uid'])
3487 $is_a_remote_comment = true;
3490 // Does this have the characteristics of a community or private group comment?
3491 // If it's a reply to a wall post on a community/prvgroup page it's a
3492 // valid community comment. Also forum_mode makes it valid for sure.
3493 // If neither, it's not.
3495 if($is_a_remote_comment && $community) {
3496 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3497 $is_a_remote_comment = false;
3498 logger('local_delivery: not a community reply');
3502 if($is_a_remote_comment) {
3503 logger('local_delivery: received remote comment');
3505 // remote reply to our post. Import and then notify everybody else.
3507 $datarray = get_atom_elements($feed, $item);
3509 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3511 intval($importer['importer_uid'])
3514 // Update content if 'updated' changes
3518 if (edited_timestamp_is_newer($r[0], $datarray)) {
3520 // do not accept (ignore) an earlier edit than one we currently have.
3521 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3524 logger('received updated comment' , LOGGER_DEBUG);
3525 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3526 dbesc($datarray['title']),
3527 dbesc($datarray['body']),
3528 dbesc($datarray['tag']),
3529 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3530 dbesc(datetime_convert()),
3532 intval($importer['importer_uid'])
3534 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3536 proc_run('php',"include/notifier.php","comment-import",$iid);
3545 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3546 intval($importer['importer_uid'])
3550 $datarray['type'] = 'remote-comment';
3551 $datarray['wall'] = 1;
3552 $datarray['parent-uri'] = $parent_uri;
3553 $datarray['uid'] = $importer['importer_uid'];
3554 $datarray['owner-name'] = $own[0]['name'];
3555 $datarray['owner-link'] = $own[0]['url'];
3556 $datarray['owner-avatar'] = $own[0]['thumb'];
3557 $datarray['contact-id'] = $importer['id'];
3559 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3561 $datarray['type'] = 'activity';
3562 $datarray['gravity'] = GRAVITY_LIKE;
3563 $datarray['last-child'] = 0;
3564 // only one like or dislike per person
3565 // splitted into two queries for performance issues
3566 $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",
3567 intval($datarray['uid']),
3568 intval($datarray['contact-id']),
3569 dbesc($datarray['verb']),
3570 dbesc($datarray['parent-uri'])
3576 $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",
3577 intval($datarray['uid']),
3578 intval($datarray['contact-id']),
3579 dbesc($datarray['verb']),
3580 dbesc($datarray['parent-uri'])
3587 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3589 $xo = parse_xml_string($datarray['object'],false);
3590 $xt = parse_xml_string($datarray['target'],false);
3592 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3594 // fetch the parent item
3596 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3598 intval($importer['importer_uid'])
3603 // extract tag, if not duplicate, and this user allows tags, add to parent item
3605 if($xo->id && $xo->content) {
3606 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3607 if(! (stristr($tagp[0]['tag'],$newtag))) {
3608 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3609 intval($importer['importer_uid'])
3611 if(count($i) && ! intval($i[0]['blocktags'])) {
3612 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3613 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3614 intval($tagp[0]['id']),
3615 dbesc(datetime_convert()),
3616 dbesc(datetime_convert())
3618 create_tags_from_item($tagp[0]['id']);
3626 $posted_id = item_store($datarray);
3630 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3632 intval($importer['importer_uid'])
3635 $parent = $r[0]['parent'];
3636 $parent_uri = $r[0]['parent-uri'];
3640 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3641 dbesc(datetime_convert()),
3642 intval($importer['importer_uid']),
3643 intval($r[0]['parent'])
3646 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3647 dbesc(datetime_convert()),
3648 intval($importer['importer_uid']),
3653 if($posted_id && $parent) {
3655 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3657 if((! $is_like) && (! $importer['self'])) {
3659 require_once('include/enotify.php');
3662 'type' => NOTIFY_COMMENT,
3663 'notify_flags' => $importer['notify-flags'],
3664 'language' => $importer['language'],
3665 'to_name' => $importer['username'],
3666 'to_email' => $importer['email'],
3667 'uid' => $importer['importer_uid'],
3668 'item' => $datarray,
3669 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3670 'source_name' => stripslashes($datarray['author-name']),
3671 'source_link' => $datarray['author-link'],
3672 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3673 ? $importer['thumb'] : $datarray['author-avatar']),
3674 'verb' => ACTIVITY_POST,
3676 'parent' => $parent,
3677 'parent_uri' => $parent_uri,
3689 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3691 $item_id = $item->get_id();
3692 $datarray = get_atom_elements($feed,$item);
3694 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3697 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3699 intval($importer['importer_uid'])
3702 // Update content if 'updated' changes
3705 if (edited_timestamp_is_newer($r[0], $datarray)) {
3707 // do not accept (ignore) an earlier edit than one we currently have.
3708 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3711 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3712 dbesc($datarray['title']),
3713 dbesc($datarray['body']),
3714 dbesc($datarray['tag']),
3715 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3716 dbesc(datetime_convert()),
3718 intval($importer['importer_uid'])
3720 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3723 // update last-child if it changes
3725 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3726 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3727 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3728 dbesc(datetime_convert()),
3730 intval($importer['importer_uid'])
3732 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3733 intval($allow[0]['data']),
3734 dbesc(datetime_convert()),
3736 intval($importer['importer_uid'])
3742 $datarray['parent-uri'] = $parent_uri;
3743 $datarray['uid'] = $importer['importer_uid'];
3744 $datarray['contact-id'] = $importer['id'];
3745 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3746 $datarray['type'] = 'activity';
3747 $datarray['gravity'] = GRAVITY_LIKE;
3748 // only one like or dislike per person
3749 // splitted into two queries for performance issues
3750 $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",
3751 intval($datarray['uid']),
3752 intval($datarray['contact-id']),
3753 dbesc($datarray['verb']),
3759 $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",
3760 intval($datarray['uid']),
3761 intval($datarray['contact-id']),
3762 dbesc($datarray['verb']),
3770 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3772 $xo = parse_xml_string($datarray['object'],false);
3773 $xt = parse_xml_string($datarray['target'],false);
3775 if($xt->type == ACTIVITY_OBJ_NOTE) {
3776 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3778 intval($importer['importer_uid'])
3783 // extract tag, if not duplicate, add to parent item
3785 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3786 q("UPDATE item SET tag = '%s' WHERE id = %d",
3787 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3790 create_tags_from_item($r[0]['id']);
3796 $posted_id = item_store($datarray);
3798 // find out if our user is involved in this conversation and wants to be notified.
3800 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3802 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3804 intval($importer['importer_uid'])
3807 if(count($myconv)) {
3808 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3810 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3811 if(! link_compare($datarray['author-link'],$importer_url)) {
3814 foreach($myconv as $conv) {
3816 // now if we find a match, it means we're in this conversation
3818 if(! link_compare($conv['author-link'],$importer_url))
3821 require_once('include/enotify.php');
3823 $conv_parent = $conv['parent'];
3826 'type' => NOTIFY_COMMENT,
3827 'notify_flags' => $importer['notify-flags'],
3828 'language' => $importer['language'],
3829 'to_name' => $importer['username'],
3830 'to_email' => $importer['email'],
3831 'uid' => $importer['importer_uid'],
3832 'item' => $datarray,
3833 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3834 'source_name' => stripslashes($datarray['author-name']),
3835 'source_link' => $datarray['author-link'],
3836 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3837 ? $importer['thumb'] : $datarray['author-avatar']),
3838 'verb' => ACTIVITY_POST,
3840 'parent' => $conv_parent,
3841 'parent_uri' => $parent_uri
3845 // only send one notification
3857 // Head post of a conversation. Have we seen it? If not, import it.
3860 $item_id = $item->get_id();
3861 $datarray = get_atom_elements($feed,$item);
3863 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3864 $ev = bbtoevent($datarray['body']);
3865 if(x($ev,'desc') && x($ev,'start')) {
3866 $ev['cid'] = $importer['id'];
3867 $ev['uid'] = $importer['uid'];
3868 $ev['uri'] = $item_id;
3869 $ev['edited'] = $datarray['edited'];
3870 $ev['private'] = $datarray['private'];
3872 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3874 intval($importer['uid'])
3877 $ev['id'] = $r[0]['id'];
3878 $xyz = event_store($ev);
3883 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3885 intval($importer['importer_uid'])
3888 // Update content if 'updated' changes
3891 if (edited_timestamp_is_newer($r[0], $datarray)) {
3893 // do not accept (ignore) an earlier edit than one we currently have.
3894 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3897 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3898 dbesc($datarray['title']),
3899 dbesc($datarray['body']),
3900 dbesc($datarray['tag']),
3901 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3902 dbesc(datetime_convert()),
3904 intval($importer['importer_uid'])
3906 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3907 update_thread_uri($item_id, $importer['importer_uid']);
3910 // update last-child if it changes
3912 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3913 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3914 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3915 intval($allow[0]['data']),
3916 dbesc(datetime_convert()),
3918 intval($importer['importer_uid'])
3924 $datarray['parent-uri'] = $item_id;
3925 $datarray['uid'] = $importer['importer_uid'];
3926 $datarray['contact-id'] = $importer['id'];
3929 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3930 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3931 // but otherwise there's a possible data mixup on the sender's system.
3932 // the tgroup delivery code called from item_store will correct it if it's a forum,
3933 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3934 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3935 $datarray['owner-name'] = $importer['senderName'];
3936 $datarray['owner-link'] = $importer['url'];
3937 $datarray['owner-avatar'] = $importer['thumb'];
3940 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3943 // This is my contact on another system, but it's really me.
3944 // Turn this into a wall post.
3945 $notify = item_is_remote_self($importer, $datarray);
3947 $posted_id = item_store($datarray, false, $notify);
3949 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3950 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3953 $xo = parse_xml_string($datarray['object'],false);
3955 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3957 // somebody was poked/prodded. Was it me?
3959 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3961 foreach($links->link as $l) {
3962 $atts = $l->attributes();
3963 switch($atts['rel']) {
3965 $Blink = $atts['href'];
3971 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3973 // send a notification
3974 require_once('include/enotify.php');
3977 'type' => NOTIFY_POKE,
3978 'notify_flags' => $importer['notify-flags'],
3979 'language' => $importer['language'],
3980 'to_name' => $importer['username'],
3981 'to_email' => $importer['email'],
3982 'uid' => $importer['importer_uid'],
3983 'item' => $datarray,
3984 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3985 'source_name' => stripslashes($datarray['author-name']),
3986 'source_link' => $datarray['author-link'],
3987 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3988 ? $importer['thumb'] : $datarray['author-avatar']),
3989 'verb' => $datarray['verb'],
3990 'otype' => 'person',
3991 'activity' => $verb,
4008 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4009 $url = notags(trim($datarray['author-link']));
4010 $name = notags(trim($datarray['author-name']));
4011 $photo = notags(trim($datarray['author-avatar']));
4013 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4014 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4015 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4017 if(is_array($contact)) {
4018 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4019 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4020 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4021 intval(CONTACT_IS_FRIEND),
4022 intval($contact['id']),
4023 intval($importer['uid'])
4026 // send email notification to owner?
4030 // create contact record
4032 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4033 `blocked`, `readonly`, `pending`, `writable` )
4034 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4035 intval($importer['uid']),
4036 dbesc(datetime_convert()),
4038 dbesc(normalise_link($url)),
4042 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4043 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4045 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4046 intval($importer['uid']),
4050 $contact_record = $r[0];
4052 // create notification
4053 $hash = random_string();
4055 if(is_array($contact_record)) {
4056 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4057 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4058 intval($importer['uid']),
4059 intval($contact_record['id']),
4061 dbesc(datetime_convert())
4065 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4066 intval($importer['uid'])
4071 if(intval($r[0]['def_gid'])) {
4072 require_once('include/group.php');
4073 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4076 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4077 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
4082 'type' => NOTIFY_INTRO,
4083 'notify_flags' => $r[0]['notify-flags'],
4084 'language' => $r[0]['language'],
4085 'to_name' => $r[0]['username'],
4086 'to_email' => $r[0]['email'],
4087 'uid' => $r[0]['uid'],
4088 'link' => $a->get_baseurl() . '/notifications/intro',
4089 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4090 'source_link' => $contact_record['url'],
4091 'source_photo' => $contact_record['photo'],
4092 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4102 function lose_follower($importer,$contact,$datarray,$item) {
4104 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4105 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4106 intval(CONTACT_IS_SHARING),
4107 intval($contact['id'])
4111 contact_remove($contact['id']);
4115 function lose_sharer($importer,$contact,$datarray,$item) {
4117 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4118 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4119 intval(CONTACT_IS_FOLLOWER),
4120 intval($contact['id'])
4124 contact_remove($contact['id']);
4129 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4133 if(is_array($importer)) {
4134 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4135 intval($importer['uid'])
4139 // Diaspora has different message-ids in feeds than they do
4140 // through the direct Diaspora protocol. If we try and use
4141 // the feed, we'll get duplicates. So don't.
4143 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4146 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4148 // Use a single verify token, even if multiple hubs
4150 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4152 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4154 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4156 if(! strlen($contact['hub-verify'])) {
4157 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4158 dbesc($verify_token),
4159 intval($contact['id'])
4163 post_url($url,$params);
4165 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4172 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4176 $name = xmlify($name);
4177 $uri = xmlify($uri);
4180 $photo = xmlify($photo);
4184 $o .= "<name>$name</name>\r\n";
4185 $o .= "<uri>$uri</uri>\r\n";
4186 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4187 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4189 call_hooks('atom_author', $o);
4191 $o .= "</$tag>\r\n";
4195 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4199 if(! $item['parent'])
4202 if($item['deleted'])
4203 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4206 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4207 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4209 $body = $item['body'];
4212 $o = "\r\n\r\n<entry>\r\n";
4214 if(is_array($author))
4215 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4217 $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']));
4218 if(strlen($item['owner-name']))
4219 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4221 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4222 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4223 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4228 if ($item['title'] != "")
4229 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4231 $htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4233 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4234 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4235 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4236 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4237 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4238 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4239 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4243 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4245 if($item['location']) {
4246 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4247 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4251 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4253 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4254 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4257 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4258 if($item['bookmark'])
4259 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4262 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4265 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4267 if($item['signed_text']) {
4268 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4269 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4272 $verb = construct_verb($item);
4273 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4274 $actobj = construct_activity_object($item);
4277 $actarg = construct_activity_target($item);
4281 $tags = item_getfeedtags($item);
4283 foreach($tags as $t) {
4284 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4288 $o .= item_getfeedattach($item);
4290 $mentioned = get_mentions($item);
4294 call_hooks('atom_entry', $o);
4296 $o .= '</entry>' . "\r\n";
4301 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4303 if(get_config('system','disable_embedded'))
4308 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4309 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4314 $img_start = strpos($orig_body, '[img');
4315 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4316 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4317 while( ($img_st_close !== false) && ($img_len !== false) ) {
4319 $img_st_close++; // make it point to AFTER the closing bracket
4320 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4322 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4325 if(stristr($image , $site . '/photo/')) {
4326 // Only embed locally hosted photos
4328 $i = basename($image);
4329 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4330 $x = strpos($i,'-');
4333 $res = substr($i,$x+1);
4334 $i = substr($i,0,$x);
4335 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4342 // Check to see if we should replace this photo link with an embedded image
4343 // 1. No need to do so if the photo is public
4344 // 2. If there's a contact-id provided, see if they're in the access list
4345 // for the photo. If so, embed it.
4346 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4347 // permissions, regardless of order but first check to see if they're an exact
4348 // match to save some processing overhead.
4350 if(has_permissions($r[0])) {
4352 $recips = enumerate_permissions($r[0]);
4353 if(in_array($cid, $recips)) {
4358 if(compare_permissions($item,$r[0]))
4363 $data = $r[0]['data'];
4364 $type = $r[0]['type'];
4366 // If a custom width and height were specified, apply before embedding
4367 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4368 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4370 $width = intval($match[1]);
4371 $height = intval($match[2]);
4373 $ph = new Photo($data, $type);
4374 if($ph->is_valid()) {
4375 $ph->scaleImage(max($width, $height));
4376 $data = $ph->imageString();
4377 $type = $ph->getType();
4381 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4382 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4383 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4389 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4390 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4391 if($orig_body === false)
4394 $img_start = strpos($orig_body, '[img');
4395 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4396 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4399 $new_body = $new_body . $orig_body;
4405 function has_permissions($obj) {
4406 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4411 function compare_permissions($obj1,$obj2) {
4412 // first part is easy. Check that these are exactly the same.
4413 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4414 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4415 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4416 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4419 // This is harder. Parse all the permissions and compare the resulting set.
4421 $recipients1 = enumerate_permissions($obj1);
4422 $recipients2 = enumerate_permissions($obj2);
4425 if($recipients1 == $recipients2)
4430 // returns an array of contact-ids that are allowed to see this object
4432 function enumerate_permissions($obj) {
4433 require_once('include/group.php');
4434 $allow_people = expand_acl($obj['allow_cid']);
4435 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4436 $deny_people = expand_acl($obj['deny_cid']);
4437 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4438 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4439 $deny = array_unique(array_merge($deny_people,$deny_groups));
4440 $recipients = array_diff($recipients,$deny);
4444 function item_getfeedtags($item) {
4447 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4449 for($x = 0; $x < $cnt; $x ++) {
4451 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4455 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4457 for($x = 0; $x < $cnt; $x ++) {
4459 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4465 function item_getfeedattach($item) {
4467 $arr = explode('[/attach],',$item['attach']);
4469 foreach($arr as $r) {
4471 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4473 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4474 if(intval($matches[2]))
4475 $ret .= 'length="' . intval($matches[2]) . '" ';
4476 if($matches[4] !== ' ')
4477 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4478 $ret .= ' />' . "\r\n";
4487 function item_expire($uid, $days, $network = "", $force = false) {
4489 if((! $uid) || ($days < 1))
4492 // $expire_network_only = save your own wall posts
4493 // and just expire conversations started by others
4495 $expire_network_only = get_pconfig($uid,'expire','network_only');
4496 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4498 if ($network != "") {
4499 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4500 // There is an index "uid_network_received" but not "uid_network_created"
4501 // This avoids the creation of another index just for one purpose.
4502 // And it doesn't really matter wether to look at "received" or "created"
4503 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4505 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4507 $r = q("SELECT * FROM `item`
4508 WHERE `uid` = %d $range
4519 $expire_items = get_pconfig($uid, 'expire','items');
4520 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4522 // Forcing expiring of items - but not notes and marked items
4524 $expire_items = true;
4526 $expire_notes = get_pconfig($uid, 'expire','notes');
4527 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4529 $expire_starred = get_pconfig($uid, 'expire','starred');
4530 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4532 $expire_photos = get_pconfig($uid, 'expire','photos');
4533 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4535 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4537 foreach($r as $item) {
4539 // don't expire filed items
4541 if(strpos($item['file'],'[') !== false)
4544 // Only expire posts, not photos and photo comments
4546 if($expire_photos==0 && strlen($item['resource-id']))
4548 if($expire_starred==0 && intval($item['starred']))
4550 if($expire_notes==0 && $item['type']=='note')
4552 if($expire_items==0 && $item['type']!='note')
4555 drop_item($item['id'],false);
4558 proc_run('php',"include/notifier.php","expire","$uid");
4563 function drop_items($items) {
4566 if(! local_user() && ! remote_user())
4570 foreach($items as $item) {
4571 $owner = drop_item($item,false);
4572 if($owner && ! $uid)
4577 // multiple threads may have been deleted, send an expire notification
4580 proc_run('php',"include/notifier.php","expire","$uid");
4584 function drop_item($id,$interactive = true) {
4588 // locate item to be deleted
4590 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4597 notice( t('Item not found.') . EOL);
4598 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4603 $owner = $item['uid'];
4607 // check if logged in user is either the author or owner of this item
4609 if(is_array($_SESSION['remote'])) {
4610 foreach($_SESSION['remote'] as $visitor) {
4611 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4612 $cid = $visitor['cid'];
4619 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4621 // Check if we should do HTML-based delete confirmation
4622 if($_REQUEST['confirm']) {
4623 // <form> can't take arguments in its "action" parameter
4624 // so add any arguments as hidden inputs
4625 $query = explode_querystring($a->query_string);
4627 foreach($query['args'] as $arg) {
4628 if(strpos($arg, 'confirm=') === false) {
4629 $arg_parts = explode('=', $arg);
4630 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4634 return replace_macros(get_markup_template('confirm.tpl'), array(
4636 '$message' => t('Do you really want to delete this item?'),
4637 '$extra_inputs' => $inputs,
4638 '$confirm' => t('Yes'),
4639 '$confirm_url' => $query['base'],
4640 '$confirm_name' => 'confirmed',
4641 '$cancel' => t('Cancel'),
4644 // Now check how the user responded to the confirmation query
4645 if($_REQUEST['canceled']) {
4646 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4649 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4652 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4653 dbesc(datetime_convert()),
4654 dbesc(datetime_convert()),
4657 create_tags_from_item($item['id']);
4658 create_files_from_item($item['id']);
4659 delete_thread($item['id'], $item['parent-uri']);
4661 // clean up categories and tags so they don't end up as orphans
4664 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4666 foreach($matches as $mtch) {
4667 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4673 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4675 foreach($matches as $mtch) {
4676 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4680 // If item is a link to a photo resource, nuke all the associated photos
4681 // (visitors will not have photo resources)
4682 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4683 // generate a resource-id and therefore aren't intimately linked to the item.
4685 if(strlen($item['resource-id'])) {
4686 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4687 dbesc($item['resource-id']),
4688 intval($item['uid'])
4690 // ignore the result
4693 // If item is a link to an event, nuke the event record.
4695 if(intval($item['event-id'])) {
4696 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4697 intval($item['event-id']),
4698 intval($item['uid'])
4700 // ignore the result
4703 // clean up item_id and sign meta-data tables
4706 // Old code - caused very long queries and warning entries in the mysql logfiles:
4708 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4709 intval($item['id']),
4710 intval($item['uid'])
4713 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4714 intval($item['id']),
4715 intval($item['uid'])
4719 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4721 // Creating list of parents
4722 $r = q("select id from item where parent = %d and uid = %d",
4723 intval($item['id']),
4724 intval($item['uid'])
4729 foreach ($r AS $row) {
4730 if ($parentid != "")
4733 $parentid .= $row["id"];
4737 if ($parentid != "") {
4738 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4740 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4743 // If it's the parent of a comment thread, kill all the kids
4745 if($item['uri'] == $item['parent-uri']) {
4746 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4747 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4748 dbesc(datetime_convert()),
4749 dbesc(datetime_convert()),
4750 dbesc($item['parent-uri']),
4751 intval($item['uid'])
4753 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4754 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4755 delete_thread_uri($item['parent-uri'], $item['uid']);
4756 // ignore the result
4759 // ensure that last-child is set in case the comment that had it just got wiped.
4760 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4761 dbesc(datetime_convert()),
4762 dbesc($item['parent-uri']),
4763 intval($item['uid'])
4765 // who is the last child now?
4766 $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",
4767 dbesc($item['parent-uri']),
4768 intval($item['uid'])
4771 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4776 // Add a relayable_retraction signature for Diaspora.
4777 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4779 $drop_id = intval($item['id']);
4781 // send the notification upstream/downstream as the case may be
4783 proc_run('php',"include/notifier.php","drop","$drop_id");
4787 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4793 notice( t('Permission denied.') . EOL);
4794 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4801 function first_post_date($uid,$wall = false) {
4802 $r = q("select id, created from item
4803 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4805 order by created asc limit 1",
4807 intval($wall ? 1 : 0)
4810 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4811 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4816 function posted_dates($uid,$wall) {
4817 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4819 $dthen = first_post_date($uid,$wall);
4823 // Set the start and end date to the beginning of the month
4824 $dnow = substr($dnow,0,8).'01';
4825 $dthen = substr($dthen,0,8).'01';
4828 // Starting with the current month, get the first and last days of every
4829 // month down to and including the month of the first post
4830 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4831 $dstart = substr($dnow,0,8) . '01';
4832 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4833 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4834 $end_month = datetime_convert('','',$dend,'Y-m-d');
4835 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4836 $ret[] = array($str,$end_month,$start_month);
4837 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4843 function posted_date_widget($url,$uid,$wall) {
4846 if(! feature_enabled($uid,'archives'))
4849 // For former Facebook folks that left because of "timeline"
4851 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4854 $ret = posted_dates($uid,$wall);
4858 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4859 '$title' => t('Archives'),
4860 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4867 function store_diaspora_retract_sig($item, $user, $baseurl) {
4868 // Note that we can't add a target_author_signature
4869 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4870 // the comment, that means we're the home of the post, and Diaspora will only
4871 // check the parent_author_signature of retractions that it doesn't have to relay further
4873 // I don't think this function gets called for an "unlike," but I'll check anyway
4875 $enabled = intval(get_config('system','diaspora_enabled'));
4877 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4881 logger('drop_item: storing diaspora retraction signature');
4883 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4885 if(local_user() == $item['uid']) {
4887 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4888 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4891 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4892 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4895 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4896 // only handles DFRN deletes
4897 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4898 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4899 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4905 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4906 intval($item['id']),
4907 dbesc($signed_text),