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');
15 require_once('mod/share.php');
17 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0) {
20 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
21 $public_feed = (($dfrn_id) ? false : true);
22 $starred = false; // not yet implemented, possible security issues
25 if($public_feed && $a->argc > 2) {
26 for($x = 2; $x < $a->argc; $x++) {
27 if($a->argv[$x] == 'converse')
29 if($a->argv[$x] == 'starred')
31 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
32 $category = $a->argv[$x+1];
38 // default permissions - anonymous user
40 $sql_extra = " AND `allow_cid` = '' AND `allow_gid` = '' AND `deny_cid` = '' AND `deny_gid` = '' ";
42 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
43 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
44 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
52 $owner_id = $owner['user_uid'];
53 $owner_nick = $owner['nickname'];
55 $birthday = feed_birthday($owner_id,$owner['timezone']);
64 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
68 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
69 $my_id = '1:' . $dfrn_id;
72 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
73 $my_id = '0:' . $dfrn_id;
80 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
88 require_once('include/security.php');
89 $groups = init_groups_visitor($contact['id']);
92 for($x = 0; $x < count($groups); $x ++)
93 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
94 $gs = implode('|', $groups);
97 $gs = '<<>>' ; // Impossible to match
99 $sql_extra = sprintf("
100 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
101 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
102 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
103 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
105 intval($contact['id']),
106 intval($contact['id']),
117 if(! strlen($last_update))
118 $last_update = 'now -30 days';
120 if(isset($category)) {
121 $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` ",
122 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
123 //$sql_extra .= file_tag_file_query('item',$category,'category');
128 $sql_extra .= " AND `contact`.`self` = 1 ";
131 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
133 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
134 // dbesc($check_date),
136 $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
137 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
138 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
139 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
140 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
141 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
142 FROM `item` $sql_post_table
143 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
144 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
145 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
146 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
147 AND `item`.`wall` = 1 AND `item`.`changed` > '%s'
149 ORDER BY `parent` %s, `created` ASC LIMIT 0, 300",
155 // Will check further below if this actually returned results.
156 // We will provide an empty feed if that is the case.
160 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
164 $hubxml = feed_hublinks();
166 $salmon = feed_salmonlinks($owner_nick);
168 $alternatelink = $owner['url'];
171 $alternatelink .= "/category/".$category;
173 $atom .= replace_macros($feed_template, array(
174 '$version' => xmlify(FRIENDICA_VERSION),
175 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
176 '$feed_title' => xmlify($owner['name']),
177 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
179 '$salmon' => $salmon,
180 '$alternatelink' => xmlify($alternatelink),
181 '$name' => xmlify($owner['name']),
182 '$profile_page' => xmlify($owner['url']),
183 '$photo' => xmlify($owner['photo']),
184 '$thumb' => xmlify($owner['thumb']),
185 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
186 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
187 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
188 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
189 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
192 call_hooks('atom_feed', $atom);
194 if(! count($items)) {
196 call_hooks('atom_feed_end', $atom);
198 $atom .= '</feed>' . "\r\n";
202 foreach($items as $item) {
204 // prevent private email from leaking.
205 if($item['network'] === NETWORK_MAIL)
208 // public feeds get html, our own nodes use bbcode
212 // catch any email that's in a public conversation and make sure it doesn't leak
220 $atom .= atom_entry($item,$type,null,$owner,true);
223 call_hooks('atom_feed_end', $atom);
225 $atom .= '</feed>' . "\r\n";
231 function construct_verb($item) {
233 return $item['verb'];
234 return ACTIVITY_POST;
237 function construct_activity_object($item) {
239 if($item['object']) {
240 $o = '<as:object>' . "\r\n";
241 $r = parse_xml_string($item['object'],false);
247 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
249 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
251 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
253 if(substr($r->link,0,1) === '<') {
254 // patch up some facebook "like" activity objects that got stored incorrectly
255 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
256 // we can probably remove this hack here and in the following function in a few months time.
257 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
258 $r->link = str_replace('&','&', $r->link);
259 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
263 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
266 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
267 $o .= '</as:object>' . "\r\n";
274 function construct_activity_target($item) {
276 if($item['target']) {
277 $o = '<as:target>' . "\r\n";
278 $r = parse_xml_string($item['target'],false);
282 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
284 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
286 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
288 if(substr($r->link,0,1) === '<') {
289 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
290 $r->link = str_replace('&','&', $r->link);
291 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
295 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
298 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
299 $o .= '</as:target>' . "\r\n";
308 * The purpose of this function is to apply system message length limits to
309 * imported messages without including any embedded photos in the length
311 if(! function_exists('limit_body_size')) {
312 function limit_body_size($body) {
314 // logger('limit_body_size: start', LOGGER_DEBUG);
316 $maxlen = get_max_import_size();
318 // If the length of the body, including the embedded images, is smaller
319 // than the maximum, then don't waste time looking for the images
320 if($maxlen && (strlen($body) > $maxlen)) {
322 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
329 $img_start = strpos($orig_body, '[img');
330 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
331 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
332 while(($img_st_close !== false) && ($img_end !== false)) {
334 $img_st_close++; // make it point to AFTER the closing bracket
335 $img_end += $img_start;
336 $img_end += strlen('[/img]');
338 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
339 // This is an embedded image
341 if( ($textlen + $img_start) > $maxlen ) {
342 if($textlen < $maxlen) {
343 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
344 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
349 $new_body = $new_body . substr($orig_body, 0, $img_start);
350 $textlen += $img_start;
353 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
357 if( ($textlen + $img_end) > $maxlen ) {
358 if($textlen < $maxlen) {
359 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
360 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
365 $new_body = $new_body . substr($orig_body, 0, $img_end);
366 $textlen += $img_end;
369 $orig_body = substr($orig_body, $img_end);
371 if($orig_body === false) // in case the body ends on a closing image tag
374 $img_start = strpos($orig_body, '[img');
375 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
376 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
379 if( ($textlen + strlen($orig_body)) > $maxlen) {
380 if($textlen < $maxlen) {
381 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
382 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
387 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
388 $new_body = $new_body . $orig_body;
389 $textlen += strlen($orig_body);
398 function title_is_body($title, $body) {
400 $title = strip_tags($title);
401 $title = trim($title);
402 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
403 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
405 $body = strip_tags($body);
407 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
408 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
410 if (strlen($title) < strlen($body))
411 $body = substr($body, 0, strlen($title));
413 if (($title != $body) and (substr($title, -3) == "...")) {
414 $pos = strrpos($title, "...");
416 $title = substr($title, 0, $pos);
417 $body = substr($body, 0, $pos);
421 return($title == $body);
426 function get_atom_elements($feed, $item, $contact = array()) {
428 require_once('library/HTMLPurifier.auto.php');
429 require_once('include/html2bbcode.php');
431 $best_photo = array();
435 $author = $item->get_author();
437 $res['author-name'] = unxmlify($author->get_name());
438 $res['author-link'] = unxmlify($author->get_link());
441 $res['author-name'] = unxmlify($feed->get_title());
442 $res['author-link'] = unxmlify($feed->get_permalink());
444 $res['uri'] = unxmlify($item->get_id());
445 $res['title'] = unxmlify($item->get_title());
446 $res['body'] = unxmlify($item->get_content());
447 $res['plink'] = unxmlify($item->get_link(0));
449 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
450 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
452 $res['body'] = nl2br($res['body']);
455 // removing the content of the title if its identically to the body
456 // This helps with auto generated titles e.g. from tumblr
457 if (title_is_body($res["title"], $res["body"]))
461 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
465 // look for a photo. We should check media size and find the best one,
466 // but for now let's just find any author photo
468 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
470 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
471 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
472 foreach($base as $link) {
473 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
474 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
475 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
480 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
482 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
483 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
484 if($base && count($base)) {
485 foreach($base as $link) {
486 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
487 $res['author-link'] = unxmlify($link['attribs']['']['href']);
488 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
489 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
490 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
496 // No photo/profile-link on the item - look at the feed level
498 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
499 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
500 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
501 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
502 foreach($base as $link) {
503 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
504 $res['author-link'] = unxmlify($link['attribs']['']['href']);
505 if(! $res['author-avatar']) {
506 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
507 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
512 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
514 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
515 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
517 if($base && count($base)) {
518 foreach($base as $link) {
519 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
520 $res['author-link'] = unxmlify($link['attribs']['']['href']);
521 if(! (x($res,'author-avatar'))) {
522 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
523 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
530 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
531 if($apps && $apps[0]['attribs']['']['source']) {
532 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
533 if($res['app'] === 'web')
534 $res['app'] = 'OStatus';
537 // base64 encoded json structure representing Diaspora signature
539 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
541 $res['dsprsig'] = unxmlify($dsig[0]['data']);
544 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
546 $res['guid'] = unxmlify($dguid[0]['data']);
548 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
550 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
554 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
557 $have_real_body = false;
559 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
561 $have_real_body = true;
562 $res['body'] = $rawenv[0]['data'];
563 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
564 // make sure nobody is trying to sneak some html tags by us
565 $res['body'] = notags(base64url_decode($res['body']));
569 $res['body'] = limit_body_size($res['body']);
571 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
572 // the content type. Our own network only emits text normally, though it might have been converted to
573 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
574 // have to assume it is all html and needs to be purified.
576 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
577 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
578 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
581 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
583 $res['body'] = reltoabs($res['body'],$base_url);
585 $res['body'] = html2bb_video($res['body']);
587 $res['body'] = oembed_html2bbcode($res['body']);
589 $config = HTMLPurifier_Config::createDefault();
590 $config->set('Cache.DefinitionImpl', null);
592 // we shouldn't need a whitelist, because the bbcode converter
593 // will strip out any unsupported tags.
595 $purifier = new HTMLPurifier($config);
596 $res['body'] = $purifier->purify($res['body']);
598 $res['body'] = @html2bbcode($res['body']);
602 elseif(! $have_real_body) {
604 // it's not one of our messages and it has no tags
605 // so it's probably just text. We'll escape it just to be safe.
607 $res['body'] = escape_tags($res['body']);
611 // this tag is obsolete but we keep it for really old sites
613 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
614 if($allow && $allow[0]['data'] == 1)
615 $res['last-child'] = 1;
617 $res['last-child'] = 0;
619 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
620 if($private && intval($private[0]['data']) > 0)
621 $res['private'] = intval($private[0]['data']);
625 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
626 if($extid && $extid[0]['data'])
627 $res['extid'] = $extid[0]['data'];
629 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
631 $res['location'] = unxmlify($rawlocation[0]['data']);
634 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
636 $res['created'] = unxmlify($rawcreated[0]['data']);
639 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
641 $res['edited'] = unxmlify($rawedited[0]['data']);
643 if((x($res,'edited')) && (! (x($res,'created'))))
644 $res['created'] = $res['edited'];
646 if(! $res['created'])
647 $res['created'] = $item->get_date('c');
650 $res['edited'] = $item->get_date('c');
653 // Disallow time travelling posts
655 $d1 = strtotime($res['created']);
656 $d2 = strtotime($res['edited']);
657 $d3 = strtotime('now');
660 $res['created'] = datetime_convert();
662 $res['edited'] = datetime_convert();
664 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
665 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
666 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
667 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
668 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
669 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
670 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
671 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
672 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
674 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
675 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
677 foreach($base as $link) {
678 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
679 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
680 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
685 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
687 $res['coord'] = unxmlify($rawgeo[0]['data']);
689 if ($contact["network"] == NETWORK_FEED) {
690 $res['verb'] = ACTIVITY_POST;
691 $res['object-type'] = ACTIVITY_OBJ_NOTE;
694 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
696 // select between supported verbs
699 $res['verb'] = unxmlify($rawverb[0]['data']);
702 // translate OStatus unfollow to activity streams if it happened to get selected
704 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
705 $res['verb'] = ACTIVITY_UNFOLLOW;
707 $cats = $item->get_categories();
710 foreach($cats as $cat) {
711 $term = $cat->get_term();
713 $term = $cat->get_label();
714 $scheme = $cat->get_scheme();
715 if($scheme && $term && stristr($scheme,'X-DFRN:'))
716 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
718 $tag_arr[] = notags(trim($term));
720 $res['tag'] = implode(',', $tag_arr);
723 $attach = $item->get_enclosures();
726 foreach($attach as $att) {
727 $len = intval($att->get_length());
728 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
729 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
730 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
731 if(strpos($type,';'))
732 $type = substr($type,0,strpos($type,';'));
733 if((! $link) || (strpos($link,'http') !== 0))
739 $type = 'application/octet-stream';
741 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
743 $res['attach'] = implode(',', $att_arr);
746 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
749 $res['object'] = '<object>' . "\n";
750 $child = $rawobj[0]['child'];
751 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
752 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
753 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
755 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
756 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
757 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
758 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
759 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
760 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
761 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
762 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
764 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
765 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
766 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
767 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
769 $body = html2bb_video($body);
771 $config = HTMLPurifier_Config::createDefault();
772 $config->set('Cache.DefinitionImpl', null);
774 $purifier = new HTMLPurifier($config);
775 $body = $purifier->purify($body);
776 $body = html2bbcode($body);
779 $res['object'] .= '<content>' . $body . '</content>' . "\n";
782 $res['object'] .= '</object>' . "\n";
785 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
788 $res['target'] = '<target>' . "\n";
789 $child = $rawobj[0]['child'];
790 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
791 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
793 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
794 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
795 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
796 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
797 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
798 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
799 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
800 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
802 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
803 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
804 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
805 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
807 $body = html2bb_video($body);
809 $config = HTMLPurifier_Config::createDefault();
810 $config->set('Cache.DefinitionImpl', null);
812 $purifier = new HTMLPurifier($config);
813 $body = $purifier->purify($body);
814 $body = html2bbcode($body);
817 $res['target'] .= '<content>' . $body . '</content>' . "\n";
820 $res['target'] .= '</target>' . "\n";
823 // This is some experimental stuff. By now retweets are shown with "RT:"
824 // But: There is data so that the message could be shown similar to native retweets
825 // There is some better way to parse this array - but it didn't worked for me.
826 $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"];
827 if (is_array($child)) {
828 logger('get_atom_elements: Looking for status.net repeated message');
830 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
831 $orig_uri = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"];
832 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
833 $uri = $author["uri"][0]["data"];
834 $name = $author["name"][0]["data"];
835 $avatar = @array_shift($author["link"][2]["attribs"]);
836 $avatar = $avatar["href"];
838 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
839 logger('get_atom_elements: fixing sender of repeated message.');
841 if (!intval(get_config('system','wall-to-wall_share'))) {
842 $prefix = share_header($name, $uri, $avatar, "", "", $orig_uri);
844 $res["body"] = $prefix.html2bbcode($message)."[/share]";
846 $res["owner-name"] = $res["author-name"];
847 $res["owner-link"] = $res["author-link"];
848 $res["owner-avatar"] = $res["author-avatar"];
850 $res["author-name"] = $name;
851 $res["author-link"] = $uri;
852 $res["author-avatar"] = $avatar;
854 $res["body"] = html2bbcode($message);
859 // Search for ostatus conversation url
860 $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"];
862 if (is_array($links)) {
863 foreach ($links as $link) {
864 $conversation = array_shift($link["attribs"]);
866 if ($conversation["rel"] == "ostatus:conversation") {
867 $res["ostatus_conversation"] = $conversation["href"];
868 logger('get_atom_elements: found conversation url '.$res["ostatus_conversation"]);
873 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
876 // Handle enclosures and treat them as preview picture
878 foreach ($attach AS $attachment)
879 if ($attachment->type == "image/jpeg")
880 $preview = $attachment->link;
882 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
883 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
885 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
886 unset($res["attach"]);
887 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
888 $res["body"] = add_page_info_to_body($res["body"]);
889 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
890 $res["body"] = add_page_info_to_body($res["body"]);
893 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
895 call_hooks('parse_atom', $arr);
900 function add_page_info_data($data) {
901 call_hooks('page_info_data', $data);
903 // It maybe is a rich content, but if it does have everything that a link has,
904 // then treat it that way
905 if (($data["type"] == "rich") AND is_string($data["title"]) AND
906 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
907 $data["type"] = "link";
909 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
912 if ($no_photos AND ($data["type"] == "photo"))
915 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
916 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
917 require_once("include/network.php");
918 $data["url"] = short_link($data["url"]);
921 if (($data["type"] != "photo") AND is_string($data["title"]))
922 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
924 if (($data["type"] != "video") AND ($photo != ""))
925 $text .= '[img]'.$photo.'[/img]';
926 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
927 $imagedata = $data["images"][0];
928 $text .= '[img]'.$imagedata["src"].'[/img]';
931 if (($data["type"] != "photo") AND is_string($data["text"]))
932 $text .= "[quote]".$data["text"]."[/quote]";
935 if (isset($data["keywords"]) AND count($data["keywords"])) {
938 foreach ($data["keywords"] AS $keyword) {
939 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
940 array("","", "", "", "", ""), $keyword);
941 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
945 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
948 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
949 require_once("mod/parse_url.php");
951 $data = Cache::get("parse_url:".$url);
953 $data = parseurl_getsiteinfo($url, true);
954 Cache::set("parse_url:".$url,serialize($data));
956 $data = unserialize($data);
959 $data["images"][0]["src"] = $photo;
961 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
963 if (!$keywords AND isset($data["keywords"]))
964 unset($data["keywords"]);
966 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
967 $list = explode(",", $keyword_blacklist);
968 foreach ($list AS $keyword) {
969 $keyword = trim($keyword);
970 $index = array_search($keyword, $data["keywords"]);
971 if ($index !== false)
972 unset($data["keywords"][$index]);
979 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
980 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
983 if (isset($data["keywords"]) AND count($data["keywords"])) {
985 foreach ($data["keywords"] AS $keyword) {
986 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
987 array("","", "", "", "", ""), $keyword);
992 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
999 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1000 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1002 $text = add_page_info_data($data);
1007 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1009 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1011 $URLSearchString = "^\[\]";
1013 // Adding these spaces is a quick hack due to my problems with regular expressions :)
1014 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1017 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1019 // Convert urls without bbcode elements
1020 if (!$matches AND $texturl) {
1021 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1023 // Yeah, a hack. I really hate regular expressions :)
1025 $matches[1] = $matches[2];
1029 $footer = add_page_info($matches[1], $no_photos);
1031 // Remove the link from the body if the link is attached at the end of the post
1032 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1033 $removedlink = trim(str_replace($matches[1], "", $body));
1034 if (($removedlink == "") OR strstr($body, $removedlink))
1035 $body = $removedlink;
1037 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1038 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1039 if (($removedlink == "") OR strstr($body, $removedlink))
1040 $body = $removedlink;
1043 // Add the page information to the bottom
1044 if (isset($footer) AND (trim($footer) != ""))
1050 function encode_rel_links($links) {
1052 if(! ((is_array($links)) && (count($links))))
1054 foreach($links as $link) {
1056 if($link['attribs']['']['rel'])
1057 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1058 if($link['attribs']['']['type'])
1059 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1060 if($link['attribs']['']['href'])
1061 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1062 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1063 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1064 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1065 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1066 $o .= ' />' . "\n" ;
1073 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1075 // If it is a posting where users should get notifications, then define it as wall posting
1078 $arr['type'] = 'wall';
1080 $arr['last-child'] = 1;
1081 $arr['network'] = NETWORK_DFRN;
1084 // If a Diaspora signature structure was passed in, pull it out of the
1085 // item array and set it aside for later storage.
1088 if(x($arr,'dsprsig')) {
1089 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1090 unset($arr['dsprsig']);
1093 // if an OStatus conversation url was passed in, it is stored and then
1094 // removed from the array.
1095 $ostatus_conversation = null;
1097 if (isset($arr["ostatus_conversation"])) {
1098 $ostatus_conversation = $arr["ostatus_conversation"];
1099 unset($arr["ostatus_conversation"]);
1102 if(x($arr, 'gravity'))
1103 $arr['gravity'] = intval($arr['gravity']);
1104 elseif($arr['parent-uri'] === $arr['uri'])
1105 $arr['gravity'] = 0;
1106 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1107 $arr['gravity'] = 6;
1109 $arr['gravity'] = 6; // extensible catchall
1111 if(! x($arr,'type'))
1112 $arr['type'] = 'remote';
1116 /* check for create date and expire time */
1117 $uid = intval($arr['uid']);
1118 $r = q("SELECT expire FROM user WHERE uid = %d", $uid);
1120 $expire_interval = $r[0]['expire'];
1121 if ($expire_interval>0) {
1122 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1123 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1124 if ($created_date < $expire_date) {
1125 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1131 // If there is no guid then take the same guid that was taken before for the same uri
1132 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) {
1133 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1134 $r = q("SELECT `guid` FROM `item` WHERE `uri` = '%s' AND `guid` != '' LIMIT 1",
1135 dbesc(trim($arr['uri']))
1139 $arr['guid'] = $r[0]["guid"];
1140 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1144 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1145 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1146 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1147 // $arr['body'] = strip_tags($arr['body']);
1150 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1151 require_once('library/langdet/Text/LanguageDetect.php');
1152 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1153 $l = new Text_LanguageDetect;
1154 //$lng = $l->detectConfidence($naked_body);
1155 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1156 $lng = $l->detect($naked_body, 3);
1158 if (sizeof($lng) > 0) {
1161 foreach ($lng as $language => $score) {
1162 if ($postopts == "")
1163 $postopts = "lang=";
1167 $postopts .= $language.";".$score;
1169 $arr['postopts'] = $postopts;
1173 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1174 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1175 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1176 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1177 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1178 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1179 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1180 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1181 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1182 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1183 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1184 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1185 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1186 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1187 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1188 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1189 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1190 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1191 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1192 $arr['deleted'] = 0;
1193 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1194 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1195 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1196 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1197 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1198 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1199 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1200 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1201 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1202 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1203 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1204 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1205 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1206 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1207 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1208 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1209 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1210 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1211 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1212 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $arr['network']));
1213 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1214 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1215 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1216 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1217 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1219 if ($arr['plink'] == "") {
1221 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1224 if ($arr['network'] == "") {
1225 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1226 intval($arr['contact-id']),
1231 $arr['network'] = $r[0]["network"];
1233 // Fallback to friendica (why is it empty in some cases?)
1234 if ($arr['network'] == "")
1235 $arr['network'] = NETWORK_DFRN;
1237 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1240 if ($arr['guid'] != "") {
1241 // Checking if there is already an item with the same guid
1242 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1243 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1244 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1247 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1252 // Check for hashtags in the body and repair or add hashtag links
1253 item_body_set_hashtags($arr);
1255 $arr['thr-parent'] = $arr['parent-uri'];
1256 if($arr['parent-uri'] === $arr['uri']) {
1258 $parent_deleted = 0;
1259 $allow_cid = $arr['allow_cid'];
1260 $allow_gid = $arr['allow_gid'];
1261 $deny_cid = $arr['deny_cid'];
1262 $deny_gid = $arr['deny_gid'];
1263 $notify_type = 'wall-new';
1267 // find the parent and snarf the item id and ACLs
1268 // and anything else we need to inherit
1270 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1271 dbesc($arr['parent-uri']),
1277 // is the new message multi-level threaded?
1278 // even though we don't support it now, preserve the info
1279 // and re-attach to the conversation parent.
1281 if($r[0]['uri'] != $r[0]['parent-uri']) {
1282 $arr['parent-uri'] = $r[0]['parent-uri'];
1283 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1284 ORDER BY `id` ASC LIMIT 1",
1285 dbesc($r[0]['parent-uri']),
1286 dbesc($r[0]['parent-uri']),
1293 $parent_id = $r[0]['id'];
1294 $parent_deleted = $r[0]['deleted'];
1295 $allow_cid = $r[0]['allow_cid'];
1296 $allow_gid = $r[0]['allow_gid'];
1297 $deny_cid = $r[0]['deny_cid'];
1298 $deny_gid = $r[0]['deny_gid'];
1299 $arr['wall'] = $r[0]['wall'];
1300 $notify_type = 'comment-new';
1302 // if the parent is private, force privacy for the entire conversation
1303 // This differs from the above settings as it subtly allows comments from
1304 // email correspondents to be private even if the overall thread is not.
1306 if($r[0]['private'])
1307 $arr['private'] = $r[0]['private'];
1309 // Edge case. We host a public forum that was originally posted to privately.
1310 // The original author commented, but as this is a comment, the permissions
1311 // weren't fixed up so it will still show the comment as private unless we fix it here.
1313 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1314 $arr['private'] = 0;
1317 // If its a post from myself then tag the thread as "mention"
1318 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1319 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1322 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1323 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1324 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1325 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1326 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1332 // Allow one to see reply tweets from status.net even when
1333 // we don't have or can't see the original post.
1336 logger('item_store: $force_parent=true, reply converted to top-level post.');
1338 $arr['parent-uri'] = $arr['uri'];
1339 $arr['gravity'] = 0;
1342 logger('item_store: item parent was not found - ignoring item');
1346 $parent_deleted = 0;
1350 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1354 if($r && count($r)) {
1355 logger('item-store: duplicate item ignored. ' . print_r($arr,true));
1359 // Is this item available in the global items (with uid=0)?
1360 if ($arr["uid"] == 0) {
1361 $arr["global"] = true;
1363 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1365 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1367 $arr["global"] = (count($isglobal) > 0);
1370 // Fill the cache field
1371 put_item_in_cache($arr);
1373 call_hooks('post_remote',$arr);
1375 if(x($arr,'cancel')) {
1376 logger('item_store: post cancelled by plugin.');
1380 // Store the unescaped version
1385 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1387 $r = dbq("INSERT INTO `item` (`"
1388 . implode("`, `", array_keys($arr))
1390 . implode("', '", array_values($arr))
1396 // find the item we just created
1397 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1403 $current_post = $r[0]['id'];
1404 logger('item_store: created item ' . $current_post);
1406 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1407 // This can be used to filter for inactive contacts.
1408 // Only do this for public postings to avoid privacy problems, since poco data is public.
1409 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1411 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1413 // Is it a forum? Then we don't care about the rules from above
1414 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1415 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1416 intval($arr['contact-id']));
1422 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1423 dbesc($arr['received']),
1424 dbesc($arr['received']),
1425 intval($arr['contact-id'])
1428 logger('item_store: could not locate created item');
1432 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1433 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1435 intval($arr['uid']),
1436 intval($current_post)
1440 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1441 $parent_id = $current_post;
1443 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1446 $private = $arr['private'];
1448 // Set parent id - and also make sure to inherit the parent's ACLs.
1450 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1451 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1458 intval($parent_deleted),
1459 intval($current_post)
1462 // Complete ostatus threads
1463 if ($ostatus_conversation)
1464 complete_conversation($current_post, $ostatus_conversation);
1466 $arr['id'] = $current_post;
1467 $arr['parent'] = $parent_id;
1468 $arr['allow_cid'] = $allow_cid;
1469 $arr['allow_gid'] = $allow_gid;
1470 $arr['deny_cid'] = $deny_cid;
1471 $arr['deny_gid'] = $deny_gid;
1472 $arr['private'] = $private;
1473 $arr['deleted'] = $parent_deleted;
1475 // update the commented timestamp on the parent
1476 // Only update "commented" if it is really a comment
1477 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1478 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1479 dbesc(datetime_convert()),
1480 dbesc(datetime_convert()),
1484 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1485 dbesc(datetime_convert()),
1490 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1491 intval($current_post),
1492 dbesc($dsprsig->signed_text),
1493 dbesc($dsprsig->signature),
1494 dbesc($dsprsig->signer)
1500 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1503 if($arr['last-child']) {
1504 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1506 intval($arr['uid']),
1507 intval($current_post)
1511 $deleted = tag_deliver($arr['uid'],$current_post);
1513 // current post can be deleted if is for a community page and no mention are
1515 if (!$deleted AND !$dontcache) {
1517 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1518 if (count($r) == 1) {
1519 call_hooks('post_remote_end', $r[0]);
1521 logger('item_store: new item not found in DB, id ' . $current_post);
1524 // Add every contact of the post to the global contact table
1527 create_tags_from_item($current_post);
1528 create_files_from_item($current_post);
1530 // Only check for notifications on start posts
1531 if ($arr['parent-uri'] === $arr['uri']) {
1532 add_thread($current_post);
1533 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1535 // Send a notification for every new post?
1536 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1537 intval($arr['contact-id']),
1540 $send_notification = count($r);
1542 if (!$send_notification) {
1543 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1544 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1547 foreach ($tags AS $tag) {
1548 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1549 normalise_link($tag["url"]), intval($arr['uid']));
1551 $send_notification = true;
1556 if ($send_notification) {
1557 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1558 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1559 intval($arr['uid']));
1561 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1562 intval($current_post),
1568 require_once('include/enotify.php');
1570 'type' => NOTIFY_SHARE,
1571 'notify_flags' => $u[0]['notify-flags'],
1572 'language' => $u[0]['language'],
1573 'to_name' => $u[0]['username'],
1574 'to_email' => $u[0]['email'],
1575 'uid' => $u[0]['uid'],
1577 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1578 'source_name' => $item[0]['author-name'],
1579 'source_link' => $item[0]['author-link'],
1580 'source_photo' => $item[0]['author-avatar'],
1581 'verb' => ACTIVITY_TAG,
1583 'parent' => $arr['parent']
1585 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1588 update_thread($parent_id);
1589 add_shadow_entry($arr);
1593 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1595 return $current_post;
1598 function item_body_set_hashtags(&$item) {
1600 $tags = get_tags($item["body"]);
1606 // This sorting is important when there are hashtags that are part of other hashtags
1607 // Otherwise there could be problems with hashtags like #test and #test2
1612 $URLSearchString = "^\[\]";
1614 // All hashtags should point to the home server
1615 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1616 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1618 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1619 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1621 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1622 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1624 return("[url=".$match[1]."]".str_replace("#", "#", $match[2])."[/url]");
1627 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1629 return("[bookmark=".$match[1]."]".str_replace("#", "#", $match[2])."[/bookmark]");
1632 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1634 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1637 // Repair recursive urls
1638 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1639 "#$2", $item["body"]);
1641 foreach($tags as $tag) {
1642 if(strpos($tag,'#') !== 0)
1645 if(strpos($tag,'[url='))
1648 $basetag = str_replace('_',' ',substr($tag,1));
1650 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1652 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1654 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1655 if(strlen($item["tag"]))
1656 $item["tag"] = ','.$item["tag"];
1657 $item["tag"] = $newtag.$item["tag"];
1661 // Convert back the masked hashtags
1662 $item["body"] = str_replace("#", "#", $item["body"]);
1665 function get_item_guid($id) {
1666 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1668 return($r[0]["guid"]);
1673 function get_item_id($guid, $uid = 0) {
1679 $uid == local_user();
1681 // Does the given user have this item?
1683 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1684 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1685 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1688 $nick = $r[0]["nickname"];
1692 // Or is it anywhere on the server?
1694 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1695 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1696 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1697 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1698 AND `item`.`private` = 0 AND `item`.`wall` = 1
1699 AND `item`.`guid` = '%s'", dbesc($guid));
1702 $nick = $r[0]["nickname"];
1705 return(array("nick" => $nick, "id" => $id));
1709 function get_item_contact($item,$contacts) {
1710 if(! count($contacts) || (! is_array($item)))
1712 foreach($contacts as $contact) {
1713 if($contact['id'] == $item['contact-id']) {
1715 break; // NOTREACHED
1722 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1724 * @param int $item_id
1725 * @return bool true if item was deleted, else false
1727 function tag_deliver($uid,$item_id) {
1735 $u = q("select * from user where uid = %d limit 1",
1741 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1742 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1745 $i = q("select * from item where id = %d and uid = %d limit 1",
1754 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1756 // Diaspora uses their own hardwired link URL in @-tags
1757 // instead of the one we supply with webfinger
1759 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1761 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1763 foreach($matches as $mtch) {
1764 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1766 logger('tag_deliver: mention found: ' . $mtch[2]);
1772 if ( ($community_page || $prvgroup) &&
1773 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1774 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1776 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1777 q("DELETE FROM item WHERE id = %d and uid = %d",
1787 // send a notification
1789 // use a local photo if we have one
1791 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1792 intval($u[0]['uid']),
1793 dbesc(normalise_link($item['author-link']))
1795 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1798 require_once('include/enotify.php');
1800 'type' => NOTIFY_TAGSELF,
1801 'notify_flags' => $u[0]['notify-flags'],
1802 'language' => $u[0]['language'],
1803 'to_name' => $u[0]['username'],
1804 'to_email' => $u[0]['email'],
1805 'uid' => $u[0]['uid'],
1807 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1808 'source_name' => $item['author-name'],
1809 'source_link' => $item['author-link'],
1810 'source_photo' => $photo,
1811 'verb' => ACTIVITY_TAG,
1813 'parent' => $item['parent']
1817 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1819 call_hooks('tagged', $arr);
1821 if((! $community_page) && (! $prvgroup))
1825 // tgroup delivery - setup a second delivery chain
1826 // prevent delivery looping - only proceed
1827 // if the message originated elsewhere and is a top-level post
1829 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1832 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1835 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1836 intval($u[0]['uid'])
1841 // also reset all the privacy bits to the forum default permissions
1843 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1845 $forum_mode = (($prvgroup) ? 2 : 1);
1847 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1848 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1849 intval($forum_mode),
1850 dbesc($c[0]['name']),
1851 dbesc($c[0]['url']),
1852 dbesc($c[0]['thumb']),
1854 dbesc($u[0]['allow_cid']),
1855 dbesc($u[0]['allow_gid']),
1856 dbesc($u[0]['deny_cid']),
1857 dbesc($u[0]['deny_gid']),
1860 update_thread($item_id);
1862 proc_run('php','include/notifier.php','tgroup',$item_id);
1868 function tgroup_check($uid,$item) {
1874 // check that the message originated elsewhere and is a top-level post
1876 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1880 $u = q("select * from user where uid = %d limit 1",
1886 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1887 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1890 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1892 // Diaspora uses their own hardwired link URL in @-tags
1893 // instead of the one we supply with webfinger
1895 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1897 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1899 foreach($matches as $mtch) {
1900 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1902 logger('tgroup_check: mention found: ' . $mtch[2]);
1910 if((! $community_page) && (! $prvgroup))
1924 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1928 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1930 if($contact['duplex'] && $contact['dfrn-id'])
1931 $idtosend = '0:' . $orig_id;
1932 if($contact['duplex'] && $contact['issued-id'])
1933 $idtosend = '1:' . $orig_id;
1935 $rino = ((function_exists('mcrypt_encrypt')) ? 1 : 0);
1937 $rino_enable = get_config('system','rino_encrypt');
1942 $ssl_val = intval(get_config('system','ssl_policy'));
1946 case SSL_POLICY_FULL:
1947 $ssl_policy = 'full';
1949 case SSL_POLICY_SELFSIGN:
1950 $ssl_policy = 'self';
1952 case SSL_POLICY_NONE:
1954 $ssl_policy = 'none';
1958 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino=1' : '');
1960 logger('dfrn_deliver: ' . $url);
1962 $xml = fetch_url($url);
1964 $curl_stat = $a->get_curl_code();
1966 return(-1); // timed out
1968 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
1973 if(strpos($xml,'<?xml') === false) {
1974 logger('dfrn_deliver: no valid XML returned');
1975 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
1979 $res = parse_xml_string($xml);
1981 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
1982 return (($res->status) ? $res->status : 3);
1984 $postvars = array();
1985 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
1986 $challenge = hex2bin((string) $res->challenge);
1987 $perm = (($res->perm) ? $res->perm : null);
1988 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
1989 $rino_allowed = ((intval($res->rino) === 1) ? 1 : 0);
1990 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
1992 if($owner['page-flags'] == PAGE_PRVGROUP)
1995 $final_dfrn_id = '';
1998 if((($perm == 'rw') && (! intval($contact['writable'])))
1999 || (($perm == 'r') && (intval($contact['writable'])))) {
2000 q("update contact set writable = %d where id = %d",
2001 intval(($perm == 'rw') ? 1 : 0),
2002 intval($contact['id'])
2004 $contact['writable'] = (string) 1 - intval($contact['writable']);
2008 if(($contact['duplex'] && strlen($contact['pubkey']))
2009 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2010 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2011 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2012 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2015 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2016 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2019 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2021 if(strpos($final_dfrn_id,':') == 1)
2022 $final_dfrn_id = substr($final_dfrn_id,2);
2024 if($final_dfrn_id != $orig_id) {
2025 logger('dfrn_deliver: wrong dfrn_id.');
2026 // did not decode properly - cannot trust this site
2030 $postvars['dfrn_id'] = $idtosend;
2031 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2033 $postvars['dissolve'] = '1';
2036 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2037 $postvars['data'] = $atom;
2038 $postvars['perm'] = 'rw';
2041 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2042 $postvars['perm'] = 'r';
2045 $postvars['ssl_policy'] = $ssl_policy;
2048 $postvars['page'] = $page;
2050 if($rino && $rino_allowed && (! $dissolve)) {
2051 $key = substr(random_string(),0,16);
2052 $data = bin2hex(aes_encrypt($postvars['data'],$key));
2053 $postvars['data'] = $data;
2054 logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2057 if($dfrn_version >= 2.1) {
2058 if(($contact['duplex'] && strlen($contact['pubkey']))
2059 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2060 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2062 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2065 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2069 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2070 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2073 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2077 logger('md5 rawkey ' . md5($postvars['key']));
2079 $postvars['key'] = bin2hex($postvars['key']);
2082 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2084 $xml = post_url($contact['notify'],$postvars);
2086 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2088 $curl_stat = $a->get_curl_code();
2089 if((! $curl_stat) || (! strlen($xml)))
2090 return(-1); // timed out
2092 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2095 if(strpos($xml,'<?xml') === false) {
2096 logger('dfrn_deliver: phase 2: no valid XML returned');
2097 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2101 if($contact['term-date'] != '0000-00-00 00:00:00') {
2102 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2103 require_once('include/Contact.php');
2104 unmark_for_death($contact);
2107 $res = parse_xml_string($xml);
2109 return $res->status;
2114 This function returns true if $update has an edited timestamp newer
2115 than $existing, i.e. $update contains new data which should override
2116 what's already there. If there is no timestamp yet, the update is
2117 assumed to be newer. If the update has no timestamp, the existing
2118 item is assumed to be up-to-date. If the timestamps are equal it
2119 assumes the update has been seen before and should be ignored.
2121 function edited_timestamp_is_newer($existing, $update) {
2122 if (!x($existing,'edited') || !$existing['edited']) {
2125 if (!x($update,'edited') || !$update['edited']) {
2128 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2129 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2130 return (strcmp($existing_edited, $update_edited) < 0);
2135 * consume_feed - process atom feed and update anything/everything we might need to update
2137 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2139 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2140 * It is this person's stuff that is going to be updated.
2141 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2142 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2143 * have a contact record.
2144 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2145 * might not) try and subscribe to it.
2146 * $datedir sorts in reverse order
2147 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2148 * imported prior to its children being seen in the stream unless we are certain
2149 * of how the feed is arranged/ordered.
2150 * With $pass = 1, we only pull parent items out of the stream.
2151 * With $pass = 2, we only pull children (comments/likes).
2153 * So running this twice, first with pass 1 and then with pass 2 will do the right
2154 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2155 * model where comments can have sub-threads. That would require some massive sorting
2156 * to get all the feed items into a mostly linear ordering, and might still require
2160 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2162 require_once('library/simplepie/simplepie.inc');
2163 require_once('include/contact_selectors.php');
2165 if(! strlen($xml)) {
2166 logger('consume_feed: empty input');
2170 $feed = new SimplePie();
2171 $feed->set_raw_data($xml);
2173 $feed->enable_order_by_date(true);
2175 $feed->enable_order_by_date(false);
2179 logger('consume_feed: Error parsing XML: ' . $feed->error());
2181 $permalink = $feed->get_permalink();
2183 // Check at the feed level for updated contact name and/or photo
2187 $photo_timestamp = '';
2190 $contact_updated = '';
2192 $hubs = $feed->get_links('hub');
2193 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2196 $hub = implode(',', $hubs);
2198 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2200 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2202 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2203 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2204 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2205 $new_name = $elems['name'][0]['data'];
2207 // Manually checking for changed contact names
2208 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2209 $name_updated = date("c");
2210 $photo_timestamp = date("c");
2213 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2214 if ($photo_timestamp == "")
2215 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2216 $photo_url = $elems['link'][0]['attribs']['']['href'];
2219 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2220 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2224 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2225 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2227 $contact_updated = $photo_timestamp;
2229 require_once("include/Photo.php");
2230 $photo_failure = false;
2231 $have_photo = false;
2233 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2234 intval($contact['id']),
2235 intval($contact['uid'])
2238 $resource_id = $r[0]['resource-id'];
2242 $resource_id = photo_new_resource();
2245 $img_str = fetch_url($photo_url,true);
2246 // guess mimetype from headers or filename
2247 $type = guess_image_type($photo_url,true);
2250 $img = new Photo($img_str, $type);
2251 if($img->is_valid()) {
2253 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2254 dbesc($resource_id),
2255 intval($contact['id']),
2256 intval($contact['uid'])
2260 $img->scaleImageSquare(175);
2262 $hash = $resource_id;
2263 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2265 $img->scaleImage(80);
2266 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2268 $img->scaleImage(48);
2269 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2273 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2274 WHERE `uid` = %d AND `id` = %d",
2275 dbesc(datetime_convert()),
2276 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2277 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2278 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2279 intval($contact['uid']),
2280 intval($contact['id'])
2285 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2286 if ($name_updated > $contact_updated)
2287 $contact_updated = $name_updated;
2289 $r = q("select * from contact where uid = %d and id = %d limit 1",
2290 intval($contact['uid']),
2291 intval($contact['id'])
2294 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2295 dbesc(notags(trim($new_name))),
2296 dbesc(datetime_convert()),
2297 intval($contact['uid']),
2298 intval($contact['id'])
2301 // do our best to update the name on content items
2304 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2305 dbesc(notags(trim($new_name))),
2306 dbesc($r[0]['name']),
2307 dbesc($r[0]['url']),
2308 intval($contact['uid'])
2313 if ($contact_updated AND $new_name AND $photo_url)
2314 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2316 if(strlen($birthday)) {
2317 if(substr($birthday,0,4) != $contact['bdyear']) {
2318 logger('consume_feed: updating birthday: ' . $birthday);
2322 * Add new birthday event for this person
2324 * $bdtext is just a readable placeholder in case the event is shared
2325 * with others. We will replace it during presentation to our $importer
2326 * to contain a sparkle link and perhaps a photo.
2330 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2331 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2334 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2335 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2336 intval($contact['uid']),
2337 intval($contact['id']),
2338 dbesc(datetime_convert()),
2339 dbesc(datetime_convert()),
2340 dbesc(datetime_convert('UTC','UTC', $birthday)),
2341 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2350 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2351 dbesc(substr($birthday,0,4)),
2352 intval($contact['uid']),
2353 intval($contact['id'])
2356 // This function is called twice without reloading the contact
2357 // Make sure we only create one event. This is why &$contact
2358 // is a reference var in this function
2360 $contact['bdyear'] = substr($birthday,0,4);
2364 $community_page = 0;
2365 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2367 $community_page = intval($rawtags[0]['data']);
2369 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2370 q("update contact set forum = %d where id = %d",
2371 intval($community_page),
2372 intval($contact['id'])
2374 $contact['forum'] = (string) $community_page;
2378 // process any deleted entries
2380 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2381 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2382 foreach($del_entries as $dentry) {
2384 if(isset($dentry['attribs']['']['ref'])) {
2385 $uri = $dentry['attribs']['']['ref'];
2387 if(isset($dentry['attribs']['']['when'])) {
2388 $when = $dentry['attribs']['']['when'];
2389 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2392 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2394 if($deleted && is_array($contact)) {
2395 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2396 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2398 intval($importer['uid']),
2399 intval($contact['id'])
2404 if(! $item['deleted'])
2405 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2407 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2408 $xo = parse_xml_string($item['object'],false);
2409 $xt = parse_xml_string($item['target'],false);
2410 if($xt->type === ACTIVITY_OBJ_NOTE) {
2411 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2413 intval($importer['importer_uid'])
2417 // For tags, the owner cannot remove the tag on the author's copy of the post.
2419 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2420 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2421 $author_copy = (($item['origin']) ? true : false);
2423 if($owner_remove && $author_copy)
2425 if($author_remove || $owner_remove) {
2426 $tags = explode(',',$i[0]['tag']);
2429 foreach($tags as $tag)
2430 if(trim($tag) !== trim($xo->body))
2431 $newtags[] = trim($tag);
2433 q("update item set tag = '%s' where id = %d",
2434 dbesc(implode(',',$newtags)),
2437 create_tags_from_item($i[0]['id']);
2443 if($item['uri'] == $item['parent-uri']) {
2444 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2445 `body` = '', `title` = ''
2446 WHERE `parent-uri` = '%s' AND `uid` = %d",
2448 dbesc(datetime_convert()),
2449 dbesc($item['uri']),
2450 intval($importer['uid'])
2452 create_tags_from_itemuri($item['uri'], $importer['uid']);
2453 create_files_from_itemuri($item['uri'], $importer['uid']);
2454 update_thread_uri($item['uri'], $importer['uid']);
2457 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2458 `body` = '', `title` = ''
2459 WHERE `uri` = '%s' AND `uid` = %d",
2461 dbesc(datetime_convert()),
2463 intval($importer['uid'])
2465 create_tags_from_itemuri($uri, $importer['uid']);
2466 create_files_from_itemuri($uri, $importer['uid']);
2467 if($item['last-child']) {
2468 // ensure that last-child is set in case the comment that had it just got wiped.
2469 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2470 dbesc(datetime_convert()),
2471 dbesc($item['parent-uri']),
2472 intval($item['uid'])
2474 // who is the last child now?
2475 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2476 ORDER BY `created` DESC LIMIT 1",
2477 dbesc($item['parent-uri']),
2478 intval($importer['uid'])
2481 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2492 // Now process the feed
2494 if($feed->get_item_quantity()) {
2496 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2498 // in inverse date order
2500 $items = array_reverse($feed->get_items());
2502 $items = $feed->get_items();
2505 foreach($items as $item) {
2508 $item_id = $item->get_id();
2509 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2510 if(isset($rawthread[0]['attribs']['']['ref'])) {
2512 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2515 if(($is_reply) && is_array($contact)) {
2520 // not allowed to post
2522 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2526 // Have we seen it? If not, import it.
2528 $item_id = $item->get_id();
2529 $datarray = get_atom_elements($feed, $item, $contact);
2531 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2532 $datarray['author-name'] = $contact['name'];
2533 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2534 $datarray['author-link'] = $contact['url'];
2535 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2536 $datarray['author-avatar'] = $contact['thumb'];
2538 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2539 logger('consume_feed: no author information! ' . print_r($datarray,true));
2543 $force_parent = false;
2544 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2545 if($contact['network'] === NETWORK_OSTATUS)
2546 $force_parent = true;
2547 if(strlen($datarray['title']))
2548 unset($datarray['title']);
2549 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2550 dbesc(datetime_convert()),
2552 intval($importer['uid'])
2554 $datarray['last-child'] = 1;
2555 update_thread_uri($parent_uri, $importer['uid']);
2559 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2561 intval($importer['uid'])
2564 // Update content if 'updated' changes
2567 if (edited_timestamp_is_newer($r[0], $datarray)) {
2569 // do not accept (ignore) an earlier edit than one we currently have.
2570 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2573 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2574 dbesc($datarray['title']),
2575 dbesc($datarray['body']),
2576 dbesc($datarray['tag']),
2577 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2578 dbesc(datetime_convert()),
2580 intval($importer['uid'])
2582 create_tags_from_itemuri($item_id, $importer['uid']);
2583 update_thread_uri($item_id, $importer['uid']);
2586 // update last-child if it changes
2588 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2589 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2590 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2591 dbesc(datetime_convert()),
2593 intval($importer['uid'])
2595 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2596 intval($allow[0]['data']),
2597 dbesc(datetime_convert()),
2599 intval($importer['uid'])
2601 update_thread_uri($item_id, $importer['uid']);
2607 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2608 // one way feed - no remote comment ability
2609 $datarray['last-child'] = 0;
2611 $datarray['parent-uri'] = $parent_uri;
2612 $datarray['uid'] = $importer['uid'];
2613 $datarray['contact-id'] = $contact['id'];
2614 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2615 $datarray['type'] = 'activity';
2616 $datarray['gravity'] = GRAVITY_LIKE;
2617 // only one like or dislike per person
2618 // splitted into two queries for performance issues
2619 $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",
2620 intval($datarray['uid']),
2621 intval($datarray['contact-id']),
2622 dbesc($datarray['verb']),
2628 $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",
2629 intval($datarray['uid']),
2630 intval($datarray['contact-id']),
2631 dbesc($datarray['verb']),
2638 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2639 $xo = parse_xml_string($datarray['object'],false);
2640 $xt = parse_xml_string($datarray['target'],false);
2642 if($xt->type == ACTIVITY_OBJ_NOTE) {
2643 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2645 intval($importer['importer_uid'])
2650 // extract tag, if not duplicate, add to parent item
2651 if($xo->id && $xo->content) {
2652 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2653 if(! (stristr($r[0]['tag'],$newtag))) {
2654 q("UPDATE item SET tag = '%s' WHERE id = %d",
2655 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2658 create_tags_from_item($r[0]['id']);
2664 $r = item_store($datarray,$force_parent);
2670 // Head post of a conversation. Have we seen it? If not, import it.
2672 $item_id = $item->get_id();
2674 $datarray = get_atom_elements($feed, $item, $contact);
2676 if(is_array($contact)) {
2677 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2678 $datarray['author-name'] = $contact['name'];
2679 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2680 $datarray['author-link'] = $contact['url'];
2681 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2682 $datarray['author-avatar'] = $contact['thumb'];
2685 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2686 logger('consume_feed: no author information! ' . print_r($datarray,true));
2690 // special handling for events
2692 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2693 $ev = bbtoevent($datarray['body']);
2694 if(x($ev,'desc') && x($ev,'start')) {
2695 $ev['uid'] = $importer['uid'];
2696 $ev['uri'] = $item_id;
2697 $ev['edited'] = $datarray['edited'];
2698 $ev['private'] = $datarray['private'];
2700 if(is_array($contact))
2701 $ev['cid'] = $contact['id'];
2702 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2704 intval($importer['uid'])
2707 $ev['id'] = $r[0]['id'];
2708 $xyz = event_store($ev);
2713 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2714 if(strlen($datarray['title']))
2715 unset($datarray['title']);
2716 $datarray['last-child'] = 1;
2720 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2722 intval($importer['uid'])
2725 // Update content if 'updated' changes
2728 if (edited_timestamp_is_newer($r[0], $datarray)) {
2730 // do not accept (ignore) an earlier edit than one we currently have.
2731 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2734 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2735 dbesc($datarray['title']),
2736 dbesc($datarray['body']),
2737 dbesc($datarray['tag']),
2738 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2739 dbesc(datetime_convert()),
2741 intval($importer['uid'])
2743 create_tags_from_itemuri($item_id, $importer['uid']);
2744 update_thread_uri($item_id, $importer['uid']);
2747 // update last-child if it changes
2749 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2750 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2751 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2752 intval($allow[0]['data']),
2753 dbesc(datetime_convert()),
2755 intval($importer['uid'])
2757 update_thread_uri($item_id, $importer['uid']);
2762 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2763 logger('consume-feed: New follower');
2764 new_follower($importer,$contact,$datarray,$item);
2767 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2768 lose_follower($importer,$contact,$datarray,$item);
2772 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2773 logger('consume-feed: New friend request');
2774 new_follower($importer,$contact,$datarray,$item,true);
2777 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2778 lose_sharer($importer,$contact,$datarray,$item);
2783 if(! is_array($contact))
2787 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2788 // one way feed - no remote comment ability
2789 $datarray['last-child'] = 0;
2791 if($contact['network'] === NETWORK_FEED)
2792 $datarray['private'] = 2;
2794 $datarray['parent-uri'] = $item_id;
2795 $datarray['uid'] = $importer['uid'];
2796 $datarray['contact-id'] = $contact['id'];
2798 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2799 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2800 // but otherwise there's a possible data mixup on the sender's system.
2801 // the tgroup delivery code called from item_store will correct it if it's a forum,
2802 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2803 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2804 $datarray['owner-name'] = $contact['name'];
2805 $datarray['owner-link'] = $contact['url'];
2806 $datarray['owner-avatar'] = $contact['thumb'];
2809 // We've allowed "followers" to reach this point so we can decide if they are
2810 // posting an @-tag delivery, which followers are allowed to do for certain
2811 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2813 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2816 // This is my contact on another system, but it's really me.
2817 // Turn this into a wall post.
2818 $notify = item_is_remote_self($contact, $datarray);
2820 $r = item_store($datarray, false, $notify);
2821 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2829 function item_is_remote_self($contact, &$datarray) {
2832 if (!$contact['remote_self'])
2835 // Prevent the forwarding of posts that are forwarded
2836 if ($datarray["extid"] == NETWORK_DFRN)
2839 // Prevent to forward already forwarded posts
2840 if ($datarray["app"] == $a->get_hostname())
2843 // Only forward posts
2844 if ($datarray["verb"] != ACTIVITY_POST)
2847 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2850 $datarray2 = $datarray;
2851 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2852 if ($contact['remote_self'] == 2) {
2853 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2854 intval($contact['uid']));
2856 $datarray['contact-id'] = $r[0]["id"];
2858 $datarray['owner-name'] = $r[0]["name"];
2859 $datarray['owner-link'] = $r[0]["url"];
2860 $datarray['owner-avatar'] = $r[0]["thumb"];
2862 $datarray['author-name'] = $datarray['owner-name'];
2863 $datarray['author-link'] = $datarray['owner-link'];
2864 $datarray['author-avatar'] = $datarray['owner-avatar'];
2867 if ($contact['network'] != NETWORK_FEED) {
2868 $datarray["guid"] = get_guid(32);
2869 unset($datarray["plink"]);
2870 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2871 $datarray["parent-uri"] = $datarray["uri"];
2872 $datarray["extid"] = $contact['network'];
2873 $urlpart = parse_url($datarray2['author-link']);
2874 $datarray["app"] = $urlpart["host"];
2876 $datarray['private'] = 0;
2879 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2880 // $datarray["app"] = network_to_name($contact['network']);
2882 if ($contact['network'] != NETWORK_FEED) {
2883 // Store the original post
2884 $r = item_store($datarray2, false, false);
2885 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2887 $datarray["app"] = "Feed";
2892 function local_delivery($importer,$data) {
2895 logger(__function__, LOGGER_TRACE);
2897 if($importer['readonly']) {
2898 // We aren't receiving stuff from this person. But we will quietly ignore them
2899 // rather than a blatant "go away" message.
2900 logger('local_delivery: ignoring');
2905 // Consume notification feed. This may differ from consuming a public feed in several ways
2906 // - might contain email or friend suggestions
2907 // - might contain remote followup to our message
2908 // - in which case we need to accept it and then notify other conversants
2909 // - we may need to send various email notifications
2911 $feed = new SimplePie();
2912 $feed->set_raw_data($data);
2913 $feed->enable_order_by_date(false);
2918 logger('local_delivery: Error parsing XML: ' . $feed->error());
2921 // Check at the feed level for updated contact name and/or photo
2925 $photo_timestamp = '';
2927 $contact_updated = '';
2930 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2932 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
2934 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2937 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2938 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2939 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2940 $new_name = $elems['name'][0]['data'];
2942 // Manually checking for changed contact names
2943 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
2944 $name_updated = date("c");
2945 $photo_timestamp = date("c");
2948 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2949 if ($photo_timestamp == "")
2950 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2951 $photo_url = $elems['link'][0]['attribs']['']['href'];
2955 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
2957 $contact_updated = $photo_timestamp;
2959 logger('local_delivery: Updating photo for ' . $importer['name']);
2960 require_once("include/Photo.php");
2961 $photo_failure = false;
2962 $have_photo = false;
2964 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2965 intval($importer['id']),
2966 intval($importer['importer_uid'])
2969 $resource_id = $r[0]['resource-id'];
2973 $resource_id = photo_new_resource();
2976 $img_str = fetch_url($photo_url,true);
2977 // guess mimetype from headers or filename
2978 $type = guess_image_type($photo_url,true);
2981 $img = new Photo($img_str, $type);
2982 if($img->is_valid()) {
2984 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2985 dbesc($resource_id),
2986 intval($importer['id']),
2987 intval($importer['importer_uid'])
2991 $img->scaleImageSquare(175);
2993 $hash = $resource_id;
2994 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2996 $img->scaleImage(80);
2997 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2999 $img->scaleImage(48);
3000 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3004 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3005 WHERE `uid` = %d AND `id` = %d",
3006 dbesc(datetime_convert()),
3007 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3008 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3009 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3010 intval($importer['importer_uid']),
3011 intval($importer['id'])
3016 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3017 if ($name_updated > $contact_updated)
3018 $contact_updated = $name_updated;
3020 $r = q("select * from contact where uid = %d and id = %d limit 1",
3021 intval($importer['importer_uid']),
3022 intval($importer['id'])
3025 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3026 dbesc(notags(trim($new_name))),
3027 dbesc(datetime_convert()),
3028 intval($importer['importer_uid']),
3029 intval($importer['id'])
3032 // do our best to update the name on content items
3035 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3036 dbesc(notags(trim($new_name))),
3037 dbesc($r[0]['name']),
3038 dbesc($r[0]['url']),
3039 intval($importer['importer_uid'])
3044 if ($contact_updated AND $new_name AND $photo_url)
3045 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3047 // Currently unsupported - needs a lot of work
3048 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3049 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3050 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3052 $newloc['uid'] = $importer['importer_uid'];
3053 $newloc['cid'] = $importer['id'];
3054 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3055 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3056 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3057 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3058 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3059 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3060 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3061 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3062 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3063 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3064 /** relocated user must have original key pair */
3065 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3066 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3068 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3071 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3072 intval($importer['id']),
3073 intval($importer['importer_uid']));
3078 $x = q("UPDATE contact SET
3089 `site-pubkey` = '%s'
3090 WHERE id=%d AND uid=%d;",
3091 dbesc($newloc['name']),
3092 dbesc($newloc['photo']),
3093 dbesc($newloc['thumb']),
3094 dbesc($newloc['micro']),
3095 dbesc($newloc['url']),
3096 dbesc(normalise_link($newloc['url'])),
3097 dbesc($newloc['request']),
3098 dbesc($newloc['confirm']),
3099 dbesc($newloc['notify']),
3100 dbesc($newloc['poll']),
3101 dbesc($newloc['sitepubkey']),
3102 intval($importer['id']),
3103 intval($importer['importer_uid']));
3109 'owner-link' => array($old['url'], $newloc['url']),
3110 'author-link' => array($old['url'], $newloc['url']),
3111 'owner-avatar' => array($old['photo'], $newloc['photo']),
3112 'author-avatar' => array($old['photo'], $newloc['photo']),
3114 foreach ($fields as $n=>$f){
3115 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3118 intval($importer['importer_uid']));
3124 // merge with current record, current contents have priority
3125 // update record, set url-updated
3126 // update profile photos
3132 // handle friend suggestion notification
3134 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3135 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3136 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3138 $fsugg['uid'] = $importer['importer_uid'];
3139 $fsugg['cid'] = $importer['id'];
3140 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3141 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3142 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3143 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3144 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3146 // Does our member already have a friend matching this description?
3148 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3149 dbesc($fsugg['name']),
3150 dbesc(normalise_link($fsugg['url'])),
3151 intval($fsugg['uid'])
3156 // Do we already have an fcontact record for this person?
3159 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3160 dbesc($fsugg['url']),
3161 dbesc($fsugg['name']),
3162 dbesc($fsugg['request'])
3167 // OK, we do. Do we already have an introduction for this person ?
3168 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3169 intval($fsugg['uid']),
3176 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3177 dbesc($fsugg['name']),
3178 dbesc($fsugg['url']),
3179 dbesc($fsugg['photo']),
3180 dbesc($fsugg['request'])
3182 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3183 dbesc($fsugg['url']),
3184 dbesc($fsugg['name']),
3185 dbesc($fsugg['request'])
3190 // database record did not get created. Quietly give up.
3195 $hash = random_string();
3197 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3198 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3199 intval($fsugg['uid']),
3201 intval($fsugg['cid']),
3202 dbesc($fsugg['body']),
3204 dbesc(datetime_convert()),
3209 'type' => NOTIFY_SUGGEST,
3210 'notify_flags' => $importer['notify-flags'],
3211 'language' => $importer['language'],
3212 'to_name' => $importer['username'],
3213 'to_email' => $importer['email'],
3214 'uid' => $importer['importer_uid'],
3216 'link' => $a->get_baseurl() . '/notifications/intros',
3217 'source_name' => $importer['name'],
3218 'source_link' => $importer['url'],
3219 'source_photo' => $importer['photo'],
3220 'verb' => ACTIVITY_REQ_FRIEND,
3229 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3230 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3232 logger('local_delivery: private message received');
3235 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3238 $msg['uid'] = $importer['importer_uid'];
3239 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3240 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3241 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3242 $msg['contact-id'] = $importer['id'];
3243 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3244 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3246 $msg['replied'] = 0;
3247 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3248 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3249 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3253 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3254 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3256 // send notifications.
3258 require_once('include/enotify.php');
3260 $notif_params = array(
3261 'type' => NOTIFY_MAIL,
3262 'notify_flags' => $importer['notify-flags'],
3263 'language' => $importer['language'],
3264 'to_name' => $importer['username'],
3265 'to_email' => $importer['email'],
3266 'uid' => $importer['importer_uid'],
3268 'source_name' => $msg['from-name'],
3269 'source_link' => $importer['url'],
3270 'source_photo' => $importer['thumb'],
3271 'verb' => ACTIVITY_POST,
3275 notification($notif_params);
3281 $community_page = 0;
3282 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3284 $community_page = intval($rawtags[0]['data']);
3286 if(intval($importer['forum']) != $community_page) {
3287 q("update contact set forum = %d where id = %d",
3288 intval($community_page),
3289 intval($importer['id'])
3291 $importer['forum'] = (string) $community_page;
3294 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3296 // process any deleted entries
3298 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3299 if(is_array($del_entries) && count($del_entries)) {
3300 foreach($del_entries as $dentry) {
3302 if(isset($dentry['attribs']['']['ref'])) {
3303 $uri = $dentry['attribs']['']['ref'];
3305 if(isset($dentry['attribs']['']['when'])) {
3306 $when = $dentry['attribs']['']['when'];
3307 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3310 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3314 // check for relayed deletes to our conversation
3317 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3319 intval($importer['importer_uid'])
3322 $parent_uri = $r[0]['parent-uri'];
3323 if($r[0]['id'] != $r[0]['parent'])
3330 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3333 logger('local_delivery: possible community delete');
3336 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3338 // was the top-level post for this reply written by somebody on this site?
3339 // Specifically, the recipient?
3341 $is_a_remote_delete = false;
3343 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3344 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3345 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3346 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3347 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3348 AND `item`.`uid` = %d
3354 intval($importer['importer_uid'])
3357 $is_a_remote_delete = true;
3359 // Does this have the characteristics of a community or private group comment?
3360 // If it's a reply to a wall post on a community/prvgroup page it's a
3361 // valid community comment. Also forum_mode makes it valid for sure.
3362 // If neither, it's not.
3364 if($is_a_remote_delete && $community) {
3365 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3366 $is_a_remote_delete = false;
3367 logger('local_delivery: not a community delete');
3371 if($is_a_remote_delete) {
3372 logger('local_delivery: received remote delete');
3376 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3377 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3379 intval($importer['importer_uid']),
3380 intval($importer['id'])
3386 if($item['deleted'])
3389 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3391 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3392 $xo = parse_xml_string($item['object'],false);
3393 $xt = parse_xml_string($item['target'],false);
3395 if($xt->type === ACTIVITY_OBJ_NOTE) {
3396 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3398 intval($importer['importer_uid'])
3402 // For tags, the owner cannot remove the tag on the author's copy of the post.
3404 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3405 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3406 $author_copy = (($item['origin']) ? true : false);
3408 if($owner_remove && $author_copy)
3410 if($author_remove || $owner_remove) {
3411 $tags = explode(',',$i[0]['tag']);
3414 foreach($tags as $tag)
3415 if(trim($tag) !== trim($xo->body))
3416 $newtags[] = trim($tag);
3418 q("update item set tag = '%s' where id = %d",
3419 dbesc(implode(',',$newtags)),
3422 create_tags_from_item($i[0]['id']);
3428 if($item['uri'] == $item['parent-uri']) {
3429 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3430 `body` = '', `title` = ''
3431 WHERE `parent-uri` = '%s' AND `uid` = %d",
3433 dbesc(datetime_convert()),
3434 dbesc($item['uri']),
3435 intval($importer['importer_uid'])
3437 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3438 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3439 update_thread_uri($item['uri'], $importer['importer_uid']);
3442 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3443 `body` = '', `title` = ''
3444 WHERE `uri` = '%s' AND `uid` = %d",
3446 dbesc(datetime_convert()),
3448 intval($importer['importer_uid'])
3450 create_tags_from_itemuri($uri, $importer['importer_uid']);
3451 create_files_from_itemuri($uri, $importer['importer_uid']);
3452 update_thread_uri($uri, $importer['importer_uid']);
3453 if($item['last-child']) {
3454 // ensure that last-child is set in case the comment that had it just got wiped.
3455 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3456 dbesc(datetime_convert()),
3457 dbesc($item['parent-uri']),
3458 intval($item['uid'])
3460 // who is the last child now?
3461 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3462 ORDER BY `created` DESC LIMIT 1",
3463 dbesc($item['parent-uri']),
3464 intval($importer['importer_uid'])
3467 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3472 // if this is a relayed delete, propagate it to other recipients
3474 if($is_a_remote_delete)
3475 proc_run('php',"include/notifier.php","drop",$item['id']);
3483 foreach($feed->get_items() as $item) {
3486 $item_id = $item->get_id();
3487 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3488 if(isset($rawthread[0]['attribs']['']['ref'])) {
3490 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3496 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3499 logger('local_delivery: possible community reply');
3502 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3504 // was the top-level post for this reply written by somebody on this site?
3505 // Specifically, the recipient?
3507 $is_a_remote_comment = false;
3508 $top_uri = $parent_uri;
3510 $r = q("select `item`.`parent-uri` from `item`
3511 WHERE `item`.`uri` = '%s'
3515 if($r && count($r)) {
3516 $top_uri = $r[0]['parent-uri'];
3518 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3519 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3520 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3521 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3522 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3523 AND `item`.`uid` = %d
3529 intval($importer['importer_uid'])
3532 $is_a_remote_comment = true;
3535 // Does this have the characteristics of a community or private group comment?
3536 // If it's a reply to a wall post on a community/prvgroup page it's a
3537 // valid community comment. Also forum_mode makes it valid for sure.
3538 // If neither, it's not.
3540 if($is_a_remote_comment && $community) {
3541 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3542 $is_a_remote_comment = false;
3543 logger('local_delivery: not a community reply');
3547 if($is_a_remote_comment) {
3548 logger('local_delivery: received remote comment');
3550 // remote reply to our post. Import and then notify everybody else.
3552 $datarray = get_atom_elements($feed, $item);
3554 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3556 intval($importer['importer_uid'])
3559 // Update content if 'updated' changes
3563 if (edited_timestamp_is_newer($r[0], $datarray)) {
3565 // do not accept (ignore) an earlier edit than one we currently have.
3566 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3569 logger('received updated comment' , LOGGER_DEBUG);
3570 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3571 dbesc($datarray['title']),
3572 dbesc($datarray['body']),
3573 dbesc($datarray['tag']),
3574 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3575 dbesc(datetime_convert()),
3577 intval($importer['importer_uid'])
3579 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3581 proc_run('php',"include/notifier.php","comment-import",$iid);
3590 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3591 intval($importer['importer_uid'])
3595 $datarray['type'] = 'remote-comment';
3596 $datarray['wall'] = 1;
3597 $datarray['parent-uri'] = $parent_uri;
3598 $datarray['uid'] = $importer['importer_uid'];
3599 $datarray['owner-name'] = $own[0]['name'];
3600 $datarray['owner-link'] = $own[0]['url'];
3601 $datarray['owner-avatar'] = $own[0]['thumb'];
3602 $datarray['contact-id'] = $importer['id'];
3604 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3606 $datarray['type'] = 'activity';
3607 $datarray['gravity'] = GRAVITY_LIKE;
3608 $datarray['last-child'] = 0;
3609 // only one like or dislike per person
3610 // splitted into two queries for performance issues
3611 $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",
3612 intval($datarray['uid']),
3613 intval($datarray['contact-id']),
3614 dbesc($datarray['verb']),
3615 dbesc($datarray['parent-uri'])
3621 $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",
3622 intval($datarray['uid']),
3623 intval($datarray['contact-id']),
3624 dbesc($datarray['verb']),
3625 dbesc($datarray['parent-uri'])
3632 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3634 $xo = parse_xml_string($datarray['object'],false);
3635 $xt = parse_xml_string($datarray['target'],false);
3637 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3639 // fetch the parent item
3641 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3643 intval($importer['importer_uid'])
3648 // extract tag, if not duplicate, and this user allows tags, add to parent item
3650 if($xo->id && $xo->content) {
3651 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3652 if(! (stristr($tagp[0]['tag'],$newtag))) {
3653 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3654 intval($importer['importer_uid'])
3656 if(count($i) && ! intval($i[0]['blocktags'])) {
3657 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3658 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3659 intval($tagp[0]['id']),
3660 dbesc(datetime_convert()),
3661 dbesc(datetime_convert())
3663 create_tags_from_item($tagp[0]['id']);
3671 $posted_id = item_store($datarray);
3676 $datarray["id"] = $posted_id;
3678 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3680 intval($importer['importer_uid'])
3683 $parent = $r[0]['parent'];
3684 $parent_uri = $r[0]['parent-uri'];
3688 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3689 dbesc(datetime_convert()),
3690 intval($importer['importer_uid']),
3691 intval($r[0]['parent'])
3694 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3695 dbesc(datetime_convert()),
3696 intval($importer['importer_uid']),
3701 if($posted_id && $parent) {
3703 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3705 if((! $is_like) && (! $importer['self'])) {
3707 require_once('include/enotify.php');
3710 'type' => NOTIFY_COMMENT,
3711 'notify_flags' => $importer['notify-flags'],
3712 'language' => $importer['language'],
3713 'to_name' => $importer['username'],
3714 'to_email' => $importer['email'],
3715 'uid' => $importer['importer_uid'],
3716 'item' => $datarray,
3717 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3718 'source_name' => stripslashes($datarray['author-name']),
3719 'source_link' => $datarray['author-link'],
3720 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3721 ? $importer['thumb'] : $datarray['author-avatar']),
3722 'verb' => ACTIVITY_POST,
3724 'parent' => $parent,
3725 'parent_uri' => $parent_uri,
3737 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3739 $item_id = $item->get_id();
3740 $datarray = get_atom_elements($feed,$item);
3742 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3745 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3747 intval($importer['importer_uid'])
3750 // Update content if 'updated' changes
3753 if (edited_timestamp_is_newer($r[0], $datarray)) {
3755 // do not accept (ignore) an earlier edit than one we currently have.
3756 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3759 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3760 dbesc($datarray['title']),
3761 dbesc($datarray['body']),
3762 dbesc($datarray['tag']),
3763 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3764 dbesc(datetime_convert()),
3766 intval($importer['importer_uid'])
3768 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3771 // update last-child if it changes
3773 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3774 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3775 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3776 dbesc(datetime_convert()),
3778 intval($importer['importer_uid'])
3780 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3781 intval($allow[0]['data']),
3782 dbesc(datetime_convert()),
3784 intval($importer['importer_uid'])
3790 $datarray['parent-uri'] = $parent_uri;
3791 $datarray['uid'] = $importer['importer_uid'];
3792 $datarray['contact-id'] = $importer['id'];
3793 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3794 $datarray['type'] = 'activity';
3795 $datarray['gravity'] = GRAVITY_LIKE;
3796 // only one like or dislike per person
3797 // splitted into two queries for performance issues
3798 $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",
3799 intval($datarray['uid']),
3800 intval($datarray['contact-id']),
3801 dbesc($datarray['verb']),
3807 $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",
3808 intval($datarray['uid']),
3809 intval($datarray['contact-id']),
3810 dbesc($datarray['verb']),
3818 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3820 $xo = parse_xml_string($datarray['object'],false);
3821 $xt = parse_xml_string($datarray['target'],false);
3823 if($xt->type == ACTIVITY_OBJ_NOTE) {
3824 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3826 intval($importer['importer_uid'])
3831 // extract tag, if not duplicate, add to parent item
3833 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3834 q("UPDATE item SET tag = '%s' WHERE id = %d",
3835 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3838 create_tags_from_item($r[0]['id']);
3844 $posted_id = item_store($datarray);
3846 // find out if our user is involved in this conversation and wants to be notified.
3848 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3850 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3852 intval($importer['importer_uid'])
3855 if(count($myconv)) {
3856 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3858 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3859 if(! link_compare($datarray['author-link'],$importer_url)) {
3862 foreach($myconv as $conv) {
3864 // now if we find a match, it means we're in this conversation
3866 if(! link_compare($conv['author-link'],$importer_url))
3869 require_once('include/enotify.php');
3871 $conv_parent = $conv['parent'];
3874 'type' => NOTIFY_COMMENT,
3875 'notify_flags' => $importer['notify-flags'],
3876 'language' => $importer['language'],
3877 'to_name' => $importer['username'],
3878 'to_email' => $importer['email'],
3879 'uid' => $importer['importer_uid'],
3880 'item' => $datarray,
3881 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3882 'source_name' => stripslashes($datarray['author-name']),
3883 'source_link' => $datarray['author-link'],
3884 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3885 ? $importer['thumb'] : $datarray['author-avatar']),
3886 'verb' => ACTIVITY_POST,
3888 'parent' => $conv_parent,
3889 'parent_uri' => $parent_uri
3893 // only send one notification
3905 // Head post of a conversation. Have we seen it? If not, import it.
3908 $item_id = $item->get_id();
3909 $datarray = get_atom_elements($feed,$item);
3911 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
3912 $ev = bbtoevent($datarray['body']);
3913 if(x($ev,'desc') && x($ev,'start')) {
3914 $ev['cid'] = $importer['id'];
3915 $ev['uid'] = $importer['uid'];
3916 $ev['uri'] = $item_id;
3917 $ev['edited'] = $datarray['edited'];
3918 $ev['private'] = $datarray['private'];
3920 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3922 intval($importer['uid'])
3925 $ev['id'] = $r[0]['id'];
3926 $xyz = event_store($ev);
3931 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3933 intval($importer['importer_uid'])
3936 // Update content if 'updated' changes
3939 if (edited_timestamp_is_newer($r[0], $datarray)) {
3941 // do not accept (ignore) an earlier edit than one we currently have.
3942 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3945 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3946 dbesc($datarray['title']),
3947 dbesc($datarray['body']),
3948 dbesc($datarray['tag']),
3949 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3950 dbesc(datetime_convert()),
3952 intval($importer['importer_uid'])
3954 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3955 update_thread_uri($item_id, $importer['importer_uid']);
3958 // update last-child if it changes
3960 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3961 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
3962 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3963 intval($allow[0]['data']),
3964 dbesc(datetime_convert()),
3966 intval($importer['importer_uid'])
3972 $datarray['parent-uri'] = $item_id;
3973 $datarray['uid'] = $importer['importer_uid'];
3974 $datarray['contact-id'] = $importer['id'];
3977 if(! link_compare($datarray['owner-link'],$importer['url'])) {
3978 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
3979 // but otherwise there's a possible data mixup on the sender's system.
3980 // the tgroup delivery code called from item_store will correct it if it's a forum,
3981 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
3982 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
3983 $datarray['owner-name'] = $importer['senderName'];
3984 $datarray['owner-link'] = $importer['url'];
3985 $datarray['owner-avatar'] = $importer['thumb'];
3988 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
3991 // This is my contact on another system, but it's really me.
3992 // Turn this into a wall post.
3993 $notify = item_is_remote_self($importer, $datarray);
3995 $posted_id = item_store($datarray, false, $notify);
3997 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
3998 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4001 $xo = parse_xml_string($datarray['object'],false);
4003 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4005 // somebody was poked/prodded. Was it me?
4007 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4009 foreach($links->link as $l) {
4010 $atts = $l->attributes();
4011 switch($atts['rel']) {
4013 $Blink = $atts['href'];
4019 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4021 // send a notification
4022 require_once('include/enotify.php');
4025 'type' => NOTIFY_POKE,
4026 'notify_flags' => $importer['notify-flags'],
4027 'language' => $importer['language'],
4028 'to_name' => $importer['username'],
4029 'to_email' => $importer['email'],
4030 'uid' => $importer['importer_uid'],
4031 'item' => $datarray,
4032 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4033 'source_name' => stripslashes($datarray['author-name']),
4034 'source_link' => $datarray['author-link'],
4035 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4036 ? $importer['thumb'] : $datarray['author-avatar']),
4037 'verb' => $datarray['verb'],
4038 'otype' => 'person',
4039 'activity' => $verb,
4040 'parent' => $datarray['parent']
4056 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4057 $url = notags(trim($datarray['author-link']));
4058 $name = notags(trim($datarray['author-name']));
4059 $photo = notags(trim($datarray['author-avatar']));
4061 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4062 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4063 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4065 if(is_array($contact)) {
4066 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4067 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4068 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4069 intval(CONTACT_IS_FRIEND),
4070 intval($contact['id']),
4071 intval($importer['uid'])
4074 // send email notification to owner?
4078 // create contact record
4080 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4081 `blocked`, `readonly`, `pending`, `writable` )
4082 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4083 intval($importer['uid']),
4084 dbesc(datetime_convert()),
4086 dbesc(normalise_link($url)),
4090 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4091 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4093 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4094 intval($importer['uid']),
4098 $contact_record = $r[0];
4100 // create notification
4101 $hash = random_string();
4103 if(is_array($contact_record)) {
4104 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4105 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4106 intval($importer['uid']),
4107 intval($contact_record['id']),
4109 dbesc(datetime_convert())
4113 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4114 intval($importer['uid'])
4119 if(intval($r[0]['def_gid'])) {
4120 require_once('include/group.php');
4121 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4124 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4125 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4128 'type' => NOTIFY_INTRO,
4129 'notify_flags' => $r[0]['notify-flags'],
4130 'language' => $r[0]['language'],
4131 'to_name' => $r[0]['username'],
4132 'to_email' => $r[0]['email'],
4133 'uid' => $r[0]['uid'],
4134 'link' => $a->get_baseurl() . '/notifications/intro',
4135 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4136 'source_link' => $contact_record['url'],
4137 'source_photo' => $contact_record['photo'],
4138 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4147 function lose_follower($importer,$contact,$datarray,$item) {
4149 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4150 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4151 intval(CONTACT_IS_SHARING),
4152 intval($contact['id'])
4156 contact_remove($contact['id']);
4160 function lose_sharer($importer,$contact,$datarray,$item) {
4162 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4163 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4164 intval(CONTACT_IS_FOLLOWER),
4165 intval($contact['id'])
4169 contact_remove($contact['id']);
4174 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4178 if(is_array($importer)) {
4179 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4180 intval($importer['uid'])
4184 // Diaspora has different message-ids in feeds than they do
4185 // through the direct Diaspora protocol. If we try and use
4186 // the feed, we'll get duplicates. So don't.
4188 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4191 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4193 // Use a single verify token, even if multiple hubs
4195 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4197 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4199 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4201 if(! strlen($contact['hub-verify'])) {
4202 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4203 dbesc($verify_token),
4204 intval($contact['id'])
4208 post_url($url,$params);
4210 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4217 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4221 $name = xmlify($name);
4222 $uri = xmlify($uri);
4225 $photo = xmlify($photo);
4229 $o .= "<name>$name</name>\r\n";
4230 $o .= "<uri>$uri</uri>\r\n";
4231 $o .= '<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4232 $o .= '<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4234 call_hooks('atom_author', $o);
4236 $o .= "</$tag>\r\n";
4240 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4244 if(! $item['parent'])
4247 if($item['deleted'])
4248 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4251 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4252 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4254 $body = $item['body'];
4257 $o = "\r\n\r\n<entry>\r\n";
4259 if(is_array($author))
4260 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4262 $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']));
4263 if(strlen($item['owner-name']))
4264 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4266 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4267 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4268 $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['parent']) . '" />' . "\r\n";
4273 if ($item['title'] != "")
4274 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4276 $htmlbody = bbcode(bb_remove_share_information($htmlbody), false, false, 7);
4278 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4279 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4280 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4281 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4282 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4283 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4284 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id']) . '" />' . "\r\n";
4288 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4290 if($item['location']) {
4291 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4292 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4296 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4298 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4299 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4302 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4303 if($item['bookmark'])
4304 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4307 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4310 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4312 if($item['signed_text']) {
4313 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4314 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4317 $verb = construct_verb($item);
4318 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4319 $actobj = construct_activity_object($item);
4322 $actarg = construct_activity_target($item);
4326 $tags = item_getfeedtags($item);
4328 foreach($tags as $t) {
4329 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4333 $o .= item_getfeedattach($item);
4335 $mentioned = get_mentions($item);
4339 call_hooks('atom_entry', $o);
4341 $o .= '</entry>' . "\r\n";
4346 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4348 if(get_config('system','disable_embedded'))
4353 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4354 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4359 $img_start = strpos($orig_body, '[img');
4360 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4361 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4362 while( ($img_st_close !== false) && ($img_len !== false) ) {
4364 $img_st_close++; // make it point to AFTER the closing bracket
4365 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4367 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4370 if(stristr($image , $site . '/photo/')) {
4371 // Only embed locally hosted photos
4373 $i = basename($image);
4374 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4375 $x = strpos($i,'-');
4378 $res = substr($i,$x+1);
4379 $i = substr($i,0,$x);
4380 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4387 // Check to see if we should replace this photo link with an embedded image
4388 // 1. No need to do so if the photo is public
4389 // 2. If there's a contact-id provided, see if they're in the access list
4390 // for the photo. If so, embed it.
4391 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4392 // permissions, regardless of order but first check to see if they're an exact
4393 // match to save some processing overhead.
4395 if(has_permissions($r[0])) {
4397 $recips = enumerate_permissions($r[0]);
4398 if(in_array($cid, $recips)) {
4403 if(compare_permissions($item,$r[0]))
4408 $data = $r[0]['data'];
4409 $type = $r[0]['type'];
4411 // If a custom width and height were specified, apply before embedding
4412 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4413 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4415 $width = intval($match[1]);
4416 $height = intval($match[2]);
4418 $ph = new Photo($data, $type);
4419 if($ph->is_valid()) {
4420 $ph->scaleImage(max($width, $height));
4421 $data = $ph->imageString();
4422 $type = $ph->getType();
4426 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4427 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4428 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4434 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4435 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4436 if($orig_body === false)
4439 $img_start = strpos($orig_body, '[img');
4440 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4441 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4444 $new_body = $new_body . $orig_body;
4450 function has_permissions($obj) {
4451 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4456 function compare_permissions($obj1,$obj2) {
4457 // first part is easy. Check that these are exactly the same.
4458 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4459 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4460 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4461 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4464 // This is harder. Parse all the permissions and compare the resulting set.
4466 $recipients1 = enumerate_permissions($obj1);
4467 $recipients2 = enumerate_permissions($obj2);
4470 if($recipients1 == $recipients2)
4475 // returns an array of contact-ids that are allowed to see this object
4477 function enumerate_permissions($obj) {
4478 require_once('include/group.php');
4479 $allow_people = expand_acl($obj['allow_cid']);
4480 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4481 $deny_people = expand_acl($obj['deny_cid']);
4482 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4483 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4484 $deny = array_unique(array_merge($deny_people,$deny_groups));
4485 $recipients = array_diff($recipients,$deny);
4489 function item_getfeedtags($item) {
4492 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4494 for($x = 0; $x < $cnt; $x ++) {
4496 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4500 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4502 for($x = 0; $x < $cnt; $x ++) {
4504 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4510 function item_getfeedattach($item) {
4512 $arr = explode('[/attach],',$item['attach']);
4514 foreach($arr as $r) {
4516 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4518 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4519 if(intval($matches[2]))
4520 $ret .= 'length="' . intval($matches[2]) . '" ';
4521 if($matches[4] !== ' ')
4522 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4523 $ret .= ' />' . "\r\n";
4532 function item_expire($uid, $days, $network = "", $force = false) {
4534 if((! $uid) || ($days < 1))
4537 // $expire_network_only = save your own wall posts
4538 // and just expire conversations started by others
4540 $expire_network_only = get_pconfig($uid,'expire','network_only');
4541 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4543 if ($network != "") {
4544 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4545 // There is an index "uid_network_received" but not "uid_network_created"
4546 // This avoids the creation of another index just for one purpose.
4547 // And it doesn't really matter wether to look at "received" or "created"
4548 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4550 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4552 $r = q("SELECT * FROM `item`
4553 WHERE `uid` = %d $range
4564 $expire_items = get_pconfig($uid, 'expire','items');
4565 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4567 // Forcing expiring of items - but not notes and marked items
4569 $expire_items = true;
4571 $expire_notes = get_pconfig($uid, 'expire','notes');
4572 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4574 $expire_starred = get_pconfig($uid, 'expire','starred');
4575 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4577 $expire_photos = get_pconfig($uid, 'expire','photos');
4578 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4580 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4582 foreach($r as $item) {
4584 // don't expire filed items
4586 if(strpos($item['file'],'[') !== false)
4589 // Only expire posts, not photos and photo comments
4591 if($expire_photos==0 && strlen($item['resource-id']))
4593 if($expire_starred==0 && intval($item['starred']))
4595 if($expire_notes==0 && $item['type']=='note')
4597 if($expire_items==0 && $item['type']!='note')
4600 drop_item($item['id'],false);
4603 proc_run('php',"include/notifier.php","expire","$uid");
4608 function drop_items($items) {
4611 if(! local_user() && ! remote_user())
4615 foreach($items as $item) {
4616 $owner = drop_item($item,false);
4617 if($owner && ! $uid)
4622 // multiple threads may have been deleted, send an expire notification
4625 proc_run('php',"include/notifier.php","expire","$uid");
4629 function drop_item($id,$interactive = true) {
4633 // locate item to be deleted
4635 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4642 notice( t('Item not found.') . EOL);
4643 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4648 $owner = $item['uid'];
4652 // check if logged in user is either the author or owner of this item
4654 if(is_array($_SESSION['remote'])) {
4655 foreach($_SESSION['remote'] as $visitor) {
4656 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4657 $cid = $visitor['cid'];
4664 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4666 // Check if we should do HTML-based delete confirmation
4667 if($_REQUEST['confirm']) {
4668 // <form> can't take arguments in its "action" parameter
4669 // so add any arguments as hidden inputs
4670 $query = explode_querystring($a->query_string);
4672 foreach($query['args'] as $arg) {
4673 if(strpos($arg, 'confirm=') === false) {
4674 $arg_parts = explode('=', $arg);
4675 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4679 return replace_macros(get_markup_template('confirm.tpl'), array(
4681 '$message' => t('Do you really want to delete this item?'),
4682 '$extra_inputs' => $inputs,
4683 '$confirm' => t('Yes'),
4684 '$confirm_url' => $query['base'],
4685 '$confirm_name' => 'confirmed',
4686 '$cancel' => t('Cancel'),
4689 // Now check how the user responded to the confirmation query
4690 if($_REQUEST['canceled']) {
4691 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4694 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4697 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4698 dbesc(datetime_convert()),
4699 dbesc(datetime_convert()),
4702 create_tags_from_item($item['id']);
4703 create_files_from_item($item['id']);
4704 delete_thread($item['id'], $item['parent-uri']);
4706 // clean up categories and tags so they don't end up as orphans
4709 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4711 foreach($matches as $mtch) {
4712 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4718 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4720 foreach($matches as $mtch) {
4721 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4725 // If item is a link to a photo resource, nuke all the associated photos
4726 // (visitors will not have photo resources)
4727 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4728 // generate a resource-id and therefore aren't intimately linked to the item.
4730 if(strlen($item['resource-id'])) {
4731 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4732 dbesc($item['resource-id']),
4733 intval($item['uid'])
4735 // ignore the result
4738 // If item is a link to an event, nuke the event record.
4740 if(intval($item['event-id'])) {
4741 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4742 intval($item['event-id']),
4743 intval($item['uid'])
4745 // ignore the result
4748 // If item has attachments, drop them
4750 foreach(explode(",",$item['attach']) as $attach){
4751 preg_match("|attach/(\d+)|", $attach, $matches);
4752 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4753 intval($matches[1]),
4756 // ignore the result
4760 // clean up item_id and sign meta-data tables
4763 // Old code - caused very long queries and warning entries in the mysql logfiles:
4765 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4766 intval($item['id']),
4767 intval($item['uid'])
4770 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4771 intval($item['id']),
4772 intval($item['uid'])
4776 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4778 // Creating list of parents
4779 $r = q("select id from item where parent = %d and uid = %d",
4780 intval($item['id']),
4781 intval($item['uid'])
4786 foreach ($r AS $row) {
4787 if ($parentid != "")
4790 $parentid .= $row["id"];
4794 if ($parentid != "") {
4795 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4797 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4800 // If it's the parent of a comment thread, kill all the kids
4802 if($item['uri'] == $item['parent-uri']) {
4803 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4804 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4805 dbesc(datetime_convert()),
4806 dbesc(datetime_convert()),
4807 dbesc($item['parent-uri']),
4808 intval($item['uid'])
4810 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4811 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4812 delete_thread_uri($item['parent-uri'], $item['uid']);
4813 // ignore the result
4816 // ensure that last-child is set in case the comment that had it just got wiped.
4817 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4818 dbesc(datetime_convert()),
4819 dbesc($item['parent-uri']),
4820 intval($item['uid'])
4822 // who is the last child now?
4823 $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",
4824 dbesc($item['parent-uri']),
4825 intval($item['uid'])
4828 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
4833 // Add a relayable_retraction signature for Diaspora.
4834 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
4837 $drop_id = intval($item['id']);
4839 // send the notification upstream/downstream as the case may be
4841 proc_run('php',"include/notifier.php","drop","$drop_id");
4845 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4851 notice( t('Permission denied.') . EOL);
4852 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4859 function first_post_date($uid,$wall = false) {
4860 $r = q("select id, created from item
4861 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
4863 order by created asc limit 1",
4865 intval($wall ? 1 : 0)
4868 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
4869 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
4874 /* modified posted_dates() {below} to arrange the list in years */
4875 function list_post_dates($uid, $wall) {
4876 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4878 $dthen = first_post_date($uid, $wall);
4882 // Set the start and end date to the beginning of the month
4883 $dnow = substr($dnow,0,8).'01';
4884 $dthen = substr($dthen,0,8).'01';
4888 // Starting with the current month, get the first and last days of every
4889 // month down to and including the month of the first post
4890 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4891 $dyear = intval(substr($dnow,0,4));
4892 $dstart = substr($dnow,0,8) . '01';
4893 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4894 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4895 $end_month = datetime_convert('','',$dend,'Y-m-d');
4896 $str = day_translate(datetime_convert('','',$dnow,'F'));
4898 $ret[$dyear] = array();
4899 $ret[$dyear][] = array($str,$end_month,$start_month);
4900 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4905 function posted_dates($uid,$wall) {
4906 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
4908 $dthen = first_post_date($uid,$wall);
4912 // Set the start and end date to the beginning of the month
4913 $dnow = substr($dnow,0,8).'01';
4914 $dthen = substr($dthen,0,8).'01';
4917 // Starting with the current month, get the first and last days of every
4918 // month down to and including the month of the first post
4919 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
4920 $dstart = substr($dnow,0,8) . '01';
4921 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
4922 $start_month = datetime_convert('','',$dstart,'Y-m-d');
4923 $end_month = datetime_convert('','',$dend,'Y-m-d');
4924 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
4925 $ret[] = array($str,$end_month,$start_month);
4926 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
4932 function posted_date_widget($url,$uid,$wall) {
4935 if(! feature_enabled($uid,'archives'))
4938 // For former Facebook folks that left because of "timeline"
4940 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
4943 $visible_years = get_pconfig($uid,'system','archive_visible_years');
4944 if(! $visible_years)
4947 $ret = list_post_dates($uid,$wall);
4952 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
4953 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
4955 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
4956 '$title' => t('Archives'),
4957 '$size' => $visible_years,
4958 '$cutoff_year' => $cutoff_year,
4959 '$cutoff' => $cutoff,
4962 '$showmore' => t('show more')
4968 function store_diaspora_retract_sig($item, $user, $baseurl) {
4969 // Note that we can't add a target_author_signature
4970 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
4971 // the comment, that means we're the home of the post, and Diaspora will only
4972 // check the parent_author_signature of retractions that it doesn't have to relay further
4974 // I don't think this function gets called for an "unlike," but I'll check anyway
4976 $enabled = intval(get_config('system','diaspora_enabled'));
4978 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
4982 logger('drop_item: storing diaspora retraction signature');
4984 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
4986 if(local_user() == $item['uid']) {
4988 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
4989 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
4992 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
4993 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
4996 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
4997 // only handles DFRN deletes
4998 $handle_baseurl_start = strpos($r['url'],'://') + 3;
4999 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5000 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5006 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5007 intval($item['id']),
5008 dbesc($signed_text),