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 $arr['thr-parent'] = $arr['parent-uri'];
1243 if($arr['parent-uri'] === $arr['uri']) {
1245 $parent_deleted = 0;
1246 $allow_cid = $arr['allow_cid'];
1247 $allow_gid = $arr['allow_gid'];
1248 $deny_cid = $arr['deny_cid'];
1249 $deny_gid = $arr['deny_gid'];
1250 $notify_type = 'wall-new';
1254 // find the parent and snarf the item id and ACLs
1255 // and anything else we need to inherit
1257 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1258 dbesc($arr['parent-uri']),
1264 // is the new message multi-level threaded?
1265 // even though we don't support it now, preserve the info
1266 // and re-attach to the conversation parent.
1268 if($r[0]['uri'] != $r[0]['parent-uri']) {
1269 $arr['parent-uri'] = $r[0]['parent-uri'];
1270 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1271 ORDER BY `id` ASC LIMIT 1",
1272 dbesc($r[0]['parent-uri']),
1273 dbesc($r[0]['parent-uri']),
1280 $parent_id = $r[0]['id'];
1281 $parent_deleted = $r[0]['deleted'];
1282 $allow_cid = $r[0]['allow_cid'];
1283 $allow_gid = $r[0]['allow_gid'];
1284 $deny_cid = $r[0]['deny_cid'];
1285 $deny_gid = $r[0]['deny_gid'];
1286 $arr['wall'] = $r[0]['wall'];
1287 $notify_type = 'comment-new';
1289 // if the parent is private, force privacy for the entire conversation
1290 // This differs from the above settings as it subtly allows comments from
1291 // email correspondents to be private even if the overall thread is not.
1293 if($r[0]['private'])
1294 $arr['private'] = $r[0]['private'];
1296 // Edge case. We host a public forum that was originally posted to privately.
1297 // The original author commented, but as this is a comment, the permissions
1298 // weren't fixed up so it will still show the comment as private unless we fix it here.
1300 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1301 $arr['private'] = 0;
1304 // If its a post from myself then tag the thread as "mention"
1305 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1306 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1309 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1310 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1311 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1312 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1313 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1319 // Allow one to see reply tweets from status.net even when
1320 // we don't have or can't see the original post.
1323 logger('item_store: $force_parent=true, reply converted to top-level post.');
1325 $arr['parent-uri'] = $arr['uri'];
1326 $arr['gravity'] = 0;
1329 logger('item_store: item parent was not found - ignoring item');
1333 $parent_deleted = 0;
1337 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1341 if($r && count($r)) {
1342 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1346 call_hooks('post_remote',$arr);
1348 if(x($arr,'cancel')) {
1349 logger('item_store: post cancelled by plugin.');
1353 // Store the unescaped version
1358 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1360 $r = dbq("INSERT INTO `item` (`"
1361 . implode("`, `", array_keys($arr))
1363 . implode("', '", array_values($arr))
1369 // find the item we just created
1370 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1376 $current_post = $r[0]['id'];
1377 logger('item_store: created item ' . $current_post);
1380 // Is it a global copy?
1381 $store_gcontact = ($arr["uid"] == 0);
1383 // Is it a comment on a global copy?
1384 if (!$store_gcontact AND ($arr["uri"] != $arr["parent-uri"])) {
1385 $q = q("SELECT `id` FROM `item` WHERE `uri`='%s' AND `uid` = 0",
1386 $arr["parent-uri"]);
1387 $store_gcontact = count($q);
1390 // This check for private and network is maybe superflous
1391 if ($store_gcontact AND !$arr['private'] AND in_array($arr["network"],
1392 array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS, ""))) {
1394 // "3" means: We don't know this contact directly (Maybe a reshared item)
1398 // Is it a user from our server?
1399 $q = q("SELECT `id` FROM `contact` WHERE `self` AND `nurl` = '%s' LIMIT 1",
1400 dbesc(normalise_link($arr["author-link"])));
1403 $network = NETWORK_DFRN;
1404 } else { // Is it a contact from a user on our server?
1405 $q = q("SELECT `network` FROM `contact` WHERE `uid` != 0 AND `network` != ''
1406 AND (`nurl` = '%s' OR `alias` IN ('%s', '%s')) LIMIT 1",
1407 dbesc(normalise_link($arr["author-link"])),
1408 dbesc(normalise_link($arr["author-link"])),
1409 dbesc($arr["author-link"]));
1412 $network = $q[0]["network"];
1416 poco_check($arr["author-link"], $arr["author-name"], $network, $arr["author-avatar"], "", "", "", "", "", $arr["received"], $generation, $arr["contact-id"], $arr["uid"]);
1418 // Maybe its a body with a shared item? Then extract a global contact from it.
1419 poco_contact_from_body($arr["body"], $arr["received"], $arr["contact-id"], $arr["uid"]);
1422 // Set "success_update" to the date of the last time we heard from this contact
1423 // This can be used to filter for inactive contacts and poco.
1424 // Only do this for public postings to avoid privacy problems, since poco data is public.
1425 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1426 if (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])))
1427 q("UPDATE `contact` SET `success_update` = '%s' WHERE `id` = %d",
1428 dbesc($arr['received']),
1429 intval($arr['contact-id'])
1432 logger('item_store: could not locate created item');
1436 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1437 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1439 intval($arr['uid']),
1440 intval($current_post)
1444 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1445 $parent_id = $current_post;
1447 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1450 $private = $arr['private'];
1452 // Set parent id - and also make sure to inherit the parent's ACLs.
1454 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1455 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1462 intval($parent_deleted),
1463 intval($current_post)
1466 // Complete ostatus threads
1467 if ($ostatus_conversation)
1468 complete_conversation($current_post, $ostatus_conversation);
1470 $arr['id'] = $current_post;
1471 $arr['parent'] = $parent_id;
1472 $arr['allow_cid'] = $allow_cid;
1473 $arr['allow_gid'] = $allow_gid;
1474 $arr['deny_cid'] = $deny_cid;
1475 $arr['deny_gid'] = $deny_gid;
1476 $arr['private'] = $private;
1477 $arr['deleted'] = $parent_deleted;
1479 // update the commented timestamp on the parent
1481 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1482 dbesc(datetime_convert()),
1483 dbesc(datetime_convert()),
1488 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1489 intval($current_post),
1490 dbesc($dsprsig->signed_text),
1491 dbesc($dsprsig->signature),
1492 dbesc($dsprsig->signer)
1498 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1501 if($arr['last-child']) {
1502 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1504 intval($arr['uid']),
1505 intval($current_post)
1509 $deleted = tag_deliver($arr['uid'],$current_post);
1511 // current post can be deleted if is for a community page and no mention are
1513 if (!$deleted AND !$dontcache) {
1515 // Store the fresh generated item into the cache
1516 put_item_in_cache($arr);
1518 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1519 if (count($r) == 1) {
1520 call_hooks('post_remote_end', $r[0]);
1522 logger('item_store: new item not found in DB, id ' . $current_post);
1525 // Add every contact of the post to the global contact table
1528 create_tags_from_item($current_post);
1529 create_files_from_item($current_post);
1531 // Only check for notifications on start posts
1532 if ($arr['parent-uri'] === $arr['uri']) {
1533 add_thread($current_post);
1534 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1536 // Send a notification for every new post?
1537 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1538 intval($arr['contact-id']),
1541 $send_notification = count($r);
1543 if (!$send_notification) {
1544 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1545 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1548 foreach ($tags AS $tag) {
1549 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1550 normalise_link($tag["url"]), intval($arr['uid']));
1552 $send_notification = true;
1557 if ($send_notification) {
1558 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1559 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1560 intval($arr['uid']));
1562 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1563 intval($current_post),
1569 require_once('include/enotify.php');
1571 'type' => NOTIFY_SHARE,
1572 'notify_flags' => $u[0]['notify-flags'],
1573 'language' => $u[0]['language'],
1574 'to_name' => $u[0]['username'],
1575 'to_email' => $u[0]['email'],
1576 'uid' => $u[0]['uid'],
1578 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1579 'source_name' => $item[0]['author-name'],
1580 'source_link' => $item[0]['author-link'],
1581 'source_photo' => $item[0]['author-avatar'],
1582 'verb' => ACTIVITY_TAG,
1585 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1588 update_thread($parent_id);
1591 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1593 return $current_post;
1596 function get_item_guid($id) {
1597 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1599 return($r[0]["guid"]);
1604 function get_item_id($guid, $uid = 0) {
1610 $uid == local_user();
1612 // Does the given user have this item?
1614 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1615 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1616 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1619 $nick = $r[0]["nickname"];
1623 // Or is it anywhere on the server?
1625 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1626 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1627 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1628 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1629 AND `item`.`private` = 0 AND `item`.`wall` = 1
1630 AND `item`.`guid` = '%s'", dbesc($guid));
1633 $nick = $r[0]["nickname"];
1636 return(array("nick" => $nick, "id" => $id));
1640 function get_item_contact($item,$contacts) {
1641 if(! count($contacts) || (! is_array($item)))
1643 foreach($contacts as $contact) {
1644 if($contact['id'] == $item['contact-id']) {
1646 break; // NOTREACHED
1653 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1655 * @param int $item_id
1656 * @return bool true if item was deleted, else false
1658 function tag_deliver($uid,$item_id) {
1666 $u = q("select * from user where uid = %d limit 1",
1672 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1673 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1676 $i = q("select * from item where id = %d and uid = %d limit 1",
1685 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1687 // Diaspora uses their own hardwired link URL in @-tags
1688 // instead of the one we supply with webfinger
1690 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1692 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1694 foreach($matches as $mtch) {
1695 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1697 logger('tag_deliver: mention found: ' . $mtch[2]);
1703 if ( ($community_page || $prvgroup) &&
1704 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1705 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1707 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1708 q("DELETE FROM item WHERE id = %d and uid = %d",
1718 // send a notification
1720 // use a local photo if we have one
1722 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1723 intval($u[0]['uid']),
1724 dbesc(normalise_link($item['author-link']))
1726 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1729 require_once('include/enotify.php');
1731 'type' => NOTIFY_TAGSELF,
1732 'notify_flags' => $u[0]['notify-flags'],
1733 'language' => $u[0]['language'],
1734 'to_name' => $u[0]['username'],
1735 'to_email' => $u[0]['email'],
1736 'uid' => $u[0]['uid'],
1738 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1739 'source_name' => $item['author-name'],
1740 'source_link' => $item['author-link'],
1741 'source_photo' => $photo,
1742 'verb' => ACTIVITY_TAG,
1744 'parent' => $item['parent']
1748 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1750 call_hooks('tagged', $arr);
1752 if((! $community_page) && (! $prvgroup))
1756 // tgroup delivery - setup a second delivery chain
1757 // prevent delivery looping - only proceed
1758 // if the message originated elsewhere and is a top-level post
1760 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1763 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1766 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1767 intval($u[0]['uid'])
1772 // also reset all the privacy bits to the forum default permissions
1774 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1776 $forum_mode = (($prvgroup) ? 2 : 1);
1778 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1779 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1780 intval($forum_mode),
1781 dbesc($c[0]['name']),
1782 dbesc($c[0]['url']),
1783 dbesc($c[0]['thumb']),
1785 dbesc($u[0]['allow_cid']),
1786 dbesc($u[0]['allow_gid']),
1787 dbesc($u[0]['deny_cid']),
1788 dbesc($u[0]['deny_gid']),
1791 update_thread($item_id);
1793 proc_run('php','include/notifier.php','tgroup',$item_id);
1799 function tgroup_check($uid,$item) {
1805 // check that the message originated elsewhere and is a top-level post
1807 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1811 $u = q("select * from user where uid = %d limit 1",
1817 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1818 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1821 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1823 // Diaspora uses their own hardwired link URL in @-tags
1824 // instead of the one we supply with webfinger
1826 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1828 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1830 foreach($matches as $mtch) {
1831 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1833 logger('tgroup_check: mention found: ' . $mtch[2]);
1841 if((! $community_page) && (! $prvgroup))
1855 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1859 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1861 if($contact['duplex'] && $contact['dfrn-id'])
1862 $idtosend = '0:' . $orig_id;
1863 if($contact['duplex'] && $contact['issued-id'])
1864 $idtosend = '1:' . $orig_id;
1866 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1868 $rino_enable = get_config('system','rino_encrypt');
1873 $ssl_val = intval(get_config('system','ssl_policy'));
1877 case SSL_POLICY_FULL:
1878 $ssl_policy = 'full';
1880 case SSL_POLICY_SELFSIGN:
1881 $ssl_policy = 'self';
1883 case SSL_POLICY_NONE:
1885 $ssl_policy = 'none';
1889 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1891 logger('dfrn_deliver: ' . $url);
1893 $xml = fetch_url($url);
1895 $curl_stat = $a->get_curl_code();
1897 return(-1); // timed out
1899 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1904 if(strpos($xml,'<?xml') === false) {
1905 logger('dfrn_deliver: no valid XML returned');
1906 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1910 $res = parse_xml_string($xml);
1912 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1913 return (($res->status) ? $res->status : 3);
1915 $postvars = array();
1916 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1917 $challenge = hex2bin((string) $res->challenge);
1918 $perm = (($res->perm) ? $res->perm : null);
1919 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1920 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1921 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1923 if($owner['page-flags'] == PAGE_PRVGROUP)
1926 $final_dfrn_id = '';
1929 if((($perm == 'rw') && (! intval($contact['writable'])))
1930 || (($perm == 'r') && (intval($contact['writable'])))) {
1931 q("update contact set writable = %d where id = %d",
1932 intval(($perm == 'rw') ? 1 : 0),
1933 intval($contact['id'])
1935 $contact['writable'] = (string) 1 - intval($contact['writable']);
1939 if(($contact['duplex'] && strlen($contact['pubkey']))
1940 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1941 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1942 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1943 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1946 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1947 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1950 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1952 if(strpos($final_dfrn_id,':') == 1)
1953 $final_dfrn_id = substr($final_dfrn_id,2);
1955 if($final_dfrn_id != $orig_id) {
1956 logger('dfrn_deliver: wrong dfrn_id.');
1957 // did not decode properly - cannot trust this site
1961 $postvars['dfrn_id'] = $idtosend;
1962 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1964 $postvars['dissolve'] = '1';
1967 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1968 $postvars['data'] = $atom;
1969 $postvars['perm'] = 'rw';
1972 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1973 $postvars['perm'] = 'r';
1976 $postvars['ssl_policy'] = $ssl_policy;
1979 $postvars['page'] = $page;
1981 if($rino && $rino_allowed && (! $dissolve)) {
1982 $key = substr(random_string(),0,16);
1983 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1984 $postvars['data'] = $data;
1985 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1988 if($dfrn_version >= 2.1) {
1989 if(($contact['duplex'] && strlen($contact['pubkey']))
1990 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1991 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1993 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1996 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2000 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2001 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2004 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2008 logger('md5 rawkey ' . md5($postvars['key']));
2010 $postvars['key'] = bin2hex($postvars['key']);
2013 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2015 $xml = post_url($contact['notify'],$postvars);
2017 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2019 $curl_stat = $a->get_curl_code();
2020 if((! $curl_stat) || (! strlen($xml)))
2021 return(-1); // timed out
2023 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2026 if(strpos($xml,'<?xml') === false) {
2027 logger('dfrn_deliver: phase 2: no valid XML returned');
2028 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2032 if($contact['term-date'] != '0000-00-00 00:00:00') {
2033 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2034 require_once('include/Contact.php');
2035 unmark_for_death($contact);
2038 $res = parse_xml_string($xml);
2040 return $res->status;
2045 This function returns true if $update has an edited timestamp newer
2046 than $existing, i.e. $update contains new data which should override
2047 what's already there. If there is no timestamp yet, the update is
2048 assumed to be newer. If the update has no timestamp, the existing
2049 item is assumed to be up-to-date. If the timestamps are equal it
2050 assumes the update has been seen before and should be ignored.
2052 function edited_timestamp_is_newer($existing, $update) {
2053 if (!x($existing,'edited') || !$existing['edited']) {
2056 if (!x($update,'edited') || !$update['edited']) {
2059 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2060 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2061 return (strcmp($existing_edited, $update_edited) < 0);
2066 * consume_feed - process atom feed and update anything/everything we might need to update
2068 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2070 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2071 * It is this person's stuff that is going to be updated.
2072 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2073 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2074 * have a contact record.
2075 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2076 * might not) try and subscribe to it.
2077 * $datedir sorts in reverse order
2078 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2079 * imported prior to its children being seen in the stream unless we are certain
2080 * of how the feed is arranged/ordered.
2081 * With $pass = 1, we only pull parent items out of the stream.
2082 * With $pass = 2, we only pull children (comments/likes).
2084 * So running this twice, first with pass 1 and then with pass 2 will do the right
2085 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2086 * model where comments can have sub-threads. That would require some massive sorting
2087 * to get all the feed items into a mostly linear ordering, and might still require
2091 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2093 require_once('library/simplepie/simplepie.inc');
2094 require_once('include/contact_selectors.php');
2096 if(! strlen($xml)) {
2097 logger('consume_feed: empty input');
2101 $feed = new SimplePie();
2102 $feed->set_raw_data($xml);
2104 $feed->enable_order_by_date(true);
2106 $feed->enable_order_by_date(false);
2110 logger('consume_feed: Error parsing XML: ' . $feed->error());
2112 $permalink = $feed->get_permalink();
2114 // Check at the feed level for updated contact name and/or photo
2118 $photo_timestamp = '';
2121 $contact_updated = '';
2123 $hubs = $feed->get_links('hub');
2124 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2127 $hub = implode(',', $hubs);
2129 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2131 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2133 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2134 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2135 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2136 $new_name = $elems['name'][0]['data'];
2138 // Manually checking for changed contact names
2139 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2140 $name_updated = date("c");
2141 $photo_timestamp = date("c");
2144 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2145 if ($photo_timestamp == "")
2146 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2147 $photo_url = $elems['link'][0]['attribs']['']['href'];
2150 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2151 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2155 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2156 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2158 $contact_updated = $photo_timestamp;
2160 require_once("include/Photo.php");
2161 $photo_failure = false;
2162 $have_photo = false;
2164 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2165 intval($contact['id']),
2166 intval($contact['uid'])
2169 $resource_id = $r[0]['resource-id'];
2173 $resource_id = photo_new_resource();
2176 $img_str = fetch_url($photo_url,true);
2177 // guess mimetype from headers or filename
2178 $type = guess_image_type($photo_url,true);
2181 $img = new Photo($img_str, $type);
2182 if($img->is_valid()) {
2184 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2185 dbesc($resource_id),
2186 intval($contact['id']),
2187 intval($contact['uid'])
2191 $img->scaleImageSquare(175);
2193 $hash = $resource_id;
2194 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2196 $img->scaleImage(80);
2197 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2199 $img->scaleImage(48);
2200 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2204 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2205 WHERE `uid` = %d AND `id` = %d",
2206 dbesc(datetime_convert()),
2207 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2208 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2209 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2210 intval($contact['uid']),
2211 intval($contact['id'])
2216 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2217 if ($name_updated > $contact_updated)
2218 $contact_updated = $name_updated;
2220 $r = q("select * from contact where uid = %d and id = %d limit 1",
2221 intval($contact['uid']),
2222 intval($contact['id'])
2225 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2226 dbesc(notags(trim($new_name))),
2227 dbesc(datetime_convert()),
2228 intval($contact['uid']),
2229 intval($contact['id'])
2232 // do our best to update the name on content items
2235 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2236 dbesc(notags(trim($new_name))),
2237 dbesc($r[0]['name']),
2238 dbesc($r[0]['url']),
2239 intval($contact['uid'])
2244 if ($contact_updated AND $new_name AND $photo_url)
2245 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2247 if(strlen($birthday)) {
2248 if(substr($birthday,0,4) != $contact['bdyear']) {
2249 logger('consume_feed: updating birthday: ' . $birthday);
2253 * Add new birthday event for this person
2255 * $bdtext is just a readable placeholder in case the event is shared
2256 * with others. We will replace it during presentation to our $importer
2257 * to contain a sparkle link and perhaps a photo.
2261 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2262 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2265 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2266 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2267 intval($contact['uid']),
2268 intval($contact['id']),
2269 dbesc(datetime_convert()),
2270 dbesc(datetime_convert()),
2271 dbesc(datetime_convert('UTC','UTC', $birthday)),
2272 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2281 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2282 dbesc(substr($birthday,0,4)),
2283 intval($contact['uid']),
2284 intval($contact['id'])
2287 // This function is called twice without reloading the contact
2288 // Make sure we only create one event. This is why &$contact
2289 // is a reference var in this function
2291 $contact['bdyear'] = substr($birthday,0,4);
2295 $community_page = 0;
2296 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2298 $community_page = intval($rawtags[0]['data']);
2300 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2301 q("update contact set forum = %d where id = %d",
2302 intval($community_page),
2303 intval($contact['id'])
2305 $contact['forum'] = (string) $community_page;
2309 // process any deleted entries
2311 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2312 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2313 foreach($del_entries as $dentry) {
2315 if(isset($dentry['attribs']['']['ref'])) {
2316 $uri = $dentry['attribs']['']['ref'];
2318 if(isset($dentry['attribs']['']['when'])) {
2319 $when = $dentry['attribs']['']['when'];
2320 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2323 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2325 if($deleted && is_array($contact)) {
2326 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2327 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2329 intval($importer['uid']),
2330 intval($contact['id'])
2335 if(! $item['deleted'])
2336 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2338 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2339 $xo = parse_xml_string($item['object'],false);
2340 $xt = parse_xml_string($item['target'],false);
2341 if($xt->type === ACTIVITY_OBJ_NOTE) {
2342 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2344 intval($importer['importer_uid'])
2348 // For tags, the owner cannot remove the tag on the author's copy of the post.
2350 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2351 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2352 $author_copy = (($item['origin']) ? true : false);
2354 if($owner_remove && $author_copy)
2356 if($author_remove || $owner_remove) {
2357 $tags = explode(',',$i[0]['tag']);
2360 foreach($tags as $tag)
2361 if(trim($tag) !== trim($xo->body))
2362 $newtags[] = trim($tag);
2364 q("update item set tag = '%s' where id = %d",
2365 dbesc(implode(',',$newtags)),
2368 create_tags_from_item($i[0]['id']);
2374 if($item['uri'] == $item['parent-uri']) {
2375 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2376 `body` = '', `title` = ''
2377 WHERE `parent-uri` = '%s' AND `uid` = %d",
2379 dbesc(datetime_convert()),
2380 dbesc($item['uri']),
2381 intval($importer['uid'])
2383 create_tags_from_itemuri($item['uri'], $importer['uid']);
2384 create_files_from_itemuri($item['uri'], $importer['uid']);
2385 update_thread_uri($item['uri'], $importer['uid']);
2388 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2389 `body` = '', `title` = ''
2390 WHERE `uri` = '%s' AND `uid` = %d",
2392 dbesc(datetime_convert()),
2394 intval($importer['uid'])
2396 create_tags_from_itemuri($uri, $importer['uid']);
2397 create_files_from_itemuri($uri, $importer['uid']);
2398 if($item['last-child']) {
2399 // ensure that last-child is set in case the comment that had it just got wiped.
2400 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2401 dbesc(datetime_convert()),
2402 dbesc($item['parent-uri']),
2403 intval($item['uid'])
2405 // who is the last child now?
2406 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2407 ORDER BY `created` DESC LIMIT 1",
2408 dbesc($item['parent-uri']),
2409 intval($importer['uid'])
2412 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2423 // Now process the feed
2425 if($feed->get_item_quantity()) {
2427 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2429 // in inverse date order
2431 $items = array_reverse($feed->get_items());
2433 $items = $feed->get_items();
2436 foreach($items as $item) {
2439 $item_id = $item->get_id();
2440 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2441 if(isset($rawthread[0]['attribs']['']['ref'])) {
2443 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2446 if(($is_reply) && is_array($contact)) {
2451 // not allowed to post
2453 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2457 // Have we seen it? If not, import it.
2459 $item_id = $item->get_id();
2460 $datarray = get_atom_elements($feed, $item, $contact);
2462 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2463 $datarray['author-name'] = $contact['name'];
2464 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2465 $datarray['author-link'] = $contact['url'];
2466 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2467 $datarray['author-avatar'] = $contact['thumb'];
2469 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2470 logger('consume_feed: no author information! ' . print_r($datarray,true));
2474 $force_parent = false;
2475 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2476 if($contact['network'] === NETWORK_OSTATUS)
2477 $force_parent = true;
2478 if(strlen($datarray['title']))
2479 unset($datarray['title']);
2480 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2481 dbesc(datetime_convert()),
2483 intval($importer['uid'])
2485 $datarray['last-child'] = 1;
2486 update_thread_uri($parent_uri, $importer['uid']);
2490 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2492 intval($importer['uid'])
2495 // Update content if 'updated' changes
2498 if (edited_timestamp_is_newer($r[0], $datarray)) {
2500 // do not accept (ignore) an earlier edit than one we currently have.
2501 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2504 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2505 dbesc($datarray['title']),
2506 dbesc($datarray['body']),
2507 dbesc($datarray['tag']),
2508 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2509 dbesc(datetime_convert()),
2511 intval($importer['uid'])
2513 create_tags_from_itemuri($item_id, $importer['uid']);
2514 update_thread_uri($item_id, $importer['uid']);
2517 // update last-child if it changes
2519 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2520 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2521 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2522 dbesc(datetime_convert()),
2524 intval($importer['uid'])
2526 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2527 intval($allow[0]['data']),
2528 dbesc(datetime_convert()),
2530 intval($importer['uid'])
2532 update_thread_uri($item_id, $importer['uid']);
2538 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2539 // one way feed - no remote comment ability
2540 $datarray['last-child'] = 0;
2542 $datarray['parent-uri'] = $parent_uri;
2543 $datarray['uid'] = $importer['uid'];
2544 $datarray['contact-id'] = $contact['id'];
2545 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2546 $datarray['type'] = 'activity';
2547 $datarray['gravity'] = GRAVITY_LIKE;
2548 // only one like or dislike per person
2549 // splitted into two queries for performance issues
2550 $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",
2551 intval($datarray['uid']),
2552 intval($datarray['contact-id']),
2553 dbesc($datarray['verb']),
2559 $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",
2560 intval($datarray['uid']),
2561 intval($datarray['contact-id']),
2562 dbesc($datarray['verb']),
2569 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2570 $xo = parse_xml_string($datarray['object'],false);
2571 $xt = parse_xml_string($datarray['target'],false);
2573 if($xt->type == ACTIVITY_OBJ_NOTE) {
2574 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2576 intval($importer['importer_uid'])
2581 // extract tag, if not duplicate, add to parent item
2582 if($xo->id && $xo->content) {
2583 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2584 if(! (stristr($r[0]['tag'],$newtag))) {
2585 q("UPDATE item SET tag = '%s' WHERE id = %d",
2586 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2589 create_tags_from_item($r[0]['id']);
2595 $r = item_store($datarray,$force_parent);
2601 // Head post of a conversation. Have we seen it? If not, import it.
2603 $item_id = $item->get_id();
2605 $datarray = get_atom_elements($feed, $item, $contact);
2607 if(is_array($contact)) {
2608 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2609 $datarray['author-name'] = $contact['name'];
2610 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2611 $datarray['author-link'] = $contact['url'];
2612 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2613 $datarray['author-avatar'] = $contact['thumb'];
2616 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2617 logger('consume_feed: no author information! ' . print_r($datarray,true));
2621 // special handling for events
2623 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2624 $ev = bbtoevent($datarray['body']);
2625 if(x($ev,'desc') && x($ev,'start')) {
2626 $ev['uid'] = $importer['uid'];
2627 $ev['uri'] = $item_id;
2628 $ev['edited'] = $datarray['edited'];
2629 $ev['private'] = $datarray['private'];
2631 if(is_array($contact))
2632 $ev['cid'] = $contact['id'];
2633 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2635 intval($importer['uid'])
2638 $ev['id'] = $r[0]['id'];
2639 $xyz = event_store($ev);
2644 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2645 if(strlen($datarray['title']))
2646 unset($datarray['title']);
2647 $datarray['last-child'] = 1;
2651 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2653 intval($importer['uid'])
2656 // Update content if 'updated' changes
2659 if (edited_timestamp_is_newer($r[0], $datarray)) {
2661 // do not accept (ignore) an earlier edit than one we currently have.
2662 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2665 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2666 dbesc($datarray['title']),
2667 dbesc($datarray['body']),
2668 dbesc($datarray['tag']),
2669 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2670 dbesc(datetime_convert()),
2672 intval($importer['uid'])
2674 create_tags_from_itemuri($item_id, $importer['uid']);
2675 update_thread_uri($item_id, $importer['uid']);
2678 // update last-child if it changes
2680 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2681 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2682 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2683 intval($allow[0]['data']),
2684 dbesc(datetime_convert()),
2686 intval($importer['uid'])
2688 update_thread_uri($item_id, $importer['uid']);
2693 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2694 logger('consume-feed: New follower');
2695 new_follower($importer,$contact,$datarray,$item);
2698 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2699 lose_follower($importer,$contact,$datarray,$item);
2703 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2704 logger('consume-feed: New friend request');
2705 new_follower($importer,$contact,$datarray,$item,true);
2708 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2709 lose_sharer($importer,$contact,$datarray,$item);
2714 if(! is_array($contact))
2718 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2719 // one way feed - no remote comment ability
2720 $datarray['last-child'] = 0;
2722 if($contact['network'] === NETWORK_FEED)
2723 $datarray['private'] = 2;
2725 $datarray['parent-uri'] = $item_id;
2726 $datarray['uid'] = $importer['uid'];
2727 $datarray['contact-id'] = $contact['id'];
2729 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2730 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2731 // but otherwise there's a possible data mixup on the sender's system.
2732 // the tgroup delivery code called from item_store will correct it if it's a forum,
2733 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2734 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2735 $datarray['owner-name'] = $contact['name'];
2736 $datarray['owner-link'] = $contact['url'];
2737 $datarray['owner-avatar'] = $contact['thumb'];
2740 // We've allowed "followers" to reach this point so we can decide if they are
2741 // posting an @-tag delivery, which followers are allowed to do for certain
2742 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2744 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2747 // This is my contact on another system, but it's really me.
2748 // Turn this into a wall post.
2749 $notify = item_is_remote_self($contact, $datarray);
2751 $r = item_store($datarray, false, $notify);
2752 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2760 function item_is_remote_self($contact, &$datarray) {
2763 if (!$contact['remote_self'])
2766 // Prevent the forwarding of posts that are forwarded
2767 if ($datarray["extid"] == NETWORK_DFRN)
2770 // Prevent to forward already forwarded posts
2771 if ($datarray["app"] == $a->get_hostname())
2774 // Only forward posts
2775 if ($datarray["verb"] != ACTIVITY_POST)
2778 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2781 $datarray2 = $datarray;
2782 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2783 if ($contact['remote_self'] == 2) {
2784 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2785 intval($contact['uid']));
2787 $datarray['contact-id'] = $r[0]["id"];
2789 $datarray['owner-name'] = $r[0]["name"];
2790 $datarray['owner-link'] = $r[0]["url"];
2791 $datarray['owner-avatar'] = $r[0]["thumb"];
2793 $datarray['author-name'] = $datarray['owner-name'];
2794 $datarray['author-link'] = $datarray['owner-link'];
2795 $datarray['author-avatar'] = $datarray['owner-avatar'];
2798 if ($contact['network'] != NETWORK_FEED) {
2799 $datarray["guid"] = get_guid(32);
2800 unset($datarray["plink"]);
2801 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2802 $datarray["parent-uri"] = $datarray["uri"];
2803 $datarray["extid"] = $contact['network'];
2804 $urlpart = parse_url($datarray2['author-link']);
2805 $datarray["app"] = $urlpart["host"];
2807 $datarray['private'] = 0;
2810 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2811 // $datarray["app"] = network_to_name($contact['network']);
2813 if ($contact['network'] != NETWORK_FEED) {
2814 // Store the original post
2815 $r = item_store($datarray2, false, false);
2816 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2818 $datarray["app"] = "Feed";
2823 function local_delivery($importer,$data) {
2826 logger(__function__, LOGGER_TRACE);
2828 if($importer['readonly']) {
2829 // We aren't receiving stuff from this person. But we will quietly ignore them
2830 // rather than a blatant "go away" message.
2831 logger('local_delivery: ignoring');
2836 // Consume notification feed. This may differ from consuming a public feed in several ways
2837 // - might contain email or friend suggestions
2838 // - might contain remote followup to our message
2839 // - in which case we need to accept it and then notify other conversants
2840 // - we may need to send various email notifications
2842 $feed = new SimplePie();
2843 $feed->set_raw_data($data);
2844 $feed->enable_order_by_date(false);
2849 logger('local_delivery: Error parsing XML: ' . $feed->error());
2852 // Check at the feed level for updated contact name and/or photo
2856 $photo_timestamp = '';
2858 $contact_updated = '';
2861 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2863 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2865 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2868 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2869 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2870 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2871 $new_name = $elems['name'][0]['data'];
2873 // Manually checking for changed contact names
2874 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2875 $name_updated = date("c");
2876 $photo_timestamp = date("c");
2879 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2880 if ($photo_timestamp == "")
2881 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2882 $photo_url = $elems['link'][0]['attribs']['']['href'];
2886 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2888 $contact_updated = $photo_timestamp;
2890 logger('local_delivery: Updating photo for ' . $importer['name']);
2891 require_once("include/Photo.php");
2892 $photo_failure = false;
2893 $have_photo = false;
2895 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2896 intval($importer['id']),
2897 intval($importer['importer_uid'])
2900 $resource_id = $r[0]['resource-id'];
2904 $resource_id = photo_new_resource();
2907 $img_str = fetch_url($photo_url,true);
2908 // guess mimetype from headers or filename
2909 $type = guess_image_type($photo_url,true);
2912 $img = new Photo($img_str, $type);
2913 if($img->is_valid()) {
2915 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2916 dbesc($resource_id),
2917 intval($importer['id']),
2918 intval($importer['importer_uid'])
2922 $img->scaleImageSquare(175);
2924 $hash = $resource_id;
2925 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2927 $img->scaleImage(80);
2928 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2930 $img->scaleImage(48);
2931 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2935 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2936 WHERE `uid` = %d AND `id` = %d",
2937 dbesc(datetime_convert()),
2938 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2939 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2940 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2941 intval($importer['importer_uid']),
2942 intval($importer['id'])
2947 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2948 if ($name_updated > $contact_updated)
2949 $contact_updated = $name_updated;
2951 $r = q("select * from contact where uid = %d and id = %d limit 1",
2952 intval($importer['importer_uid']),
2953 intval($importer['id'])
2956 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2957 dbesc(notags(trim($new_name))),
2958 dbesc(datetime_convert()),
2959 intval($importer['importer_uid']),
2960 intval($importer['id'])
2963 // do our best to update the name on content items
2966 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2967 dbesc(notags(trim($new_name))),
2968 dbesc($r[0]['name']),
2969 dbesc($r[0]['url']),
2970 intval($importer['importer_uid'])
2975 if ($contact_updated AND $new_name AND $photo_url)
2976 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
2978 // Currently unsupported - needs a lot of work
2979 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2980 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2981 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2983 $newloc['uid'] = $importer['importer_uid'];
2984 $newloc['cid'] = $importer['id'];
2985 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2986 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2987 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2988 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2989 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2990 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2991 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2992 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2993 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2994 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2995 /** relocated user must have original key pair */
2996 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2997 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
2999 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3002 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3003 intval($importer['id']),
3004 intval($importer['importer_uid']));
3009 $x = q("UPDATE contact SET
3020 `site-pubkey` = '%s'
3021 WHERE id=%d AND uid=%d;",
3022 dbesc($newloc['name']),
3023 dbesc($newloc['photo']),
3024 dbesc($newloc['thumb']),
3025 dbesc($newloc['micro']),
3026 dbesc($newloc['url']),
3027 dbesc(normalise_link($newloc['url'])),
3028 dbesc($newloc['request']),
3029 dbesc($newloc['confirm']),
3030 dbesc($newloc['notify']),
3031 dbesc($newloc['poll']),
3032 dbesc($newloc['sitepubkey']),
3033 intval($importer['id']),
3034 intval($importer['importer_uid']));
3040 'owner-link' => array($old['url'], $newloc['url']),
3041 'author-link' => array($old['url'], $newloc['url']),
3042 'owner-avatar' => array($old['photo'], $newloc['photo']),
3043 'author-avatar' => array($old['photo'], $newloc['photo']),
3045 foreach ($fields as $n=>$f){
3046 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3049 intval($importer['importer_uid']));
3055 // merge with current record, current contents have priority
3056 // update record, set url-updated
3057 // update profile photos
3063 // handle friend suggestion notification
3065 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3066 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3067 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3069 $fsugg['uid'] = $importer['importer_uid'];
3070 $fsugg['cid'] = $importer['id'];
3071 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3072 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3073 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3074 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3075 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3077 // Does our member already have a friend matching this description?
3079 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3080 dbesc($fsugg['name']),
3081 dbesc(normalise_link($fsugg['url'])),
3082 intval($fsugg['uid'])
3087 // Do we already have an fcontact record for this person?
3090 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3091 dbesc($fsugg['url']),
3092 dbesc($fsugg['name']),
3093 dbesc($fsugg['request'])
3098 // OK, we do. Do we already have an introduction for this person ?
3099 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3100 intval($fsugg['uid']),
3107 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3108 dbesc($fsugg['name']),
3109 dbesc($fsugg['url']),
3110 dbesc($fsugg['photo']),
3111 dbesc($fsugg['request'])
3113 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3114 dbesc($fsugg['url']),
3115 dbesc($fsugg['name']),
3116 dbesc($fsugg['request'])
3121 // database record did not get created. Quietly give up.
3126 $hash = random_string();
3128 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3129 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3130 intval($fsugg['uid']),
3132 intval($fsugg['cid']),
3133 dbesc($fsugg['body']),
3135 dbesc(datetime_convert()),
3140 'type' => NOTIFY_SUGGEST,
3141 'notify_flags' => $importer['notify-flags'],
3142 'language' => $importer['language'],
3143 'to_name' => $importer['username'],
3144 'to_email' => $importer['email'],
3145 'uid' => $importer['importer_uid'],
3147 'link' => $a->get_baseurl() . '/notifications/intros',
3148 'source_name' => $importer['name'],
3149 'source_link' => $importer['url'],
3150 'source_photo' => $importer['photo'],
3151 'verb' => ACTIVITY_REQ_FRIEND,
3160 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3161 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3163 logger('local_delivery: private message received');
3166 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3169 $msg['uid'] = $importer['importer_uid'];
3170 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3171 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3172 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3173 $msg['contact-id'] = $importer['id'];
3174 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3175 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3177 $msg['replied'] = 0;
3178 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3179 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3180 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3184 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3185 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3187 // send notifications.
3189 require_once('include/enotify.php');
3191 $notif_params = array(
3192 'type' => NOTIFY_MAIL,
3193 'notify_flags' => $importer['notify-flags'],
3194 'language' => $importer['language'],
3195 'to_name' => $importer['username'],
3196 'to_email' => $importer['email'],
3197 'uid' => $importer['importer_uid'],
3199 'source_name' => $msg['from-name'],
3200 'source_link' => $importer['url'],
3201 'source_photo' => $importer['thumb'],
3202 'verb' => ACTIVITY_POST,
3206 notification($notif_params);
3212 $community_page = 0;
3213 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3215 $community_page = intval($rawtags[0]['data']);
3217 if(intval($importer['forum']) != $community_page) {
3218 q("update contact set forum = %d where id = %d",
3219 intval($community_page),
3220 intval($importer['id'])
3222 $importer['forum'] = (string) $community_page;
3225 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3227 // process any deleted entries
3229 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3230 if(is_array($del_entries) && count($del_entries)) {
3231 foreach($del_entries as $dentry) {
3233 if(isset($dentry['attribs']['']['ref'])) {
3234 $uri = $dentry['attribs']['']['ref'];
3236 if(isset($dentry['attribs']['']['when'])) {
3237 $when = $dentry['attribs']['']['when'];
3238 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3241 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3245 // check for relayed deletes to our conversation
3248 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3250 intval($importer['importer_uid'])
3253 $parent_uri = $r[0]['parent-uri'];
3254 if($r[0]['id'] != $r[0]['parent'])
3261 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3264 logger('local_delivery: possible community delete');
3267 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3269 // was the top-level post for this reply written by somebody on this site?
3270 // Specifically, the recipient?
3272 $is_a_remote_delete = false;
3274 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3275 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3276 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3277 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3278 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3279 AND `item`.`uid` = %d
3285 intval($importer['importer_uid'])
3288 $is_a_remote_delete = true;
3290 // Does this have the characteristics of a community or private group comment?
3291 // If it's a reply to a wall post on a community/prvgroup page it's a
3292 // valid community comment. Also forum_mode makes it valid for sure.
3293 // If neither, it's not.
3295 if($is_a_remote_delete && $community) {
3296 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3297 $is_a_remote_delete = false;
3298 logger('local_delivery: not a community delete');
3302 if($is_a_remote_delete) {
3303 logger('local_delivery: received remote delete');
3307 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3308 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3310 intval($importer['importer_uid']),
3311 intval($importer['id'])
3317 if($item['deleted'])
3320 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3322 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3323 $xo = parse_xml_string($item['object'],false);
3324 $xt = parse_xml_string($item['target'],false);
3326 if($xt->type === ACTIVITY_OBJ_NOTE) {
3327 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3329 intval($importer['importer_uid'])
3333 // For tags, the owner cannot remove the tag on the author's copy of the post.
3335 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3336 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3337 $author_copy = (($item['origin']) ? true : false);
3339 if($owner_remove && $author_copy)
3341 if($author_remove || $owner_remove) {
3342 $tags = explode(',',$i[0]['tag']);
3345 foreach($tags as $tag)
3346 if(trim($tag) !== trim($xo->body))
3347 $newtags[] = trim($tag);
3349 q("update item set tag = '%s' where id = %d",
3350 dbesc(implode(',',$newtags)),
3353 create_tags_from_item($i[0]['id']);
3359 if($item['uri'] == $item['parent-uri']) {
3360 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3361 `body` = '', `title` = ''
3362 WHERE `parent-uri` = '%s' AND `uid` = %d",
3364 dbesc(datetime_convert()),
3365 dbesc($item['uri']),
3366 intval($importer['importer_uid'])
3368 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3369 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3370 update_thread_uri($item['uri'], $importer['importer_uid']);
3373 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3374 `body` = '', `title` = ''
3375 WHERE `uri` = '%s' AND `uid` = %d",
3377 dbesc(datetime_convert()),
3379 intval($importer['importer_uid'])
3381 create_tags_from_itemuri($uri, $importer['importer_uid']);
3382 create_files_from_itemuri($uri, $importer['importer_uid']);
3383 update_thread_uri($uri, $importer['importer_uid']);
3384 if($item['last-child']) {
3385 // ensure that last-child is set in case the comment that had it just got wiped.
3386 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3387 dbesc(datetime_convert()),
3388 dbesc($item['parent-uri']),
3389 intval($item['uid'])
3391 // who is the last child now?
3392 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3393 ORDER BY `created` DESC LIMIT 1",
3394 dbesc($item['parent-uri']),
3395 intval($importer['importer_uid'])
3398 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3403 // if this is a relayed delete, propagate it to other recipients
3405 if($is_a_remote_delete)
3406 proc_run('php',"include/notifier.php","drop",$item['id']);
3414 foreach($feed->get_items() as $item) {
3417 $item_id = $item->get_id();
3418 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3419 if(isset($rawthread[0]['attribs']['']['ref'])) {
3421 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3427 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3430 logger('local_delivery: possible community reply');
3433 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3435 // was the top-level post for this reply written by somebody on this site?
3436 // Specifically, the recipient?
3438 $is_a_remote_comment = false;
3439 $top_uri = $parent_uri;
3441 $r = q("select `item`.`parent-uri` from `item`
3442 WHERE `item`.`uri` = '%s'
3446 if($r && count($r)) {
3447 $top_uri = $r[0]['parent-uri'];
3449 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3450 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3451 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3452 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3453 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3454 AND `item`.`uid` = %d
3460 intval($importer['importer_uid'])
3463 $is_a_remote_comment = true;
3466 // Does this have the characteristics of a community or private group comment?
3467 // If it's a reply to a wall post on a community/prvgroup page it's a
3468 // valid community comment. Also forum_mode makes it valid for sure.
3469 // If neither, it's not.
3471 if($is_a_remote_comment && $community) {
3472 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3473 $is_a_remote_comment = false;
3474 logger('local_delivery: not a community reply');
3478 if($is_a_remote_comment) {
3479 logger('local_delivery: received remote comment');
3481 // remote reply to our post. Import and then notify everybody else.
3483 $datarray = get_atom_elements($feed, $item);
3485 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3487 intval($importer['importer_uid'])
3490 // Update content if 'updated' changes
3494 if (edited_timestamp_is_newer($r[0], $datarray)) {
3496 // do not accept (ignore) an earlier edit than one we currently have.
3497 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3500 logger('received updated comment' , LOGGER_DEBUG);
3501 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3502 dbesc($datarray['title']),
3503 dbesc($datarray['body']),
3504 dbesc($datarray['tag']),
3505 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3506 dbesc(datetime_convert()),
3508 intval($importer['importer_uid'])
3510 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3512 proc_run('php',"include/notifier.php","comment-import",$iid);
3521 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3522 intval($importer['importer_uid'])
3526 $datarray['type'] = 'remote-comment';
3527 $datarray['wall'] = 1;
3528 $datarray['parent-uri'] = $parent_uri;
3529 $datarray['uid'] = $importer['importer_uid'];
3530 $datarray['owner-name'] = $own[0]['name'];
3531 $datarray['owner-link'] = $own[0]['url'];
3532 $datarray['owner-avatar'] = $own[0]['thumb'];
3533 $datarray['contact-id'] = $importer['id'];
3535 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3537 $datarray['type'] = 'activity';
3538 $datarray['gravity'] = GRAVITY_LIKE;
3539 $datarray['last-child'] = 0;
3540 // only one like or dislike per person
3541 // splitted into two queries for performance issues
3542 $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",
3543 intval($datarray['uid']),
3544 intval($datarray['contact-id']),
3545 dbesc($datarray['verb']),
3546 dbesc($datarray['parent-uri'])
3552 $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",
3553 intval($datarray['uid']),
3554 intval($datarray['contact-id']),
3555 dbesc($datarray['verb']),
3556 dbesc($datarray['parent-uri'])
3563 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3565 $xo = parse_xml_string($datarray['object'],false);
3566 $xt = parse_xml_string($datarray['target'],false);
3568 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3570 // fetch the parent item
3572 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3574 intval($importer['importer_uid'])
3579 // extract tag, if not duplicate, and this user allows tags, add to parent item
3581 if($xo->id && $xo->content) {
3582 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3583 if(! (stristr($tagp[0]['tag'],$newtag))) {
3584 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3585 intval($importer['importer_uid'])
3587 if(count($i) && ! intval($i[0]['blocktags'])) {
3588 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3589 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3590 intval($tagp[0]['id']),
3591 dbesc(datetime_convert()),
3592 dbesc(datetime_convert())
3594 create_tags_from_item($tagp[0]['id']);
3602 $posted_id = item_store($datarray);
3606 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3608 intval($importer['importer_uid'])
3611 $parent = $r[0]['parent'];
3612 $parent_uri = $r[0]['parent-uri'];
3616 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3617 dbesc(datetime_convert()),
3618 intval($importer['importer_uid']),
3619 intval($r[0]['parent'])
3622 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3623 dbesc(datetime_convert()),
3624 intval($importer['importer_uid']),
3629 if($posted_id && $parent) {
3631 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3633 if((! $is_like) && (! $importer['self'])) {
3635 require_once('include/enotify.php');
3638 'type' => NOTIFY_COMMENT,
3639 'notify_flags' => $importer['notify-flags'],
3640 'language' => $importer['language'],
3641 'to_name' => $importer['username'],
3642 'to_email' => $importer['email'],
3643 'uid' => $importer['importer_uid'],
3644 'item' => $datarray,
3645 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3646 'source_name' => stripslashes($datarray['author-name']),
3647 'source_link' => $datarray['author-link'],
3648 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3649 ? $importer['thumb'] : $datarray['author-avatar']),
3650 'verb' => ACTIVITY_POST,
3652 'parent' => $parent,
3653 'parent_uri' => $parent_uri,
3665 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3667 $item_id = $item->get_id();
3668 $datarray = get_atom_elements($feed,$item);
3670 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3673 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3675 intval($importer['importer_uid'])
3678 // Update content if 'updated' changes
3681 if (edited_timestamp_is_newer($r[0], $datarray)) {
3683 // do not accept (ignore) an earlier edit than one we currently have.
3684 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3687 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3688 dbesc($datarray['title']),
3689 dbesc($datarray['body']),
3690 dbesc($datarray['tag']),
3691 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3692 dbesc(datetime_convert()),
3694 intval($importer['importer_uid'])
3696 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3699 // update last-child if it changes
3701 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3702 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3703 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3704 dbesc(datetime_convert()),
3706 intval($importer['importer_uid'])
3708 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3709 intval($allow[0]['data']),
3710 dbesc(datetime_convert()),
3712 intval($importer['importer_uid'])
3718 $datarray['parent-uri'] = $parent_uri;
3719 $datarray['uid'] = $importer['importer_uid'];
3720 $datarray['contact-id'] = $importer['id'];
3721 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3722 $datarray['type'] = 'activity';
3723 $datarray['gravity'] = GRAVITY_LIKE;
3724 // only one like or dislike per person
3725 // splitted into two queries for performance issues
3726 $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",
3727 intval($datarray['uid']),
3728 intval($datarray['contact-id']),
3729 dbesc($datarray['verb']),
3735 $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",
3736 intval($datarray['uid']),
3737 intval($datarray['contact-id']),
3738 dbesc($datarray['verb']),
3746 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3748 $xo = parse_xml_string($datarray['object'],false);
3749 $xt = parse_xml_string($datarray['target'],false);
3751 if($xt->type == ACTIVITY_OBJ_NOTE) {
3752 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3754 intval($importer['importer_uid'])
3759 // extract tag, if not duplicate, add to parent item
3761 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3762 q("UPDATE item SET tag = '%s' WHERE id = %d",
3763 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3766 create_tags_from_item($r[0]['id']);
3772 $posted_id = item_store($datarray);
3774 // find out if our user is involved in this conversation and wants to be notified.
3776 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3778 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3780 intval($importer['importer_uid'])
3783 if(count($myconv)) {
3784 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3786 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3787 if(! link_compare($datarray['author-link'],$importer_url)) {
3790 foreach($myconv as $conv) {
3792 // now if we find a match, it means we're in this conversation
3794 if(! link_compare($conv['author-link'],$importer_url))
3797 require_once('include/enotify.php');
3799 $conv_parent = $conv['parent'];
3802 'type' => NOTIFY_COMMENT,
3803 'notify_flags' => $importer['notify-flags'],
3804 'language' => $importer['language'],
3805 'to_name' => $importer['username'],
3806 'to_email' => $importer['email'],
3807 'uid' => $importer['importer_uid'],
3808 'item' => $datarray,
3809 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3810 'source_name' => stripslashes($datarray['author-name']),
3811 'source_link' => $datarray['author-link'],
3812 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3813 ? $importer['thumb'] : $datarray['author-avatar']),
3814 'verb' => ACTIVITY_POST,
3816 'parent' => $conv_parent,
3817 'parent_uri' => $parent_uri
3821 // only send one notification
3833 // Head post of a conversation. Have we seen it? If not, import it.
3836 $item_id = $item->get_id();
3837 $datarray = get_atom_elements($feed,$item);
3839 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3840 $ev = bbtoevent($datarray['body']);
3841 if(x($ev,'desc') && x($ev,'start')) {
3842 $ev['cid'] = $importer['id'];
3843 $ev['uid'] = $importer['uid'];
3844 $ev['uri'] = $item_id;
3845 $ev['edited'] = $datarray['edited'];
3846 $ev['private'] = $datarray['private'];
3848 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3850 intval($importer['uid'])
3853 $ev['id'] = $r[0]['id'];
3854 $xyz = event_store($ev);
3859 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3861 intval($importer['importer_uid'])
3864 // Update content if 'updated' changes
3867 if (edited_timestamp_is_newer($r[0], $datarray)) {
3869 // do not accept (ignore) an earlier edit than one we currently have.
3870 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3873 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3874 dbesc($datarray['title']),
3875 dbesc($datarray['body']),
3876 dbesc($datarray['tag']),
3877 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3878 dbesc(datetime_convert()),
3880 intval($importer['importer_uid'])
3882 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3883 update_thread_uri($item_id, $importer['importer_uid']);
3886 // update last-child if it changes
3888 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3889 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3890 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3891 intval($allow[0]['data']),
3892 dbesc(datetime_convert()),
3894 intval($importer['importer_uid'])
3900 $datarray['parent-uri'] = $item_id;
3901 $datarray['uid'] = $importer['importer_uid'];
3902 $datarray['contact-id'] = $importer['id'];
3905 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3906 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3907 // but otherwise there's a possible data mixup on the sender's system.
3908 // the tgroup delivery code called from item_store will correct it if it's a forum,
3909 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3910 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3911 $datarray['owner-name'] = $importer['senderName'];
3912 $datarray['owner-link'] = $importer['url'];
3913 $datarray['owner-avatar'] = $importer['thumb'];
3916 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3919 // This is my contact on another system, but it's really me.
3920 // Turn this into a wall post.
3921 $notify = item_is_remote_self($importer, $datarray);
3923 $posted_id = item_store($datarray, false, $notify);
3925 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3926 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3929 $xo = parse_xml_string($datarray['object'],false);
3931 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3933 // somebody was poked/prodded. Was it me?
3935 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3937 foreach($links->link as $l) {
3938 $atts = $l->attributes();
3939 switch($atts['rel']) {
3941 $Blink = $atts['href'];
3947 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3949 // send a notification
3950 require_once('include/enotify.php');
3953 'type' => NOTIFY_POKE,
3954 'notify_flags' => $importer['notify-flags'],
3955 'language' => $importer['language'],
3956 'to_name' => $importer['username'],
3957 'to_email' => $importer['email'],
3958 'uid' => $importer['importer_uid'],
3959 'item' => $datarray,
3960 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3961 'source_name' => stripslashes($datarray['author-name']),
3962 'source_link' => $datarray['author-link'],
3963 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3964 ? $importer['thumb'] : $datarray['author-avatar']),
3965 'verb' => $datarray['verb'],
3966 'otype' => 'person',
3967 'activity' => $verb,
3984 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3985 $url = notags(trim($datarray['author-link']));
3986 $name = notags(trim($datarray['author-name']));
3987 $photo = notags(trim($datarray['author-avatar']));
3989 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3990 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3991 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3993 if(is_array($contact)) {
3994 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3995 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3996 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3997 intval(CONTACT_IS_FRIEND),
3998 intval($contact['id']),
3999 intval($importer['uid'])
4002 // send email notification to owner?
4006 // create contact record
4008 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4009 `blocked`, `readonly`, `pending`, `writable` )
4010 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4011 intval($importer['uid']),
4012 dbesc(datetime_convert()),
4014 dbesc(normalise_link($url)),
4018 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4019 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4021 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4022 intval($importer['uid']),
4026 $contact_record = $r[0];
4028 // create notification
4029 $hash = random_string();
4031 if(is_array($contact_record)) {
4032 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4033 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4034 intval($importer['uid']),
4035 intval($contact_record['id']),
4037 dbesc(datetime_convert())
4041 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4042 intval($importer['uid'])
4047 if(intval($r[0]['def_gid'])) {
4048 require_once('include/group.php');
4049 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4052 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4053 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
4058 'type' => NOTIFY_INTRO,
4059 'notify_flags' => $r[0]['notify-flags'],
4060 'language' => $r[0]['language'],
4061 'to_name' => $r[0]['username'],
4062 'to_email' => $r[0]['email'],
4063 'uid' => $r[0]['uid'],
4064 'link' => $a->get_baseurl() . '/notifications/intro',
4065 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4066 'source_link' => $contact_record['url'],
4067 'source_photo' => $contact_record['photo'],
4068 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4078 function lose_follower($importer,$contact,$datarray,$item) {
4080 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4081 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4082 intval(CONTACT_IS_SHARING),
4083 intval($contact['id'])
4087 contact_remove($contact['id']);
4091 function lose_sharer($importer,$contact,$datarray,$item) {
4093 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4094 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4095 intval(CONTACT_IS_FOLLOWER),
4096 intval($contact['id'])
4100 contact_remove($contact['id']);
4105 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4109 if(is_array($importer)) {
4110 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4111 intval($importer['uid'])
4115 // Diaspora has different message-ids in feeds than they do
4116 // through the direct Diaspora protocol. If we try and use
4117 // the feed, we'll get duplicates. So don't.
4119 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4122 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4124 // Use a single verify token, even if multiple hubs
4126 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4128 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4130 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4132 if(! strlen($contact['hub-verify'])) {
4133 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4134 dbesc($verify_token),
4135 intval($contact['id'])
4139 post_url($url,$params);
4141 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4148 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4152 $name = xmlify($name);
4153 $uri = xmlify($uri);
4156 $photo = xmlify($photo);
4160 $o .= "<name>$name</name>\r\n";
4161 $o .= "<uri>$uri</uri>\r\n";
4162 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4163 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4165 call_hooks('atom_author', $o);
4167 $o .= "</$tag>\r\n";
4171 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4175 if(! $item['parent'])
4178 if($item['deleted'])
4179 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4182 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4183 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4185 $body = $item['body'];
4188 $o = "\r\n\r\n<entry>\r\n";
4190 if(is_array($author))
4191 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4193 $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']));
4194 if(strlen($item['owner-name']))
4195 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4197 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4198 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4199 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4204 if ($item['title'] != "")
4205 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4207 $htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4209 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4210 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4211 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4212 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4213 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4214 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4215 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4219 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4221 if($item['location']) {
4222 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4223 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4227 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4229 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4230 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4233 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4234 if($item['bookmark'])
4235 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4238 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4241 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4243 if($item['signed_text']) {
4244 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4245 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4248 $verb = construct_verb($item);
4249 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4250 $actobj = construct_activity_object($item);
4253 $actarg = construct_activity_target($item);
4257 $tags = item_getfeedtags($item);
4259 foreach($tags as $t) {
4260 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4264 $o .= item_getfeedattach($item);
4266 $mentioned = get_mentions($item);
4270 call_hooks('atom_entry', $o);
4272 $o .= '</entry>' . "\r\n";
4277 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4279 if(get_config('system','disable_embedded'))
4284 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4285 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4290 $img_start = strpos($orig_body, '[img');
4291 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4292 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4293 while( ($img_st_close !== false) && ($img_len !== false) ) {
4295 $img_st_close++; // make it point to AFTER the closing bracket
4296 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4298 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4301 if(stristr($image , $site . '/photo/')) {
4302 // Only embed locally hosted photos
4304 $i = basename($image);
4305 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4306 $x = strpos($i,'-');
4309 $res = substr($i,$x+1);
4310 $i = substr($i,0,$x);
4311 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4318 // Check to see if we should replace this photo link with an embedded image
4319 // 1. No need to do so if the photo is public
4320 // 2. If there's a contact-id provided, see if they're in the access list
4321 // for the photo. If so, embed it.
4322 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4323 // permissions, regardless of order but first check to see if they're an exact
4324 // match to save some processing overhead.
4326 if(has_permissions($r[0])) {
4328 $recips = enumerate_permissions($r[0]);
4329 if(in_array($cid, $recips)) {
4334 if(compare_permissions($item,$r[0]))
4339 $data = $r[0]['data'];
4340 $type = $r[0]['type'];
4342 // If a custom width and height were specified, apply before embedding
4343 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4344 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4346 $width = intval($match[1]);
4347 $height = intval($match[2]);
4349 $ph = new Photo($data, $type);
4350 if($ph->is_valid()) {
4351 $ph->scaleImage(max($width, $height));
4352 $data = $ph->imageString();
4353 $type = $ph->getType();
4357 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4358 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4359 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4365 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4366 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4367 if($orig_body === false)
4370 $img_start = strpos($orig_body, '[img');
4371 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4372 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4375 $new_body = $new_body . $orig_body;
4381 function has_permissions($obj) {
4382 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4387 function compare_permissions($obj1,$obj2) {
4388 // first part is easy. Check that these are exactly the same.
4389 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4390 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4391 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4392 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4395 // This is harder. Parse all the permissions and compare the resulting set.
4397 $recipients1 = enumerate_permissions($obj1);
4398 $recipients2 = enumerate_permissions($obj2);
4401 if($recipients1 == $recipients2)
4406 // returns an array of contact-ids that are allowed to see this object
4408 function enumerate_permissions($obj) {
4409 require_once('include/group.php');
4410 $allow_people = expand_acl($obj['allow_cid']);
4411 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4412 $deny_people = expand_acl($obj['deny_cid']);
4413 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4414 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4415 $deny = array_unique(array_merge($deny_people,$deny_groups));
4416 $recipients = array_diff($recipients,$deny);
4420 function item_getfeedtags($item) {
4423 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4425 for($x = 0; $x < $cnt; $x ++) {
4427 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4431 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4433 for($x = 0; $x < $cnt; $x ++) {
4435 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4441 function item_getfeedattach($item) {
4443 $arr = explode('[/attach],',$item['attach']);
4445 foreach($arr as $r) {
4447 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4449 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4450 if(intval($matches[2]))
4451 $ret .= 'length="' . intval($matches[2]) . '" ';
4452 if($matches[4] !== ' ')
4453 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4454 $ret .= ' />' . "\r\n";
4463 function item_expire($uid, $days, $network = "", $force = false) {
4465 if((! $uid) || ($days < 1))
4468 // $expire_network_only = save your own wall posts
4469 // and just expire conversations started by others
4471 $expire_network_only = get_pconfig($uid,'expire','network_only');
4472 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4474 if ($network != "") {
4475 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4476 // There is an index "uid_network_received" but not "uid_network_created"
4477 // This avoids the creation of another index just for one purpose.
4478 // And it doesn't really matter wether to look at "received" or "created"
4479 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4481 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4483 $r = q("SELECT * FROM `item`
4484 WHERE `uid` = %d $range
4495 $expire_items = get_pconfig($uid, 'expire','items');
4496 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4498 // Forcing expiring of items - but not notes and marked items
4500 $expire_items = true;
4502 $expire_notes = get_pconfig($uid, 'expire','notes');
4503 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4505 $expire_starred = get_pconfig($uid, 'expire','starred');
4506 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4508 $expire_photos = get_pconfig($uid, 'expire','photos');
4509 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4511 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4513 foreach($r as $item) {
4515 // don't expire filed items
4517 if(strpos($item['file'],'[') !== false)
4520 // Only expire posts, not photos and photo comments
4522 if($expire_photos==0 && strlen($item['resource-id']))
4524 if($expire_starred==0 && intval($item['starred']))
4526 if($expire_notes==0 && $item['type']=='note')
4528 if($expire_items==0 && $item['type']!='note')
4531 drop_item($item['id'],false);
4534 proc_run('php',"include/notifier.php","expire","$uid");
4539 function drop_items($items) {
4542 if(! local_user() && ! remote_user())
4546 foreach($items as $item) {
4547 $owner = drop_item($item,false);
4548 if($owner && ! $uid)
4553 // multiple threads may have been deleted, send an expire notification
4556 proc_run('php',"include/notifier.php","expire","$uid");
4560 function drop_item($id,$interactive = true) {
4564 // locate item to be deleted
4566 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4573 notice( t('Item not found.') . EOL);
4574 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4579 $owner = $item['uid'];
4583 // check if logged in user is either the author or owner of this item
4585 if(is_array($_SESSION['remote'])) {
4586 foreach($_SESSION['remote'] as $visitor) {
4587 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4588 $cid = $visitor['cid'];
4595 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4597 // Check if we should do HTML-based delete confirmation
4598 if($_REQUEST['confirm']) {
4599 // <form> can't take arguments in its "action" parameter
4600 // so add any arguments as hidden inputs
4601 $query = explode_querystring($a->query_string);
4603 foreach($query['args'] as $arg) {
4604 if(strpos($arg, 'confirm=') === false) {
4605 $arg_parts = explode('=', $arg);
4606 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4610 return replace_macros(get_markup_template('confirm.tpl'), array(
4612 '$message' => t('Do you really want to delete this item?'),
4613 '$extra_inputs' => $inputs,
4614 '$confirm' => t('Yes'),
4615 '$confirm_url' => $query['base'],
4616 '$confirm_name' => 'confirmed',
4617 '$cancel' => t('Cancel'),
4620 // Now check how the user responded to the confirmation query
4621 if($_REQUEST['canceled']) {
4622 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4625 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4628 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4629 dbesc(datetime_convert()),
4630 dbesc(datetime_convert()),
4633 create_tags_from_item($item['id']);
4634 create_files_from_item($item['id']);
4635 delete_thread($item['id'], $item['parent-uri']);
4637 // clean up categories and tags so they don't end up as orphans
4640 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4642 foreach($matches as $mtch) {
4643 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4649 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4651 foreach($matches as $mtch) {
4652 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4656 // If item is a link to a photo resource, nuke all the associated photos
4657 // (visitors will not have photo resources)
4658 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4659 // generate a resource-id and therefore aren't intimately linked to the item.
4661 if(strlen($item['resource-id'])) {
4662 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4663 dbesc($item['resource-id']),
4664 intval($item['uid'])
4666 // ignore the result
4669 // If item is a link to an event, nuke the event record.
4671 if(intval($item['event-id'])) {
4672 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4673 intval($item['event-id']),
4674 intval($item['uid'])
4676 // ignore the result
4679 // clean up item_id and sign meta-data tables
4682 // Old code - caused very long queries and warning entries in the mysql logfiles:
4684 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4685 intval($item['id']),
4686 intval($item['uid'])
4689 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4690 intval($item['id']),
4691 intval($item['uid'])
4695 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4697 // Creating list of parents
4698 $r = q("select id from item where parent = %d and uid = %d",
4699 intval($item['id']),
4700 intval($item['uid'])
4705 foreach ($r AS $row) {
4706 if ($parentid != "")
4709 $parentid .= $row["id"];
4713 if ($parentid != "") {
4714 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4716 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4719 // If it's the parent of a comment thread, kill all the kids
4721 if($item['uri'] == $item['parent-uri']) {
4722 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4723 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4724 dbesc(datetime_convert()),
4725 dbesc(datetime_convert()),
4726 dbesc($item['parent-uri']),
4727 intval($item['uid'])
4729 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4730 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4731 delete_thread_uri($item['parent-uri'], $item['uid']);
4732 // ignore the result
4735 // ensure that last-child is set in case the comment that had it just got wiped.
4736 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4737 dbesc(datetime_convert()),
4738 dbesc($item['parent-uri']),
4739 intval($item['uid'])
4741 // who is the last child now?
4742 $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",
4743 dbesc($item['parent-uri']),
4744 intval($item['uid'])
4747 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4752 // Add a relayable_retraction signature for Diaspora.
4753 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4755 $drop_id = intval($item['id']);
4757 // send the notification upstream/downstream as the case may be
4759 proc_run('php',"include/notifier.php","drop","$drop_id");
4763 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4769 notice( t('Permission denied.') . EOL);
4770 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4777 function first_post_date($uid,$wall = false) {
4778 $r = q("select id, created from item
4779 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4781 order by created asc limit 1",
4783 intval($wall ? 1 : 0)
4786 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4787 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4792 function posted_dates($uid,$wall) {
4793 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4795 $dthen = first_post_date($uid,$wall);
4799 // Set the start and end date to the beginning of the month
4800 $dnow = substr($dnow,0,8).'01';
4801 $dthen = substr($dthen,0,8).'01';
4804 // Starting with the current month, get the first and last days of every
4805 // month down to and including the month of the first post
4806 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4807 $dstart = substr($dnow,0,8) . '01';
4808 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4809 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4810 $end_month = datetime_convert('','',$dend,'Y-m-d');
4811 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4812 $ret[] = array($str,$end_month,$start_month);
4813 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4819 function posted_date_widget($url,$uid,$wall) {
4822 if(! feature_enabled($uid,'archives'))
4825 // For former Facebook folks that left because of "timeline"
4827 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4830 $ret = posted_dates($uid,$wall);
4834 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4835 '$title' => t('Archives'),
4836 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4843 function store_diaspora_retract_sig($item, $user, $baseurl) {
4844 // Note that we can't add a target_author_signature
4845 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4846 // the comment, that means we're the home of the post, and Diaspora will only
4847 // check the parent_author_signature of retractions that it doesn't have to relay further
4849 // I don't think this function gets called for an "unlike," but I'll check anyway
4851 $enabled = intval(get_config('system','diaspora_enabled'));
4853 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4857 logger('drop_item: storing diaspora retraction signature');
4859 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4861 if(local_user() == $item['uid']) {
4863 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4864 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4867 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4868 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4871 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4872 // only handles DFRN deletes
4873 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4874 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4875 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4881 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4882 intval($item['id']),
4883 dbesc($signed_text),