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);
1379 // Add every contact to the global contact table
1383 // Is it a global copy?
1384 $store_gcontact = ($arr["uid"] == 0);
1386 // Is it a comment on a global copy?
1387 if (!$store_gcontact AND ($arr["uri"] != $arr["parent-uri"])) {
1388 $q = q("SELECT `id` FROM `item` WHERE `uri`='%s' AND `uid` = 0",
1389 $arr["parent-uri"]);
1390 $store_gcontact = count($q);
1393 // This check for private and network is maybe superflous
1394 if ($store_gcontact AND !$arr['private'] AND in_array($arr["network"],
1395 array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS, ""))) {
1397 // "3" means: We don't know this contact directly (Maybe a reshared item)
1401 // Is it a user from our server?
1402 $q = q("SELECT `id` FROM `contact` WHERE `self` AND `nurl` = '%s' LIMIT 1",
1403 dbesc(normalise_link($arr["author-link"])));
1406 $network = NETWORK_DFRN;
1407 } else { // Is it a contact from a user on our server?
1408 $q = q("SELECT `network` FROM `contact` WHERE `uid` != 0 AND `network` != ''
1409 AND (`nurl` = '%s' OR `alias` IN ('%s', '%s')) LIMIT 1",
1410 dbesc(normalise_link($arr["author-link"])),
1411 dbesc(normalise_link($arr["author-link"])),
1412 dbesc($arr["author-link"]));
1415 $network = $q[0]["network"];
1419 poco_check($arr["author-link"], $arr["author-name"], $network, $arr["author-avatar"], "", "", "", "", "", $arr["received"], $generation, $arr["contact-id"], $arr["uid"]);
1421 // Maybe its a body with a shared item? Then extract a global contact from it.
1422 poco_contact_from_body($arr["body"], $arr["received"], $arr["contact-id"], $arr["uid"]);
1425 // Set "success_update" to the date of the last time we heard from this contact
1426 // This can be used to filter for inactive contacts and poco.
1427 // Only do this for public postings to avoid privacy problems, since poco data is public.
1428 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1429 if (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])))
1430 q("UPDATE `contact` SET `success_update` = '%s' WHERE `id` = %d",
1431 dbesc($arr['received']),
1432 intval($arr['contact-id'])
1435 logger('item_store: could not locate created item');
1439 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1440 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1442 intval($arr['uid']),
1443 intval($current_post)
1447 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1448 $parent_id = $current_post;
1450 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1453 $private = $arr['private'];
1455 // Set parent id - and also make sure to inherit the parent's ACLs.
1457 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1458 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1465 intval($parent_deleted),
1466 intval($current_post)
1469 // Complete ostatus threads
1470 if ($ostatus_conversation)
1471 complete_conversation($current_post, $ostatus_conversation);
1473 $arr['id'] = $current_post;
1474 $arr['parent'] = $parent_id;
1475 $arr['allow_cid'] = $allow_cid;
1476 $arr['allow_gid'] = $allow_gid;
1477 $arr['deny_cid'] = $deny_cid;
1478 $arr['deny_gid'] = $deny_gid;
1479 $arr['private'] = $private;
1480 $arr['deleted'] = $parent_deleted;
1482 // update the commented timestamp on the parent
1484 q("UPDATE `item` set `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1485 dbesc(datetime_convert()),
1486 dbesc(datetime_convert()),
1491 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1492 intval($current_post),
1493 dbesc($dsprsig->signed_text),
1494 dbesc($dsprsig->signature),
1495 dbesc($dsprsig->signer)
1501 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1504 if($arr['last-child']) {
1505 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1507 intval($arr['uid']),
1508 intval($current_post)
1512 $deleted = tag_deliver($arr['uid'],$current_post);
1514 // current post can be deleted if is for a communuty page and no mention are
1516 if (!$deleted AND !$dontcache) {
1518 // Store the fresh generated item into the cache
1519 put_item_in_cache($arr);
1521 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1522 if (count($r) == 1) {
1523 call_hooks('post_remote_end', $r[0]);
1525 logger('item_store: new item not found in DB, id ' . $current_post);
1529 create_tags_from_item($current_post);
1530 create_files_from_item($current_post);
1532 // Only check for notifications on start posts
1533 if ($arr['parent-uri'] === $arr['uri']) {
1534 add_thread($current_post);
1535 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1537 // Send a notification for every new post?
1538 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1539 intval($arr['contact-id']),
1542 $send_notification = count($r);
1544 if (!$send_notification) {
1545 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1546 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1549 foreach ($tags AS $tag) {
1550 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1551 normalise_link($tag["url"]), intval($arr['uid']));
1553 $send_notification = true;
1558 if ($send_notification) {
1559 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1560 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1561 intval($arr['uid']));
1563 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1564 intval($current_post),
1570 require_once('include/enotify.php');
1572 'type' => NOTIFY_SHARE,
1573 'notify_flags' => $u[0]['notify-flags'],
1574 'language' => $u[0]['language'],
1575 'to_name' => $u[0]['username'],
1576 'to_email' => $u[0]['email'],
1577 'uid' => $u[0]['uid'],
1579 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1580 'source_name' => $item[0]['author-name'],
1581 'source_link' => $item[0]['author-link'],
1582 'source_photo' => $item[0]['author-avatar'],
1583 'verb' => ACTIVITY_TAG,
1586 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1589 update_thread($parent_id);
1592 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1594 return $current_post;
1597 function get_item_guid($id) {
1598 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1600 return($r[0]["guid"]);
1605 function get_item_id($guid, $uid = 0) {
1611 $uid == local_user();
1613 // Does the given user have this item?
1615 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1616 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1617 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1620 $nick = $r[0]["nickname"];
1624 // Or is it anywhere on the server?
1626 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1627 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1628 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1629 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1630 AND `item`.`private` = 0 AND `item`.`wall` = 1
1631 AND `item`.`guid` = '%s'", dbesc($guid));
1634 $nick = $r[0]["nickname"];
1637 return(array("nick" => $nick, "id" => $id));
1641 function get_item_contact($item,$contacts) {
1642 if(! count($contacts) || (! is_array($item)))
1644 foreach($contacts as $contact) {
1645 if($contact['id'] == $item['contact-id']) {
1647 break; // NOTREACHED
1654 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1656 * @param int $item_id
1657 * @return bool true if item was deleted, else false
1659 function tag_deliver($uid,$item_id) {
1667 $u = q("select * from user where uid = %d limit 1",
1673 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1674 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1677 $i = q("select * from item where id = %d and uid = %d limit 1",
1686 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1688 // Diaspora uses their own hardwired link URL in @-tags
1689 // instead of the one we supply with webfinger
1691 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1693 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1695 foreach($matches as $mtch) {
1696 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1698 logger('tag_deliver: mention found: ' . $mtch[2]);
1704 if ( ($community_page || $prvgroup) &&
1705 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1706 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1708 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1709 q("DELETE FROM item WHERE id = %d and uid = %d",
1719 // send a notification
1721 // use a local photo if we have one
1723 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1724 intval($u[0]['uid']),
1725 dbesc(normalise_link($item['author-link']))
1727 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1730 require_once('include/enotify.php');
1732 'type' => NOTIFY_TAGSELF,
1733 'notify_flags' => $u[0]['notify-flags'],
1734 'language' => $u[0]['language'],
1735 'to_name' => $u[0]['username'],
1736 'to_email' => $u[0]['email'],
1737 'uid' => $u[0]['uid'],
1739 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1740 'source_name' => $item['author-name'],
1741 'source_link' => $item['author-link'],
1742 'source_photo' => $photo,
1743 'verb' => ACTIVITY_TAG,
1745 'parent' => $item['parent']
1749 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1751 call_hooks('tagged', $arr);
1753 if((! $community_page) && (! $prvgroup))
1757 // tgroup delivery - setup a second delivery chain
1758 // prevent delivery looping - only proceed
1759 // if the message originated elsewhere and is a top-level post
1761 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1764 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1767 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1768 intval($u[0]['uid'])
1773 // also reset all the privacy bits to the forum default permissions
1775 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1777 $forum_mode = (($prvgroup) ? 2 : 1);
1779 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1780 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1781 intval($forum_mode),
1782 dbesc($c[0]['name']),
1783 dbesc($c[0]['url']),
1784 dbesc($c[0]['thumb']),
1786 dbesc($u[0]['allow_cid']),
1787 dbesc($u[0]['allow_gid']),
1788 dbesc($u[0]['deny_cid']),
1789 dbesc($u[0]['deny_gid']),
1792 update_thread($item_id);
1794 proc_run('php','include/notifier.php','tgroup',$item_id);
1800 function tgroup_check($uid,$item) {
1806 // check that the message originated elsewhere and is a top-level post
1808 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1812 $u = q("select * from user where uid = %d limit 1",
1818 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1819 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1822 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1824 // Diaspora uses their own hardwired link URL in @-tags
1825 // instead of the one we supply with webfinger
1827 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1829 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1831 foreach($matches as $mtch) {
1832 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1834 logger('tgroup_check: mention found: ' . $mtch[2]);
1842 if((! $community_page) && (! $prvgroup))
1856 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1860 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1862 if($contact['duplex'] && $contact['dfrn-id'])
1863 $idtosend = '0:' . $orig_id;
1864 if($contact['duplex'] && $contact['issued-id'])
1865 $idtosend = '1:' . $orig_id;
1867 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1869 $rino_enable = get_config('system','rino_encrypt');
1874 $ssl_val = intval(get_config('system','ssl_policy'));
1878 case SSL_POLICY_FULL:
1879 $ssl_policy = 'full';
1881 case SSL_POLICY_SELFSIGN:
1882 $ssl_policy = 'self';
1884 case SSL_POLICY_NONE:
1886 $ssl_policy = 'none';
1890 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1892 logger('dfrn_deliver: ' . $url);
1894 $xml = fetch_url($url);
1896 $curl_stat = $a->get_curl_code();
1898 return(-1); // timed out
1900 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1905 if(strpos($xml,'<?xml') === false) {
1906 logger('dfrn_deliver: no valid XML returned');
1907 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1911 $res = parse_xml_string($xml);
1913 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1914 return (($res->status) ? $res->status : 3);
1916 $postvars = array();
1917 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1918 $challenge = hex2bin((string) $res->challenge);
1919 $perm = (($res->perm) ? $res->perm : null);
1920 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1921 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1922 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1924 if($owner['page-flags'] == PAGE_PRVGROUP)
1927 $final_dfrn_id = '';
1930 if((($perm == 'rw') && (! intval($contact['writable'])))
1931 || (($perm == 'r') && (intval($contact['writable'])))) {
1932 q("update contact set writable = %d where id = %d",
1933 intval(($perm == 'rw') ? 1 : 0),
1934 intval($contact['id'])
1936 $contact['writable'] = (string) 1 - intval($contact['writable']);
1940 if(($contact['duplex'] && strlen($contact['pubkey']))
1941 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1942 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1943 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
1944 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
1947 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
1948 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
1951 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
1953 if(strpos($final_dfrn_id,':') == 1)
1954 $final_dfrn_id = substr($final_dfrn_id,2);
1956 if($final_dfrn_id != $orig_id) {
1957 logger('dfrn_deliver: wrong dfrn_id.');
1958 // did not decode properly - cannot trust this site
1962 $postvars['dfrn_id'] = $idtosend;
1963 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
1965 $postvars['dissolve'] = '1';
1968 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
1969 $postvars['data'] = $atom;
1970 $postvars['perm'] = 'rw';
1973 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
1974 $postvars['perm'] = 'r';
1977 $postvars['ssl_policy'] = $ssl_policy;
1980 $postvars['page'] = $page;
1982 if($rino && $rino_allowed && (! $dissolve)) {
1983 $key = substr(random_string(),0,16);
1984 $data = bin2hex(aes_encrypt($postvars['data'],$key));
1985 $postvars['data'] = $data;
1986 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
1989 if($dfrn_version >= 2.1) {
1990 if(($contact['duplex'] && strlen($contact['pubkey']))
1991 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
1992 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
1994 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
1997 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2001 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2002 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2005 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2009 logger('md5 rawkey ' . md5($postvars['key']));
2011 $postvars['key'] = bin2hex($postvars['key']);
2014 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2016 $xml = post_url($contact['notify'],$postvars);
2018 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2020 $curl_stat = $a->get_curl_code();
2021 if((! $curl_stat) || (! strlen($xml)))
2022 return(-1); // timed out
2024 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2027 if(strpos($xml,'<?xml') === false) {
2028 logger('dfrn_deliver: phase 2: no valid XML returned');
2029 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2033 if($contact['term-date'] != '0000-00-00 00:00:00') {
2034 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2035 require_once('include/Contact.php');
2036 unmark_for_death($contact);
2039 $res = parse_xml_string($xml);
2041 return $res->status;
2046 This function returns true if $update has an edited timestamp newer
2047 than $existing, i.e. $update contains new data which should override
2048 what's already there. If there is no timestamp yet, the update is
2049 assumed to be newer. If the update has no timestamp, the existing
2050 item is assumed to be up-to-date. If the timestamps are equal it
2051 assumes the update has been seen before and should be ignored.
2053 function edited_timestamp_is_newer($existing, $update) {
2054 if (!x($existing,'edited') || !$existing['edited']) {
2057 if (!x($update,'edited') || !$update['edited']) {
2060 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2061 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2062 return (strcmp($existing_edited, $update_edited) < 0);
2067 * consume_feed - process atom feed and update anything/everything we might need to update
2069 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2071 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2072 * It is this person's stuff that is going to be updated.
2073 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2074 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2075 * have a contact record.
2076 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2077 * might not) try and subscribe to it.
2078 * $datedir sorts in reverse order
2079 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2080 * imported prior to its children being seen in the stream unless we are certain
2081 * of how the feed is arranged/ordered.
2082 * With $pass = 1, we only pull parent items out of the stream.
2083 * With $pass = 2, we only pull children (comments/likes).
2085 * So running this twice, first with pass 1 and then with pass 2 will do the right
2086 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2087 * model where comments can have sub-threads. That would require some massive sorting
2088 * to get all the feed items into a mostly linear ordering, and might still require
2092 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2094 require_once('library/simplepie/simplepie.inc');
2095 require_once('include/contact_selectors.php');
2097 if(! strlen($xml)) {
2098 logger('consume_feed: empty input');
2102 $feed = new SimplePie();
2103 $feed->set_raw_data($xml);
2105 $feed->enable_order_by_date(true);
2107 $feed->enable_order_by_date(false);
2111 logger('consume_feed: Error parsing XML: ' . $feed->error());
2113 $permalink = $feed->get_permalink();
2115 // Check at the feed level for updated contact name and/or photo
2119 $photo_timestamp = '';
2122 $contact_updated = '';
2124 $hubs = $feed->get_links('hub');
2125 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2128 $hub = implode(',', $hubs);
2130 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2132 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2134 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2135 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2136 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2137 $new_name = $elems['name'][0]['data'];
2139 // Manually checking for changed contact names
2140 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2141 $name_updated = date("c");
2142 $photo_timestamp = date("c");
2145 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2146 if ($photo_timestamp == "")
2147 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2148 $photo_url = $elems['link'][0]['attribs']['']['href'];
2151 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2152 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2156 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2157 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2159 $contact_updated = $photo_timestamp;
2161 require_once("include/Photo.php");
2162 $photo_failure = false;
2163 $have_photo = false;
2165 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2166 intval($contact['id']),
2167 intval($contact['uid'])
2170 $resource_id = $r[0]['resource-id'];
2174 $resource_id = photo_new_resource();
2177 $img_str = fetch_url($photo_url,true);
2178 // guess mimetype from headers or filename
2179 $type = guess_image_type($photo_url,true);
2182 $img = new Photo($img_str, $type);
2183 if($img->is_valid()) {
2185 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2186 dbesc($resource_id),
2187 intval($contact['id']),
2188 intval($contact['uid'])
2192 $img->scaleImageSquare(175);
2194 $hash = $resource_id;
2195 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2197 $img->scaleImage(80);
2198 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2200 $img->scaleImage(48);
2201 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2205 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2206 WHERE `uid` = %d AND `id` = %d",
2207 dbesc(datetime_convert()),
2208 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2209 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2210 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2211 intval($contact['uid']),
2212 intval($contact['id'])
2217 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2218 if ($name_updated > $contact_updated)
2219 $contact_updated = $name_updated;
2221 $r = q("select * from contact where uid = %d and id = %d limit 1",
2222 intval($contact['uid']),
2223 intval($contact['id'])
2226 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2227 dbesc(notags(trim($new_name))),
2228 dbesc(datetime_convert()),
2229 intval($contact['uid']),
2230 intval($contact['id'])
2233 // do our best to update the name on content items
2236 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2237 dbesc(notags(trim($new_name))),
2238 dbesc($r[0]['name']),
2239 dbesc($r[0]['url']),
2240 intval($contact['uid'])
2245 if ($contact_updated AND $new_name AND $photo_url)
2246 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2248 if(strlen($birthday)) {
2249 if(substr($birthday,0,4) != $contact['bdyear']) {
2250 logger('consume_feed: updating birthday: ' . $birthday);
2254 * Add new birthday event for this person
2256 * $bdtext is just a readable placeholder in case the event is shared
2257 * with others. We will replace it during presentation to our $importer
2258 * to contain a sparkle link and perhaps a photo.
2262 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2263 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2266 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2267 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2268 intval($contact['uid']),
2269 intval($contact['id']),
2270 dbesc(datetime_convert()),
2271 dbesc(datetime_convert()),
2272 dbesc(datetime_convert('UTC','UTC', $birthday)),
2273 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2282 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2283 dbesc(substr($birthday,0,4)),
2284 intval($contact['uid']),
2285 intval($contact['id'])
2288 // This function is called twice without reloading the contact
2289 // Make sure we only create one event. This is why &$contact
2290 // is a reference var in this function
2292 $contact['bdyear'] = substr($birthday,0,4);
2296 $community_page = 0;
2297 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2299 $community_page = intval($rawtags[0]['data']);
2301 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2302 q("update contact set forum = %d where id = %d",
2303 intval($community_page),
2304 intval($contact['id'])
2306 $contact['forum'] = (string) $community_page;
2310 // process any deleted entries
2312 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2313 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2314 foreach($del_entries as $dentry) {
2316 if(isset($dentry['attribs']['']['ref'])) {
2317 $uri = $dentry['attribs']['']['ref'];
2319 if(isset($dentry['attribs']['']['when'])) {
2320 $when = $dentry['attribs']['']['when'];
2321 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2324 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2326 if($deleted && is_array($contact)) {
2327 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2328 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2330 intval($importer['uid']),
2331 intval($contact['id'])
2336 if(! $item['deleted'])
2337 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2339 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2340 $xo = parse_xml_string($item['object'],false);
2341 $xt = parse_xml_string($item['target'],false);
2342 if($xt->type === ACTIVITY_OBJ_NOTE) {
2343 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2345 intval($importer['importer_uid'])
2349 // For tags, the owner cannot remove the tag on the author's copy of the post.
2351 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2352 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2353 $author_copy = (($item['origin']) ? true : false);
2355 if($owner_remove && $author_copy)
2357 if($author_remove || $owner_remove) {
2358 $tags = explode(',',$i[0]['tag']);
2361 foreach($tags as $tag)
2362 if(trim($tag) !== trim($xo->body))
2363 $newtags[] = trim($tag);
2365 q("update item set tag = '%s' where id = %d",
2366 dbesc(implode(',',$newtags)),
2369 create_tags_from_item($i[0]['id']);
2375 if($item['uri'] == $item['parent-uri']) {
2376 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2377 `body` = '', `title` = ''
2378 WHERE `parent-uri` = '%s' AND `uid` = %d",
2380 dbesc(datetime_convert()),
2381 dbesc($item['uri']),
2382 intval($importer['uid'])
2384 create_tags_from_itemuri($item['uri'], $importer['uid']);
2385 create_files_from_itemuri($item['uri'], $importer['uid']);
2386 update_thread_uri($item['uri'], $importer['uid']);
2389 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2390 `body` = '', `title` = ''
2391 WHERE `uri` = '%s' AND `uid` = %d",
2393 dbesc(datetime_convert()),
2395 intval($importer['uid'])
2397 create_tags_from_itemuri($uri, $importer['uid']);
2398 create_files_from_itemuri($uri, $importer['uid']);
2399 if($item['last-child']) {
2400 // ensure that last-child is set in case the comment that had it just got wiped.
2401 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2402 dbesc(datetime_convert()),
2403 dbesc($item['parent-uri']),
2404 intval($item['uid'])
2406 // who is the last child now?
2407 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2408 ORDER BY `created` DESC LIMIT 1",
2409 dbesc($item['parent-uri']),
2410 intval($importer['uid'])
2413 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2424 // Now process the feed
2426 if($feed->get_item_quantity()) {
2428 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2430 // in inverse date order
2432 $items = array_reverse($feed->get_items());
2434 $items = $feed->get_items();
2437 foreach($items as $item) {
2440 $item_id = $item->get_id();
2441 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2442 if(isset($rawthread[0]['attribs']['']['ref'])) {
2444 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2447 if(($is_reply) && is_array($contact)) {
2452 // not allowed to post
2454 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2458 // Have we seen it? If not, import it.
2460 $item_id = $item->get_id();
2461 $datarray = get_atom_elements($feed, $item, $contact);
2463 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2464 $datarray['author-name'] = $contact['name'];
2465 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2466 $datarray['author-link'] = $contact['url'];
2467 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2468 $datarray['author-avatar'] = $contact['thumb'];
2470 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2471 logger('consume_feed: no author information! ' . print_r($datarray,true));
2475 $force_parent = false;
2476 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2477 if($contact['network'] === NETWORK_OSTATUS)
2478 $force_parent = true;
2479 if(strlen($datarray['title']))
2480 unset($datarray['title']);
2481 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2482 dbesc(datetime_convert()),
2484 intval($importer['uid'])
2486 $datarray['last-child'] = 1;
2487 update_thread_uri($parent_uri, $importer['uid']);
2491 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2493 intval($importer['uid'])
2496 // Update content if 'updated' changes
2499 if (edited_timestamp_is_newer($r[0], $datarray)) {
2501 // do not accept (ignore) an earlier edit than one we currently have.
2502 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2505 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2506 dbesc($datarray['title']),
2507 dbesc($datarray['body']),
2508 dbesc($datarray['tag']),
2509 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2510 dbesc(datetime_convert()),
2512 intval($importer['uid'])
2514 create_tags_from_itemuri($item_id, $importer['uid']);
2515 update_thread_uri($item_id, $importer['uid']);
2518 // update last-child if it changes
2520 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2521 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2522 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2523 dbesc(datetime_convert()),
2525 intval($importer['uid'])
2527 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2528 intval($allow[0]['data']),
2529 dbesc(datetime_convert()),
2531 intval($importer['uid'])
2533 update_thread_uri($item_id, $importer['uid']);
2539 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2540 // one way feed - no remote comment ability
2541 $datarray['last-child'] = 0;
2543 $datarray['parent-uri'] = $parent_uri;
2544 $datarray['uid'] = $importer['uid'];
2545 $datarray['contact-id'] = $contact['id'];
2546 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2547 $datarray['type'] = 'activity';
2548 $datarray['gravity'] = GRAVITY_LIKE;
2549 // only one like or dislike per person
2550 // splitted into two queries for performance issues
2551 $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",
2552 intval($datarray['uid']),
2553 intval($datarray['contact-id']),
2554 dbesc($datarray['verb']),
2560 $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",
2561 intval($datarray['uid']),
2562 intval($datarray['contact-id']),
2563 dbesc($datarray['verb']),
2570 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2571 $xo = parse_xml_string($datarray['object'],false);
2572 $xt = parse_xml_string($datarray['target'],false);
2574 if($xt->type == ACTIVITY_OBJ_NOTE) {
2575 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2577 intval($importer['importer_uid'])
2582 // extract tag, if not duplicate, add to parent item
2583 if($xo->id && $xo->content) {
2584 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2585 if(! (stristr($r[0]['tag'],$newtag))) {
2586 q("UPDATE item SET tag = '%s' WHERE id = %d",
2587 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2590 create_tags_from_item($r[0]['id']);
2596 $r = item_store($datarray,$force_parent);
2602 // Head post of a conversation. Have we seen it? If not, import it.
2604 $item_id = $item->get_id();
2606 $datarray = get_atom_elements($feed, $item, $contact);
2608 if(is_array($contact)) {
2609 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2610 $datarray['author-name'] = $contact['name'];
2611 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2612 $datarray['author-link'] = $contact['url'];
2613 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2614 $datarray['author-avatar'] = $contact['thumb'];
2617 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2618 logger('consume_feed: no author information! ' . print_r($datarray,true));
2622 // special handling for events
2624 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2625 $ev = bbtoevent($datarray['body']);
2626 if(x($ev,'desc') && x($ev,'start')) {
2627 $ev['uid'] = $importer['uid'];
2628 $ev['uri'] = $item_id;
2629 $ev['edited'] = $datarray['edited'];
2630 $ev['private'] = $datarray['private'];
2632 if(is_array($contact))
2633 $ev['cid'] = $contact['id'];
2634 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2636 intval($importer['uid'])
2639 $ev['id'] = $r[0]['id'];
2640 $xyz = event_store($ev);
2645 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2646 if(strlen($datarray['title']))
2647 unset($datarray['title']);
2648 $datarray['last-child'] = 1;
2652 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2654 intval($importer['uid'])
2657 // Update content if 'updated' changes
2660 if (edited_timestamp_is_newer($r[0], $datarray)) {
2662 // do not accept (ignore) an earlier edit than one we currently have.
2663 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2666 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2667 dbesc($datarray['title']),
2668 dbesc($datarray['body']),
2669 dbesc($datarray['tag']),
2670 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2671 dbesc(datetime_convert()),
2673 intval($importer['uid'])
2675 create_tags_from_itemuri($item_id, $importer['uid']);
2676 update_thread_uri($item_id, $importer['uid']);
2679 // update last-child if it changes
2681 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2682 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2683 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2684 intval($allow[0]['data']),
2685 dbesc(datetime_convert()),
2687 intval($importer['uid'])
2689 update_thread_uri($item_id, $importer['uid']);
2694 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2695 logger('consume-feed: New follower');
2696 new_follower($importer,$contact,$datarray,$item);
2699 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2700 lose_follower($importer,$contact,$datarray,$item);
2704 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2705 logger('consume-feed: New friend request');
2706 new_follower($importer,$contact,$datarray,$item,true);
2709 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2710 lose_sharer($importer,$contact,$datarray,$item);
2715 if(! is_array($contact))
2719 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2720 // one way feed - no remote comment ability
2721 $datarray['last-child'] = 0;
2723 if($contact['network'] === NETWORK_FEED)
2724 $datarray['private'] = 2;
2726 $datarray['parent-uri'] = $item_id;
2727 $datarray['uid'] = $importer['uid'];
2728 $datarray['contact-id'] = $contact['id'];
2730 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2731 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2732 // but otherwise there's a possible data mixup on the sender's system.
2733 // the tgroup delivery code called from item_store will correct it if it's a forum,
2734 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2735 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2736 $datarray['owner-name'] = $contact['name'];
2737 $datarray['owner-link'] = $contact['url'];
2738 $datarray['owner-avatar'] = $contact['thumb'];
2741 // We've allowed "followers" to reach this point so we can decide if they are
2742 // posting an @-tag delivery, which followers are allowed to do for certain
2743 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2745 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2748 // This is my contact on another system, but it's really me.
2749 // Turn this into a wall post.
2750 $notify = item_is_remote_self($contact, $datarray);
2752 $r = item_store($datarray, false, $notify);
2753 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2761 function item_is_remote_self($contact, &$datarray) {
2764 if (!$contact['remote_self'])
2767 // Prevent the forwarding of posts that are forwarded
2768 if ($datarray["extid"] == NETWORK_DFRN)
2771 // Prevent to forward already forwarded posts
2772 if ($datarray["app"] == $a->get_hostname())
2775 // Only forward posts
2776 if ($datarray["verb"] != ACTIVITY_POST)
2779 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2782 $datarray2 = $datarray;
2783 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2784 if ($contact['remote_self'] == 2) {
2785 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2786 intval($contact['uid']));
2788 $datarray['contact-id'] = $r[0]["id"];
2790 $datarray['owner-name'] = $r[0]["name"];
2791 $datarray['owner-link'] = $r[0]["url"];
2792 $datarray['owner-avatar'] = $r[0]["thumb"];
2794 $datarray['author-name'] = $datarray['owner-name'];
2795 $datarray['author-link'] = $datarray['owner-link'];
2796 $datarray['author-avatar'] = $datarray['owner-avatar'];
2799 if ($contact['network'] != NETWORK_FEED) {
2800 $datarray["guid"] = get_guid(32);
2801 unset($datarray["plink"]);
2802 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2803 $datarray["parent-uri"] = $datarray["uri"];
2804 $datarray["extid"] = $contact['network'];
2805 $urlpart = parse_url($datarray2['author-link']);
2806 $datarray["app"] = $urlpart["host"];
2808 $datarray['private'] = 0;
2811 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2812 // $datarray["app"] = network_to_name($contact['network']);
2814 if ($contact['network'] != NETWORK_FEED) {
2815 // Store the original post
2816 $r = item_store($datarray2, false, false);
2817 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2819 $datarray["app"] = "Feed";
2824 function local_delivery($importer,$data) {
2827 logger(__function__, LOGGER_TRACE);
2829 if($importer['readonly']) {
2830 // We aren't receiving stuff from this person. But we will quietly ignore them
2831 // rather than a blatant "go away" message.
2832 logger('local_delivery: ignoring');
2837 // Consume notification feed. This may differ from consuming a public feed in several ways
2838 // - might contain email or friend suggestions
2839 // - might contain remote followup to our message
2840 // - in which case we need to accept it and then notify other conversants
2841 // - we may need to send various email notifications
2843 $feed = new SimplePie();
2844 $feed->set_raw_data($data);
2845 $feed->enable_order_by_date(false);
2850 logger('local_delivery: Error parsing XML: ' . $feed->error());
2853 // Check at the feed level for updated contact name and/or photo
2857 $photo_timestamp = '';
2859 $contact_updated = '';
2862 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2864 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2866 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2869 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2870 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2871 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2872 $new_name = $elems['name'][0]['data'];
2874 // Manually checking for changed contact names
2875 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2876 $name_updated = date("c");
2877 $photo_timestamp = date("c");
2880 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2881 if ($photo_timestamp == "")
2882 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2883 $photo_url = $elems['link'][0]['attribs']['']['href'];
2887 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2889 $contact_updated = $photo_timestamp;
2891 logger('local_delivery: Updating photo for ' . $importer['name']);
2892 require_once("include/Photo.php");
2893 $photo_failure = false;
2894 $have_photo = false;
2896 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2897 intval($importer['id']),
2898 intval($importer['importer_uid'])
2901 $resource_id = $r[0]['resource-id'];
2905 $resource_id = photo_new_resource();
2908 $img_str = fetch_url($photo_url,true);
2909 // guess mimetype from headers or filename
2910 $type = guess_image_type($photo_url,true);
2913 $img = new Photo($img_str, $type);
2914 if($img->is_valid()) {
2916 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2917 dbesc($resource_id),
2918 intval($importer['id']),
2919 intval($importer['importer_uid'])
2923 $img->scaleImageSquare(175);
2925 $hash = $resource_id;
2926 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2928 $img->scaleImage(80);
2929 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2931 $img->scaleImage(48);
2932 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2936 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2937 WHERE `uid` = %d AND `id` = %d",
2938 dbesc(datetime_convert()),
2939 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2940 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2941 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2942 intval($importer['importer_uid']),
2943 intval($importer['id'])
2948 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
2949 if ($name_updated > $contact_updated)
2950 $contact_updated = $name_updated;
2952 $r = q("select * from contact where uid = %d and id = %d limit 1",
2953 intval($importer['importer_uid']),
2954 intval($importer['id'])
2957 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2958 dbesc(notags(trim($new_name))),
2959 dbesc(datetime_convert()),
2960 intval($importer['importer_uid']),
2961 intval($importer['id'])
2964 // do our best to update the name on content items
2967 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2968 dbesc(notags(trim($new_name))),
2969 dbesc($r[0]['name']),
2970 dbesc($r[0]['url']),
2971 intval($importer['importer_uid'])
2976 if ($contact_updated AND $new_name AND $photo_url)
2977 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
2979 // Currently unsupported - needs a lot of work
2980 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
2981 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
2982 $base = $reloc[0]['child'][NAMESPACE_DFRN];
2984 $newloc['uid'] = $importer['importer_uid'];
2985 $newloc['cid'] = $importer['id'];
2986 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
2987 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
2988 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
2989 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
2990 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
2991 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
2992 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
2993 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
2994 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
2995 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
2996 /** relocated user must have original key pair */
2997 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
2998 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3000 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3003 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3004 intval($importer['id']),
3005 intval($importer['importer_uid']));
3010 $x = q("UPDATE contact SET
3021 `site-pubkey` = '%s'
3022 WHERE id=%d AND uid=%d;",
3023 dbesc($newloc['name']),
3024 dbesc($newloc['photo']),
3025 dbesc($newloc['thumb']),
3026 dbesc($newloc['micro']),
3027 dbesc($newloc['url']),
3028 dbesc(normalise_link($newloc['url'])),
3029 dbesc($newloc['request']),
3030 dbesc($newloc['confirm']),
3031 dbesc($newloc['notify']),
3032 dbesc($newloc['poll']),
3033 dbesc($newloc['sitepubkey']),
3034 intval($importer['id']),
3035 intval($importer['importer_uid']));
3041 'owner-link' => array($old['url'], $newloc['url']),
3042 'author-link' => array($old['url'], $newloc['url']),
3043 'owner-avatar' => array($old['photo'], $newloc['photo']),
3044 'author-avatar' => array($old['photo'], $newloc['photo']),
3046 foreach ($fields as $n=>$f){
3047 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3050 intval($importer['importer_uid']));
3056 // merge with current record, current contents have priority
3057 // update record, set url-updated
3058 // update profile photos
3064 // handle friend suggestion notification
3066 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3067 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3068 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3070 $fsugg['uid'] = $importer['importer_uid'];
3071 $fsugg['cid'] = $importer['id'];
3072 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3073 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3074 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3075 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3076 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3078 // Does our member already have a friend matching this description?
3080 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3081 dbesc($fsugg['name']),
3082 dbesc(normalise_link($fsugg['url'])),
3083 intval($fsugg['uid'])
3088 // Do we already have an fcontact record for this person?
3091 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3092 dbesc($fsugg['url']),
3093 dbesc($fsugg['name']),
3094 dbesc($fsugg['request'])
3099 // OK, we do. Do we already have an introduction for this person ?
3100 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3101 intval($fsugg['uid']),
3108 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3109 dbesc($fsugg['name']),
3110 dbesc($fsugg['url']),
3111 dbesc($fsugg['photo']),
3112 dbesc($fsugg['request'])
3114 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3115 dbesc($fsugg['url']),
3116 dbesc($fsugg['name']),
3117 dbesc($fsugg['request'])
3122 // database record did not get created. Quietly give up.
3127 $hash = random_string();
3129 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3130 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3131 intval($fsugg['uid']),
3133 intval($fsugg['cid']),
3134 dbesc($fsugg['body']),
3136 dbesc(datetime_convert()),
3141 'type' => NOTIFY_SUGGEST,
3142 'notify_flags' => $importer['notify-flags'],
3143 'language' => $importer['language'],
3144 'to_name' => $importer['username'],
3145 'to_email' => $importer['email'],
3146 'uid' => $importer['importer_uid'],
3148 'link' => $a->get_baseurl() . '/notifications/intros',
3149 'source_name' => $importer['name'],
3150 'source_link' => $importer['url'],
3151 'source_photo' => $importer['photo'],
3152 'verb' => ACTIVITY_REQ_FRIEND,
3161 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3162 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3164 logger('local_delivery: private message received');
3167 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3170 $msg['uid'] = $importer['importer_uid'];
3171 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3172 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3173 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3174 $msg['contact-id'] = $importer['id'];
3175 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3176 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3178 $msg['replied'] = 0;
3179 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3180 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3181 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3185 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3186 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3188 // send notifications.
3190 require_once('include/enotify.php');
3192 $notif_params = array(
3193 'type' => NOTIFY_MAIL,
3194 'notify_flags' => $importer['notify-flags'],
3195 'language' => $importer['language'],
3196 'to_name' => $importer['username'],
3197 'to_email' => $importer['email'],
3198 'uid' => $importer['importer_uid'],
3200 'source_name' => $msg['from-name'],
3201 'source_link' => $importer['url'],
3202 'source_photo' => $importer['thumb'],
3203 'verb' => ACTIVITY_POST,
3207 notification($notif_params);
3213 $community_page = 0;
3214 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3216 $community_page = intval($rawtags[0]['data']);
3218 if(intval($importer['forum']) != $community_page) {
3219 q("update contact set forum = %d where id = %d",
3220 intval($community_page),
3221 intval($importer['id'])
3223 $importer['forum'] = (string) $community_page;
3226 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3228 // process any deleted entries
3230 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3231 if(is_array($del_entries) && count($del_entries)) {
3232 foreach($del_entries as $dentry) {
3234 if(isset($dentry['attribs']['']['ref'])) {
3235 $uri = $dentry['attribs']['']['ref'];
3237 if(isset($dentry['attribs']['']['when'])) {
3238 $when = $dentry['attribs']['']['when'];
3239 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3242 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3246 // check for relayed deletes to our conversation
3249 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3251 intval($importer['importer_uid'])
3254 $parent_uri = $r[0]['parent-uri'];
3255 if($r[0]['id'] != $r[0]['parent'])
3262 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3265 logger('local_delivery: possible community delete');
3268 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3270 // was the top-level post for this reply written by somebody on this site?
3271 // Specifically, the recipient?
3273 $is_a_remote_delete = false;
3275 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3276 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3277 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3278 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3279 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3280 AND `item`.`uid` = %d
3286 intval($importer['importer_uid'])
3289 $is_a_remote_delete = true;
3291 // Does this have the characteristics of a community or private group comment?
3292 // If it's a reply to a wall post on a community/prvgroup page it's a
3293 // valid community comment. Also forum_mode makes it valid for sure.
3294 // If neither, it's not.
3296 if($is_a_remote_delete && $community) {
3297 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3298 $is_a_remote_delete = false;
3299 logger('local_delivery: not a community delete');
3303 if($is_a_remote_delete) {
3304 logger('local_delivery: received remote delete');
3308 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3309 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3311 intval($importer['importer_uid']),
3312 intval($importer['id'])
3318 if($item['deleted'])
3321 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3323 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3324 $xo = parse_xml_string($item['object'],false);
3325 $xt = parse_xml_string($item['target'],false);
3327 if($xt->type === ACTIVITY_OBJ_NOTE) {
3328 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3330 intval($importer['importer_uid'])
3334 // For tags, the owner cannot remove the tag on the author's copy of the post.
3336 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3337 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3338 $author_copy = (($item['origin']) ? true : false);
3340 if($owner_remove && $author_copy)
3342 if($author_remove || $owner_remove) {
3343 $tags = explode(',',$i[0]['tag']);
3346 foreach($tags as $tag)
3347 if(trim($tag) !== trim($xo->body))
3348 $newtags[] = trim($tag);
3350 q("update item set tag = '%s' where id = %d",
3351 dbesc(implode(',',$newtags)),
3354 create_tags_from_item($i[0]['id']);
3360 if($item['uri'] == $item['parent-uri']) {
3361 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3362 `body` = '', `title` = ''
3363 WHERE `parent-uri` = '%s' AND `uid` = %d",
3365 dbesc(datetime_convert()),
3366 dbesc($item['uri']),
3367 intval($importer['importer_uid'])
3369 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3370 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3371 update_thread_uri($item['uri'], $importer['importer_uid']);
3374 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3375 `body` = '', `title` = ''
3376 WHERE `uri` = '%s' AND `uid` = %d",
3378 dbesc(datetime_convert()),
3380 intval($importer['importer_uid'])
3382 create_tags_from_itemuri($uri, $importer['importer_uid']);
3383 create_files_from_itemuri($uri, $importer['importer_uid']);
3384 update_thread_uri($uri, $importer['importer_uid']);
3385 if($item['last-child']) {
3386 // ensure that last-child is set in case the comment that had it just got wiped.
3387 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3388 dbesc(datetime_convert()),
3389 dbesc($item['parent-uri']),
3390 intval($item['uid'])
3392 // who is the last child now?
3393 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3394 ORDER BY `created` DESC LIMIT 1",
3395 dbesc($item['parent-uri']),
3396 intval($importer['importer_uid'])
3399 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3404 // if this is a relayed delete, propagate it to other recipients
3406 if($is_a_remote_delete)
3407 proc_run('php',"include/notifier.php","drop",$item['id']);
3415 foreach($feed->get_items() as $item) {
3418 $item_id = $item->get_id();
3419 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3420 if(isset($rawthread[0]['attribs']['']['ref'])) {
3422 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3428 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3431 logger('local_delivery: possible community reply');
3434 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3436 // was the top-level post for this reply written by somebody on this site?
3437 // Specifically, the recipient?
3439 $is_a_remote_comment = false;
3440 $top_uri = $parent_uri;
3442 $r = q("select `item`.`parent-uri` from `item`
3443 WHERE `item`.`uri` = '%s'
3447 if($r && count($r)) {
3448 $top_uri = $r[0]['parent-uri'];
3450 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3451 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3452 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3453 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3454 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3455 AND `item`.`uid` = %d
3461 intval($importer['importer_uid'])
3464 $is_a_remote_comment = true;
3467 // Does this have the characteristics of a community or private group comment?
3468 // If it's a reply to a wall post on a community/prvgroup page it's a
3469 // valid community comment. Also forum_mode makes it valid for sure.
3470 // If neither, it's not.
3472 if($is_a_remote_comment && $community) {
3473 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3474 $is_a_remote_comment = false;
3475 logger('local_delivery: not a community reply');
3479 if($is_a_remote_comment) {
3480 logger('local_delivery: received remote comment');
3482 // remote reply to our post. Import and then notify everybody else.
3484 $datarray = get_atom_elements($feed, $item);
3486 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3488 intval($importer['importer_uid'])
3491 // Update content if 'updated' changes
3495 if (edited_timestamp_is_newer($r[0], $datarray)) {
3497 // do not accept (ignore) an earlier edit than one we currently have.
3498 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3501 logger('received updated comment' , LOGGER_DEBUG);
3502 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3503 dbesc($datarray['title']),
3504 dbesc($datarray['body']),
3505 dbesc($datarray['tag']),
3506 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3507 dbesc(datetime_convert()),
3509 intval($importer['importer_uid'])
3511 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3513 proc_run('php',"include/notifier.php","comment-import",$iid);
3522 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3523 intval($importer['importer_uid'])
3527 $datarray['type'] = 'remote-comment';
3528 $datarray['wall'] = 1;
3529 $datarray['parent-uri'] = $parent_uri;
3530 $datarray['uid'] = $importer['importer_uid'];
3531 $datarray['owner-name'] = $own[0]['name'];
3532 $datarray['owner-link'] = $own[0]['url'];
3533 $datarray['owner-avatar'] = $own[0]['thumb'];
3534 $datarray['contact-id'] = $importer['id'];
3536 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3538 $datarray['type'] = 'activity';
3539 $datarray['gravity'] = GRAVITY_LIKE;
3540 $datarray['last-child'] = 0;
3541 // only one like or dislike per person
3542 // splitted into two queries for performance issues
3543 $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",
3544 intval($datarray['uid']),
3545 intval($datarray['contact-id']),
3546 dbesc($datarray['verb']),
3547 dbesc($datarray['parent-uri'])
3553 $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",
3554 intval($datarray['uid']),
3555 intval($datarray['contact-id']),
3556 dbesc($datarray['verb']),
3557 dbesc($datarray['parent-uri'])
3564 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3566 $xo = parse_xml_string($datarray['object'],false);
3567 $xt = parse_xml_string($datarray['target'],false);
3569 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3571 // fetch the parent item
3573 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3575 intval($importer['importer_uid'])
3580 // extract tag, if not duplicate, and this user allows tags, add to parent item
3582 if($xo->id && $xo->content) {
3583 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3584 if(! (stristr($tagp[0]['tag'],$newtag))) {
3585 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3586 intval($importer['importer_uid'])
3588 if(count($i) && ! intval($i[0]['blocktags'])) {
3589 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3590 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3591 intval($tagp[0]['id']),
3592 dbesc(datetime_convert()),
3593 dbesc(datetime_convert())
3595 create_tags_from_item($tagp[0]['id']);
3603 $posted_id = item_store($datarray);
3607 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3609 intval($importer['importer_uid'])
3612 $parent = $r[0]['parent'];
3613 $parent_uri = $r[0]['parent-uri'];
3617 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3618 dbesc(datetime_convert()),
3619 intval($importer['importer_uid']),
3620 intval($r[0]['parent'])
3623 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3624 dbesc(datetime_convert()),
3625 intval($importer['importer_uid']),
3630 if($posted_id && $parent) {
3632 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3634 if((! $is_like) && (! $importer['self'])) {
3636 require_once('include/enotify.php');
3639 'type' => NOTIFY_COMMENT,
3640 'notify_flags' => $importer['notify-flags'],
3641 'language' => $importer['language'],
3642 'to_name' => $importer['username'],
3643 'to_email' => $importer['email'],
3644 'uid' => $importer['importer_uid'],
3645 'item' => $datarray,
3646 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3647 'source_name' => stripslashes($datarray['author-name']),
3648 'source_link' => $datarray['author-link'],
3649 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3650 ? $importer['thumb'] : $datarray['author-avatar']),
3651 'verb' => ACTIVITY_POST,
3653 'parent' => $parent,
3654 'parent_uri' => $parent_uri,
3666 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3668 $item_id = $item->get_id();
3669 $datarray = get_atom_elements($feed,$item);
3671 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3674 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3676 intval($importer['importer_uid'])
3679 // Update content if 'updated' changes
3682 if (edited_timestamp_is_newer($r[0], $datarray)) {
3684 // do not accept (ignore) an earlier edit than one we currently have.
3685 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3688 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3689 dbesc($datarray['title']),
3690 dbesc($datarray['body']),
3691 dbesc($datarray['tag']),
3692 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3693 dbesc(datetime_convert()),
3695 intval($importer['importer_uid'])
3697 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3700 // update last-child if it changes
3702 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3703 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3704 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3705 dbesc(datetime_convert()),
3707 intval($importer['importer_uid'])
3709 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3710 intval($allow[0]['data']),
3711 dbesc(datetime_convert()),
3713 intval($importer['importer_uid'])
3719 $datarray['parent-uri'] = $parent_uri;
3720 $datarray['uid'] = $importer['importer_uid'];
3721 $datarray['contact-id'] = $importer['id'];
3722 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3723 $datarray['type'] = 'activity';
3724 $datarray['gravity'] = GRAVITY_LIKE;
3725 // only one like or dislike per person
3726 // splitted into two queries for performance issues
3727 $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",
3728 intval($datarray['uid']),
3729 intval($datarray['contact-id']),
3730 dbesc($datarray['verb']),
3736 $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",
3737 intval($datarray['uid']),
3738 intval($datarray['contact-id']),
3739 dbesc($datarray['verb']),
3747 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3749 $xo = parse_xml_string($datarray['object'],false);
3750 $xt = parse_xml_string($datarray['target'],false);
3752 if($xt->type == ACTIVITY_OBJ_NOTE) {
3753 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3755 intval($importer['importer_uid'])
3760 // extract tag, if not duplicate, add to parent item
3762 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3763 q("UPDATE item SET tag = '%s' WHERE id = %d",
3764 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3767 create_tags_from_item($r[0]['id']);
3773 $posted_id = item_store($datarray);
3775 // find out if our user is involved in this conversation and wants to be notified.
3777 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3779 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3781 intval($importer['importer_uid'])
3784 if(count($myconv)) {
3785 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3787 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3788 if(! link_compare($datarray['author-link'],$importer_url)) {
3791 foreach($myconv as $conv) {
3793 // now if we find a match, it means we're in this conversation
3795 if(! link_compare($conv['author-link'],$importer_url))
3798 require_once('include/enotify.php');
3800 $conv_parent = $conv['parent'];
3803 'type' => NOTIFY_COMMENT,
3804 'notify_flags' => $importer['notify-flags'],
3805 'language' => $importer['language'],
3806 'to_name' => $importer['username'],
3807 'to_email' => $importer['email'],
3808 'uid' => $importer['importer_uid'],
3809 'item' => $datarray,
3810 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3811 'source_name' => stripslashes($datarray['author-name']),
3812 'source_link' => $datarray['author-link'],
3813 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3814 ? $importer['thumb'] : $datarray['author-avatar']),
3815 'verb' => ACTIVITY_POST,
3817 'parent' => $conv_parent,
3818 'parent_uri' => $parent_uri
3822 // only send one notification
3834 // Head post of a conversation. Have we seen it? If not, import it.
3837 $item_id = $item->get_id();
3838 $datarray = get_atom_elements($feed,$item);
3840 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3841 $ev = bbtoevent($datarray['body']);
3842 if(x($ev,'desc') && x($ev,'start')) {
3843 $ev['cid'] = $importer['id'];
3844 $ev['uid'] = $importer['uid'];
3845 $ev['uri'] = $item_id;
3846 $ev['edited'] = $datarray['edited'];
3847 $ev['private'] = $datarray['private'];
3849 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3851 intval($importer['uid'])
3854 $ev['id'] = $r[0]['id'];
3855 $xyz = event_store($ev);
3860 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3862 intval($importer['importer_uid'])
3865 // Update content if 'updated' changes
3868 if (edited_timestamp_is_newer($r[0], $datarray)) {
3870 // do not accept (ignore) an earlier edit than one we currently have.
3871 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3874 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3875 dbesc($datarray['title']),
3876 dbesc($datarray['body']),
3877 dbesc($datarray['tag']),
3878 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3879 dbesc(datetime_convert()),
3881 intval($importer['importer_uid'])
3883 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3884 update_thread_uri($item_id, $importer['importer_uid']);
3887 // update last-child if it changes
3889 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3890 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3891 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3892 intval($allow[0]['data']),
3893 dbesc(datetime_convert()),
3895 intval($importer['importer_uid'])
3901 $datarray['parent-uri'] = $item_id;
3902 $datarray['uid'] = $importer['importer_uid'];
3903 $datarray['contact-id'] = $importer['id'];
3906 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3907 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3908 // but otherwise there's a possible data mixup on the sender's system.
3909 // the tgroup delivery code called from item_store will correct it if it's a forum,
3910 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3911 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3912 $datarray['owner-name'] = $importer['senderName'];
3913 $datarray['owner-link'] = $importer['url'];
3914 $datarray['owner-avatar'] = $importer['thumb'];
3917 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3920 // This is my contact on another system, but it's really me.
3921 // Turn this into a wall post.
3922 $notify = item_is_remote_self($importer, $datarray);
3924 $posted_id = item_store($datarray, false, $notify);
3926 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3927 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
3930 $xo = parse_xml_string($datarray['object'],false);
3932 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
3934 // somebody was poked/prodded. Was it me?
3936 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
3938 foreach($links->link as $l) {
3939 $atts = $l->attributes();
3940 switch($atts['rel']) {
3942 $Blink = $atts['href'];
3948 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
3950 // send a notification
3951 require_once('include/enotify.php');
3954 'type' => NOTIFY_POKE,
3955 'notify_flags' => $importer['notify-flags'],
3956 'language' => $importer['language'],
3957 'to_name' => $importer['username'],
3958 'to_email' => $importer['email'],
3959 'uid' => $importer['importer_uid'],
3960 'item' => $datarray,
3961 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3962 'source_name' => stripslashes($datarray['author-name']),
3963 'source_link' => $datarray['author-link'],
3964 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3965 ? $importer['thumb'] : $datarray['author-avatar']),
3966 'verb' => $datarray['verb'],
3967 'otype' => 'person',
3968 'activity' => $verb,
3985 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
3986 $url = notags(trim($datarray['author-link']));
3987 $name = notags(trim($datarray['author-name']));
3988 $photo = notags(trim($datarray['author-avatar']));
3990 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
3991 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
3992 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
3994 if(is_array($contact)) {
3995 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
3996 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
3997 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
3998 intval(CONTACT_IS_FRIEND),
3999 intval($contact['id']),
4000 intval($importer['uid'])
4003 // send email notification to owner?
4007 // create contact record
4009 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4010 `blocked`, `readonly`, `pending`, `writable` )
4011 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4012 intval($importer['uid']),
4013 dbesc(datetime_convert()),
4015 dbesc(normalise_link($url)),
4019 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4020 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4022 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4023 intval($importer['uid']),
4027 $contact_record = $r[0];
4029 // create notification
4030 $hash = random_string();
4032 if(is_array($contact_record)) {
4033 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4034 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4035 intval($importer['uid']),
4036 intval($contact_record['id']),
4038 dbesc(datetime_convert())
4042 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4043 intval($importer['uid'])
4048 if(intval($r[0]['def_gid'])) {
4049 require_once('include/group.php');
4050 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4053 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4054 (($r[0]['page-flags'] == PAGE_NORMAL) OR ($r[0]['page-flags'] == PAGE_SOAPBOX))) {
4059 'type' => NOTIFY_INTRO,
4060 'notify_flags' => $r[0]['notify-flags'],
4061 'language' => $r[0]['language'],
4062 'to_name' => $r[0]['username'],
4063 'to_email' => $r[0]['email'],
4064 'uid' => $r[0]['uid'],
4065 'link' => $a->get_baseurl() . '/notifications/intro',
4066 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4067 'source_link' => $contact_record['url'],
4068 'source_photo' => $contact_record['photo'],
4069 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4079 function lose_follower($importer,$contact,$datarray,$item) {
4081 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4082 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4083 intval(CONTACT_IS_SHARING),
4084 intval($contact['id'])
4088 contact_remove($contact['id']);
4092 function lose_sharer($importer,$contact,$datarray,$item) {
4094 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4095 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4096 intval(CONTACT_IS_FOLLOWER),
4097 intval($contact['id'])
4101 contact_remove($contact['id']);
4106 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4110 if(is_array($importer)) {
4111 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4112 intval($importer['uid'])
4116 // Diaspora has different message-ids in feeds than they do
4117 // through the direct Diaspora protocol. If we try and use
4118 // the feed, we'll get duplicates. So don't.
4120 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4123 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4125 // Use a single verify token, even if multiple hubs
4127 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4129 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4131 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4133 if(! strlen($contact['hub-verify'])) {
4134 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4135 dbesc($verify_token),
4136 intval($contact['id'])
4140 post_url($url,$params);
4142 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4149 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4153 $name = xmlify($name);
4154 $uri = xmlify($uri);
4157 $photo = xmlify($photo);
4161 $o .= "<name>$name</name>\r\n";
4162 $o .= "<uri>$uri</uri>\r\n";
4163 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4164 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4166 call_hooks('atom_author', $o);
4168 $o .= "</$tag>\r\n";
4172 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4176 if(! $item['parent'])
4179 if($item['deleted'])
4180 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4183 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4184 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4186 $body = $item['body'];
4189 $o = "\r\n\r\n<entry>\r\n";
4191 if(is_array($author))
4192 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4194 $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']));
4195 if(strlen($item['owner-name']))
4196 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4198 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4199 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4200 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4205 if ($item['title'] != "")
4206 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4208 $htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4210 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4211 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4212 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4213 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4214 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4215 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4216 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4220 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4222 if($item['location']) {
4223 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4224 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4228 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4230 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4231 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4234 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4235 if($item['bookmark'])
4236 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4239 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4242 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4244 if($item['signed_text']) {
4245 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4246 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4249 $verb = construct_verb($item);
4250 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4251 $actobj = construct_activity_object($item);
4254 $actarg = construct_activity_target($item);
4258 $tags = item_getfeedtags($item);
4260 foreach($tags as $t) {
4261 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4265 $o .= item_getfeedattach($item);
4267 $mentioned = get_mentions($item);
4271 call_hooks('atom_entry', $o);
4273 $o .= '</entry>' . "\r\n";
4278 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4280 if(get_config('system','disable_embedded'))
4285 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4286 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4291 $img_start = strpos($orig_body, '[img');
4292 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4293 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4294 while( ($img_st_close !== false) && ($img_len !== false) ) {
4296 $img_st_close++; // make it point to AFTER the closing bracket
4297 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4299 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4302 if(stristr($image , $site . '/photo/')) {
4303 // Only embed locally hosted photos
4305 $i = basename($image);
4306 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4307 $x = strpos($i,'-');
4310 $res = substr($i,$x+1);
4311 $i = substr($i,0,$x);
4312 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4319 // Check to see if we should replace this photo link with an embedded image
4320 // 1. No need to do so if the photo is public
4321 // 2. If there's a contact-id provided, see if they're in the access list
4322 // for the photo. If so, embed it.
4323 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4324 // permissions, regardless of order but first check to see if they're an exact
4325 // match to save some processing overhead.
4327 if(has_permissions($r[0])) {
4329 $recips = enumerate_permissions($r[0]);
4330 if(in_array($cid, $recips)) {
4335 if(compare_permissions($item,$r[0]))
4340 $data = $r[0]['data'];
4341 $type = $r[0]['type'];
4343 // If a custom width and height were specified, apply before embedding
4344 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4345 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4347 $width = intval($match[1]);
4348 $height = intval($match[2]);
4350 $ph = new Photo($data, $type);
4351 if($ph->is_valid()) {
4352 $ph->scaleImage(max($width, $height));
4353 $data = $ph->imageString();
4354 $type = $ph->getType();
4358 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4359 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4360 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4366 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4367 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4368 if($orig_body === false)
4371 $img_start = strpos($orig_body, '[img');
4372 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4373 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4376 $new_body = $new_body . $orig_body;
4382 function has_permissions($obj) {
4383 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4388 function compare_permissions($obj1,$obj2) {
4389 // first part is easy. Check that these are exactly the same.
4390 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4391 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4392 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4393 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4396 // This is harder. Parse all the permissions and compare the resulting set.
4398 $recipients1 = enumerate_permissions($obj1);
4399 $recipients2 = enumerate_permissions($obj2);
4402 if($recipients1 == $recipients2)
4407 // returns an array of contact-ids that are allowed to see this object
4409 function enumerate_permissions($obj) {
4410 require_once('include/group.php');
4411 $allow_people = expand_acl($obj['allow_cid']);
4412 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4413 $deny_people = expand_acl($obj['deny_cid']);
4414 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4415 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4416 $deny = array_unique(array_merge($deny_people,$deny_groups));
4417 $recipients = array_diff($recipients,$deny);
4421 function item_getfeedtags($item) {
4424 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4426 for($x = 0; $x < $cnt; $x ++) {
4428 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4432 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4434 for($x = 0; $x < $cnt; $x ++) {
4436 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4442 function item_getfeedattach($item) {
4444 $arr = explode('[/attach],',$item['attach']);
4446 foreach($arr as $r) {
4448 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4450 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4451 if(intval($matches[2]))
4452 $ret .= 'length="' . intval($matches[2]) . '" ';
4453 if($matches[4] !== ' ')
4454 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4455 $ret .= ' />' . "\r\n";
4464 function item_expire($uid, $days, $network = "", $force = false) {
4466 if((! $uid) || ($days < 1))
4469 // $expire_network_only = save your own wall posts
4470 // and just expire conversations started by others
4472 $expire_network_only = get_pconfig($uid,'expire','network_only');
4473 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4475 if ($network != "") {
4476 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4477 // There is an index "uid_network_received" but not "uid_network_created"
4478 // This avoids the creation of another index just for one purpose.
4479 // And it doesn't really matter wether to look at "received" or "created"
4480 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4482 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4484 $r = q("SELECT * FROM `item`
4485 WHERE `uid` = %d $range
4496 $expire_items = get_pconfig($uid, 'expire','items');
4497 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4499 // Forcing expiring of items - but not notes and marked items
4501 $expire_items = true;
4503 $expire_notes = get_pconfig($uid, 'expire','notes');
4504 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4506 $expire_starred = get_pconfig($uid, 'expire','starred');
4507 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4509 $expire_photos = get_pconfig($uid, 'expire','photos');
4510 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4512 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4514 foreach($r as $item) {
4516 // don't expire filed items
4518 if(strpos($item['file'],'[') !== false)
4521 // Only expire posts, not photos and photo comments
4523 if($expire_photos==0 && strlen($item['resource-id']))
4525 if($expire_starred==0 && intval($item['starred']))
4527 if($expire_notes==0 && $item['type']=='note')
4529 if($expire_items==0 && $item['type']!='note')
4532 drop_item($item['id'],false);
4535 proc_run('php',"include/notifier.php","expire","$uid");
4540 function drop_items($items) {
4543 if(! local_user() && ! remote_user())
4547 foreach($items as $item) {
4548 $owner = drop_item($item,false);
4549 if($owner && ! $uid)
4554 // multiple threads may have been deleted, send an expire notification
4557 proc_run('php',"include/notifier.php","expire","$uid");
4561 function drop_item($id,$interactive = true) {
4565 // locate item to be deleted
4567 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4574 notice( t('Item not found.') . EOL);
4575 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4580 $owner = $item['uid'];
4584 // check if logged in user is either the author or owner of this item
4586 if(is_array($_SESSION['remote'])) {
4587 foreach($_SESSION['remote'] as $visitor) {
4588 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4589 $cid = $visitor['cid'];
4596 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4598 // Check if we should do HTML-based delete confirmation
4599 if($_REQUEST['confirm']) {
4600 // <form> can't take arguments in its "action" parameter
4601 // so add any arguments as hidden inputs
4602 $query = explode_querystring($a->query_string);
4604 foreach($query['args'] as $arg) {
4605 if(strpos($arg, 'confirm=') === false) {
4606 $arg_parts = explode('=', $arg);
4607 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4611 return replace_macros(get_markup_template('confirm.tpl'), array(
4613 '$message' => t('Do you really want to delete this item?'),
4614 '$extra_inputs' => $inputs,
4615 '$confirm' => t('Yes'),
4616 '$confirm_url' => $query['base'],
4617 '$confirm_name' => 'confirmed',
4618 '$cancel' => t('Cancel'),
4621 // Now check how the user responded to the confirmation query
4622 if($_REQUEST['canceled']) {
4623 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4626 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4629 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4630 dbesc(datetime_convert()),
4631 dbesc(datetime_convert()),
4634 create_tags_from_item($item['id']);
4635 create_files_from_item($item['id']);
4636 delete_thread($item['id'], $item['parent-uri']);
4638 // clean up categories and tags so they don't end up as orphans
4641 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4643 foreach($matches as $mtch) {
4644 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4650 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4652 foreach($matches as $mtch) {
4653 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4657 // If item is a link to a photo resource, nuke all the associated photos
4658 // (visitors will not have photo resources)
4659 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4660 // generate a resource-id and therefore aren't intimately linked to the item.
4662 if(strlen($item['resource-id'])) {
4663 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4664 dbesc($item['resource-id']),
4665 intval($item['uid'])
4667 // ignore the result
4670 // If item is a link to an event, nuke the event record.
4672 if(intval($item['event-id'])) {
4673 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4674 intval($item['event-id']),
4675 intval($item['uid'])
4677 // ignore the result
4680 // clean up item_id and sign meta-data tables
4683 // Old code - caused very long queries and warning entries in the mysql logfiles:
4685 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4686 intval($item['id']),
4687 intval($item['uid'])
4690 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4691 intval($item['id']),
4692 intval($item['uid'])
4696 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4698 // Creating list of parents
4699 $r = q("select id from item where parent = %d and uid = %d",
4700 intval($item['id']),
4701 intval($item['uid'])
4706 foreach ($r AS $row) {
4707 if ($parentid != "")
4710 $parentid .= $row["id"];
4714 if ($parentid != "") {
4715 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4717 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4720 // If it's the parent of a comment thread, kill all the kids
4722 if($item['uri'] == $item['parent-uri']) {
4723 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4724 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4725 dbesc(datetime_convert()),
4726 dbesc(datetime_convert()),
4727 dbesc($item['parent-uri']),
4728 intval($item['uid'])
4730 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4731 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4732 delete_thread_uri($item['parent-uri'], $item['uid']);
4733 // ignore the result
4736 // ensure that last-child is set in case the comment that had it just got wiped.
4737 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4738 dbesc(datetime_convert()),
4739 dbesc($item['parent-uri']),
4740 intval($item['uid'])
4742 // who is the last child now?
4743 $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",
4744 dbesc($item['parent-uri']),
4745 intval($item['uid'])
4748 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4753 // Add a relayable_retraction signature for Diaspora.
4754 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4756 $drop_id = intval($item['id']);
4758 // send the notification upstream/downstream as the case may be
4760 proc_run('php',"include/notifier.php","drop","$drop_id");
4764 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4770 notice( t('Permission denied.') . EOL);
4771 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4778 function first_post_date($uid,$wall = false) {
4779 $r = q("select id, created from item
4780 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4782 order by created asc limit 1",
4784 intval($wall ? 1 : 0)
4787 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4788 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4793 function posted_dates($uid,$wall) {
4794 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4796 $dthen = first_post_date($uid,$wall);
4800 // Set the start and end date to the beginning of the month
4801 $dnow = substr($dnow,0,8).'01';
4802 $dthen = substr($dthen,0,8).'01';
4805 // Starting with the current month, get the first and last days of every
4806 // month down to and including the month of the first post
4807 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4808 $dstart = substr($dnow,0,8) . '01';
4809 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4810 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4811 $end_month = datetime_convert('','',$dend,'Y-m-d');
4812 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4813 $ret[] = array($str,$end_month,$start_month);
4814 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4820 function posted_date_widget($url,$uid,$wall) {
4823 if(! feature_enabled($uid,'archives'))
4826 // For former Facebook folks that left because of "timeline"
4828 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4831 $ret = posted_dates($uid,$wall);
4835 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4836 '$title' => t('Archives'),
4837 '$size' => ((count($ret) > 6) ? 6 : count($ret)),
4844 function store_diaspora_retract_sig($item, $user, $baseurl) {
4845 // Note that we can't add a target_author_signature
4846 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4847 // the comment, that means we're the home of the post, and Diaspora will only
4848 // check the parent_author_signature of retractions that it doesn't have to relay further
4850 // I don't think this function gets called for an "unlike," but I'll check anyway
4852 $enabled = intval(get_config('system','diaspora_enabled'));
4854 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4858 logger('drop_item: storing diaspora retraction signature');
4860 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4862 if(local_user() == $item['uid']) {
4864 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4865 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4868 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4869 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4872 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4873 // only handles DFRN deletes
4874 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4875 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
4876 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
4882 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
4883 intval($item['id']),
4884 dbesc($signed_text),