3 require_once('include/bbcode.php');
4 require_once('include/oembed.php');
5 require_once('include/salmon.php');
6 require_once('include/crypto.php');
7 require_once('include/Photo.php');
8 require_once('include/tags.php');
9 require_once('include/files.php');
10 require_once('include/text.php');
11 require_once('include/email.php');
12 require_once('include/threads.php');
13 require_once('include/socgraph.php');
14 require_once('include/plaintext.php');
15 require_once('include/ostatus.php');
16 require_once('mod/share.php');
18 require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
21 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0, $forpubsub = false) {
24 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
25 $public_feed = (($dfrn_id) ? false : true);
26 $starred = false; // not yet implemented, possible security issues
29 if($public_feed && $a->argc > 2) {
30 for($x = 2; $x < $a->argc; $x++) {
31 if($a->argv[$x] == 'converse')
33 if($a->argv[$x] == 'starred')
35 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
36 $category = $a->argv[$x+1];
42 // default permissions - anonymous user
44 $sql_extra = " AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' ";
46 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
47 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
48 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
56 $owner_id = $owner['user_uid'];
57 $owner_nick = $owner['nickname'];
59 $birthday = feed_birthday($owner_id,$owner['timezone']);
69 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
73 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
74 $my_id = '1:' . $dfrn_id;
77 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
78 $my_id = '0:' . $dfrn_id;
85 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
93 require_once('include/security.php');
94 $groups = init_groups_visitor($contact['id']);
97 for($x = 0; $x < count($groups); $x ++)
98 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
99 $gs = implode('|', $groups);
102 $gs = '<<>>' ; // Impossible to match
104 $sql_extra = sprintf("
105 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
106 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
107 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
108 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
110 intval($contact['id']),
111 intval($contact['id']),
122 // Include answers to status.net posts in pubsub feeds
124 $sql_post_table = "INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent`
125 LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid`";
126 $visibility = sprintf("AND (`item`.`parent` = `item`.`id`) OR (`item`.`network` = '%s' AND ((`thread`.`network`='%s') OR (`thritem`.`network` = '%s')))",
127 dbesc(NETWORK_DFRN), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS));
128 $date_field = "`received`";
129 $sql_order = "`item`.`received` DESC";
131 $date_field = "`changed`";
132 $sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
135 if(! strlen($last_update))
136 $last_update = 'now -30 days';
138 if(isset($category)) {
139 $sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
140 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
141 //$sql_extra .= file_tag_file_query('item',$category,'category');
146 $sql_extra .= " AND `contact`.`self` = 1 ";
149 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
151 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
152 // dbesc($check_date),
154 $r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
155 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
156 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
157 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
158 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
159 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
160 FROM `item` $sql_post_table
161 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
162 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
163 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
164 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
165 AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
167 ORDER BY $sql_order LIMIT 0, 300",
173 // Will check further below if this actually returned results.
174 // We will provide an empty feed if that is the case.
178 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
182 $hubxml = feed_hublinks();
184 $salmon = feed_salmonlinks($owner_nick);
186 $alternatelink = $owner['url'];
189 $alternatelink .= "/category/".$category;
191 $atom .= replace_macros($feed_template, array(
192 '$version' => xmlify(FRIENDICA_VERSION),
193 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
194 '$feed_title' => xmlify($owner['name']),
195 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
197 '$salmon' => $salmon,
198 '$alternatelink' => xmlify($alternatelink),
199 '$name' => xmlify($owner['name']),
200 '$profile_page' => xmlify($owner['url']),
201 '$photo' => xmlify($owner['photo']),
202 '$thumb' => xmlify($owner['thumb']),
203 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
204 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
205 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
206 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
207 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
210 call_hooks('atom_feed', $atom);
212 if(! count($items)) {
214 call_hooks('atom_feed_end', $atom);
216 $atom .= '</feed>' . "\r\n";
220 foreach($items as $item) {
222 // prevent private email from leaking.
223 if($item['network'] === NETWORK_MAIL)
226 // public feeds get html, our own nodes use bbcode
230 // catch any email that's in a public conversation and make sure it doesn't leak
238 $atom .= atom_entry($item,$type,null,$owner,true);
241 call_hooks('atom_feed_end', $atom);
243 $atom .= '</feed>' . "\r\n";
249 function construct_verb($item) {
251 return $item['verb'];
252 return ACTIVITY_POST;
255 function construct_activity_object($item) {
257 if($item['object']) {
258 $o = '<as:object>' . "\r\n";
259 $r = parse_xml_string($item['object'],false);
265 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
267 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
269 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
271 if(substr($r->link,0,1) === '<') {
272 // patch up some facebook "like" activity objects that got stored incorrectly
273 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
274 // we can probably remove this hack here and in the following function in a few months time.
275 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
276 $r->link = str_replace('&','&', $r->link);
277 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
281 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
284 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
285 $o .= '</as:object>' . "\r\n";
292 function construct_activity_target($item) {
294 if($item['target']) {
295 $o = '<as:target>' . "\r\n";
296 $r = parse_xml_string($item['target'],false);
300 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
302 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
304 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
306 if(substr($r->link,0,1) === '<') {
307 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
308 $r->link = str_replace('&','&', $r->link);
309 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
313 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
316 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
317 $o .= '</as:target>' . "\r\n";
326 * The purpose of this function is to apply system message length limits to
327 * imported messages without including any embedded photos in the length
329 if(! function_exists('limit_body_size')) {
330 function limit_body_size($body) {
332 // logger('limit_body_size: start', LOGGER_DEBUG);
334 $maxlen = get_max_import_size();
336 // If the length of the body, including the embedded images, is smaller
337 // than the maximum, then don't waste time looking for the images
338 if($maxlen && (strlen($body) > $maxlen)) {
340 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
347 $img_start = strpos($orig_body, '[img');
348 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
349 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
350 while(($img_st_close !== false) && ($img_end !== false)) {
352 $img_st_close++; // make it point to AFTER the closing bracket
353 $img_end += $img_start;
354 $img_end += strlen('[/img]');
356 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
357 // This is an embedded image
359 if( ($textlen + $img_start) > $maxlen ) {
360 if($textlen < $maxlen) {
361 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
362 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
367 $new_body = $new_body . substr($orig_body, 0, $img_start);
368 $textlen += $img_start;
371 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
375 if( ($textlen + $img_end) > $maxlen ) {
376 if($textlen < $maxlen) {
377 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
378 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
383 $new_body = $new_body . substr($orig_body, 0, $img_end);
384 $textlen += $img_end;
387 $orig_body = substr($orig_body, $img_end);
389 if($orig_body === false) // in case the body ends on a closing image tag
392 $img_start = strpos($orig_body, '[img');
393 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
394 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
397 if( ($textlen + strlen($orig_body)) > $maxlen) {
398 if($textlen < $maxlen) {
399 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
400 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
405 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
406 $new_body = $new_body . $orig_body;
407 $textlen += strlen($orig_body);
416 function title_is_body($title, $body) {
418 $title = strip_tags($title);
419 $title = trim($title);
420 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
421 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
423 $body = strip_tags($body);
425 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
426 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
428 if (strlen($title) < strlen($body))
429 $body = substr($body, 0, strlen($title));
431 if (($title != $body) and (substr($title, -3) == "...")) {
432 $pos = strrpos($title, "...");
434 $title = substr($title, 0, $pos);
435 $body = substr($body, 0, $pos);
439 return($title == $body);
444 function get_atom_elements($feed, $item, $contact = array()) {
446 require_once('library/HTMLPurifier.auto.php');
447 require_once('include/html2bbcode.php');
449 $best_photo = array();
453 $author = $item->get_author();
455 $res['author-name'] = unxmlify($author->get_name());
456 $res['author-link'] = unxmlify($author->get_link());
459 $res['author-name'] = unxmlify($feed->get_title());
460 $res['author-link'] = unxmlify($feed->get_permalink());
462 $res['uri'] = unxmlify($item->get_id());
463 $res['title'] = unxmlify($item->get_title());
464 $res['body'] = unxmlify($item->get_content());
465 $res['plink'] = unxmlify($item->get_link(0));
467 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
468 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
470 $res['body'] = nl2br($res['body']);
473 // removing the content of the title if its identically to the body
474 // This helps with auto generated titles e.g. from tumblr
475 if (title_is_body($res["title"], $res["body"]))
479 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
483 // look for a photo. We should check media size and find the best one,
484 // but for now let's just find any author photo
485 // Additionally we look for an alternate author link. On OStatus this one is the one we want.
487 $authorlinks = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
488 if (is_array($authorlinks)) {
489 foreach ($authorlinks as $link) {
490 $linkdata = array_shift($link["attribs"]);
492 if ($linkdata["rel"] == "alternate")
493 $res["author-link"] = $linkdata["href"];
497 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
499 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
500 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
501 foreach($base as $link) {
502 if($link['attribs']['']['rel'] === 'alternate')
503 $res['author-link'] = unxmlify($link['attribs']['']['href']);
505 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
506 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
507 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
512 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
514 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
515 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
516 if($base && count($base)) {
517 foreach($base as $link) {
518 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
519 $res['author-link'] = unxmlify($link['attribs']['']['href']);
520 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
521 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
522 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
528 // No photo/profile-link on the item - look at the feed level
530 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
531 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
532 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
533 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
534 foreach($base as $link) {
535 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
536 $res['author-link'] = unxmlify($link['attribs']['']['href']);
537 if(! $res['author-avatar']) {
538 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
539 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
544 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
546 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
547 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
549 if($base && count($base)) {
550 foreach($base as $link) {
551 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
552 $res['author-link'] = unxmlify($link['attribs']['']['href']);
553 if(! (x($res,'author-avatar'))) {
554 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
555 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
562 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
563 if($apps && $apps[0]['attribs']['']['source']) {
564 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
565 if($res['app'] === 'web')
566 $res['app'] = 'OStatus';
569 // base64 encoded json structure representing Diaspora signature
571 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
573 $res['dsprsig'] = unxmlify($dsig[0]['data']);
576 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
578 $res['guid'] = unxmlify($dguid[0]['data']);
580 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
582 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
586 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
589 $have_real_body = false;
591 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
593 $have_real_body = true;
594 $res['body'] = $rawenv[0]['data'];
595 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
596 // make sure nobody is trying to sneak some html tags by us
597 $res['body'] = notags(base64url_decode($res['body']));
601 $res['body'] = limit_body_size($res['body']);
603 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
604 // the content type. Our own network only emits text normally, though it might have been converted to
605 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
606 // have to assume it is all html and needs to be purified.
608 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
609 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
610 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
613 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
615 $res['body'] = reltoabs($res['body'],$base_url);
617 $res['body'] = html2bb_video($res['body']);
619 $res['body'] = oembed_html2bbcode($res['body']);
621 $config = HTMLPurifier_Config::createDefault();
622 $config->set('Cache.DefinitionImpl', null);
624 // we shouldn't need a whitelist, because the bbcode converter
625 // will strip out any unsupported tags.
627 $purifier = new HTMLPurifier($config);
628 $res['body'] = $purifier->purify($res['body']);
630 $res['body'] = @html2bbcode($res['body']);
634 elseif(! $have_real_body) {
636 // it's not one of our messages and it has no tags
637 // so it's probably just text. We'll escape it just to be safe.
639 $res['body'] = escape_tags($res['body']);
643 // this tag is obsolete but we keep it for really old sites
645 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
646 if($allow && $allow[0]['data'] == 1)
647 $res['last-child'] = 1;
649 $res['last-child'] = 0;
651 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
652 if($private && intval($private[0]['data']) > 0)
653 $res['private'] = intval($private[0]['data']);
657 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
658 if($extid && $extid[0]['data'])
659 $res['extid'] = $extid[0]['data'];
661 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
663 $res['location'] = unxmlify($rawlocation[0]['data']);
666 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
668 $res['created'] = unxmlify($rawcreated[0]['data']);
671 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
673 $res['edited'] = unxmlify($rawedited[0]['data']);
675 if((x($res,'edited')) && (! (x($res,'created'))))
676 $res['created'] = $res['edited'];
678 if(! $res['created'])
679 $res['created'] = $item->get_date('c');
682 $res['edited'] = $item->get_date('c');
685 // Disallow time travelling posts
687 $d1 = strtotime($res['created']);
688 $d2 = strtotime($res['edited']);
689 $d3 = strtotime('now');
692 $res['created'] = datetime_convert();
694 $res['edited'] = datetime_convert();
696 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
697 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
698 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
699 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
700 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
701 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
702 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
703 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
704 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
706 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
707 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
709 foreach($base as $link) {
710 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
711 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
712 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
717 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
719 $res['coord'] = unxmlify($rawgeo[0]['data']);
721 if ($contact["network"] == NETWORK_FEED) {
722 $res['verb'] = ACTIVITY_POST;
723 $res['object-type'] = ACTIVITY_OBJ_NOTE;
726 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
728 // select between supported verbs
731 $res['verb'] = unxmlify($rawverb[0]['data']);
734 // translate OStatus unfollow to activity streams if it happened to get selected
736 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
737 $res['verb'] = ACTIVITY_UNFOLLOW;
739 $cats = $item->get_categories();
742 foreach($cats as $cat) {
743 $term = $cat->get_term();
745 $term = $cat->get_label();
746 $scheme = $cat->get_scheme();
747 if($scheme && $term && stristr($scheme,'X-DFRN:'))
748 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
750 $tag_arr[] = notags(trim($term));
752 $res['tag'] = implode(',', $tag_arr);
755 $attach = $item->get_enclosures();
758 foreach($attach as $att) {
759 $len = intval($att->get_length());
760 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
761 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
762 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
763 if(strpos($type,';'))
764 $type = substr($type,0,strpos($type,';'));
765 if((! $link) || (strpos($link,'http') !== 0))
771 $type = 'application/octet-stream';
773 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
775 $res['attach'] = implode(',', $att_arr);
778 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
781 $res['object'] = '<object>' . "\n";
782 $child = $rawobj[0]['child'];
783 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
784 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
785 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
787 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
788 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
789 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
790 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
791 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
792 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
793 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
794 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
796 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
797 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
798 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
799 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
801 $body = html2bb_video($body);
803 $config = HTMLPurifier_Config::createDefault();
804 $config->set('Cache.DefinitionImpl', null);
806 $purifier = new HTMLPurifier($config);
807 $body = $purifier->purify($body);
808 $body = html2bbcode($body);
811 $res['object'] .= '<content>' . $body . '</content>' . "\n";
814 $res['object'] .= '</object>' . "\n";
817 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
820 $res['target'] = '<target>' . "\n";
821 $child = $rawobj[0]['child'];
822 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
823 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
825 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
826 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
827 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
828 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
829 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
830 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
831 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
832 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
834 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
835 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
836 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
837 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
839 $body = html2bb_video($body);
841 $config = HTMLPurifier_Config::createDefault();
842 $config->set('Cache.DefinitionImpl', null);
844 $purifier = new HTMLPurifier($config);
845 $body = $purifier->purify($body);
846 $body = html2bbcode($body);
849 $res['target'] .= '<content>' . $body . '</content>' . "\n";
852 $res['target'] .= '</target>' . "\n";
855 // This is some experimental stuff. By now retweets are shown with "RT:"
856 // But: There is data so that the message could be shown similar to native retweets
857 // There is some better way to parse this array - but it didn't worked for me.
858 $child = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://activitystrea.ms/spec/1.0/"][object][0]["child"];
859 if (is_array($child)) {
860 logger('get_atom_elements: Looking for status.net repeated message');
862 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
863 $orig_id = ostatus_convert_href($child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"]);
864 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
865 $uri = $author["uri"][0]["data"];
866 $name = $author["name"][0]["data"];
867 $avatar = @array_shift($author["link"][2]["attribs"]);
868 $avatar = $avatar["href"];
870 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
871 logger('get_atom_elements: fixing sender of repeated message. '.$orig_id, LOGGER_DEBUG);
873 if (!intval(get_config('system','wall-to-wall_share'))) {
874 $prefix = share_header($name, $uri, $avatar, "", "", $orig_link);
876 $res["body"] = $prefix.html2bbcode($message)."[/share]";
878 $res["owner-name"] = $res["author-name"];
879 $res["owner-link"] = $res["author-link"];
880 $res["owner-avatar"] = $res["author-avatar"];
882 $res["author-name"] = $name;
883 $res["author-link"] = $uri;
884 $res["author-avatar"] = $avatar;
886 $res["body"] = html2bbcode($message);
891 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
894 // Handle enclosures and treat them as preview picture
896 foreach ($attach AS $attachment)
897 if ($attachment->type == "image/jpeg")
898 $preview = $attachment->link;
900 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
901 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
903 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
904 unset($res["attach"]);
905 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
906 $res["body"] = add_page_info_to_body($res["body"]);
907 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
908 $res["body"] = add_page_info_to_body($res["body"]);
911 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
913 call_hooks('parse_atom', $arr);
918 function add_page_info_data($data) {
919 call_hooks('page_info_data', $data);
921 // It maybe is a rich content, but if it does have everything that a link has,
922 // then treat it that way
923 if (($data["type"] == "rich") AND is_string($data["title"]) AND
924 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
925 $data["type"] = "link";
927 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
930 if ($no_photos AND ($data["type"] == "photo"))
933 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
934 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
935 require_once("include/network.php");
936 $data["url"] = short_link($data["url"]);
939 if (($data["type"] != "photo") AND is_string($data["title"]))
940 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
942 if (($data["type"] != "video") AND ($photo != ""))
943 $text .= '[img]'.$photo.'[/img]';
944 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
945 $imagedata = $data["images"][0];
946 $text .= '[img]'.$imagedata["src"].'[/img]';
949 if (($data["type"] != "photo") AND is_string($data["text"]))
950 $text .= "[quote]".$data["text"]."[/quote]";
953 if (isset($data["keywords"]) AND count($data["keywords"])) {
956 foreach ($data["keywords"] AS $keyword) {
957 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
958 array("","", "", "", "", ""), $keyword);
959 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
963 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
966 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
967 require_once("mod/parse_url.php");
969 $data = Cache::get("parse_url:".$url);
971 $data = parseurl_getsiteinfo($url, true);
972 Cache::set("parse_url:".$url,serialize($data));
974 $data = unserialize($data);
977 $data["images"][0]["src"] = $photo;
979 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
981 if (!$keywords AND isset($data["keywords"]))
982 unset($data["keywords"]);
984 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
985 $list = explode(",", $keyword_blacklist);
986 foreach ($list AS $keyword) {
987 $keyword = trim($keyword);
988 $index = array_search($keyword, $data["keywords"]);
989 if ($index !== false)
990 unset($data["keywords"][$index]);
997 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
998 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1001 if (isset($data["keywords"]) AND count($data["keywords"])) {
1003 foreach ($data["keywords"] AS $keyword) {
1004 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
1005 array("","", "", "", "", ""), $keyword);
1010 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
1017 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1018 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1020 $text = add_page_info_data($data);
1025 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1027 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1029 $URLSearchString = "^\[\]";
1031 // Adding these spaces is a quick hack due to my problems with regular expressions :)
1032 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1035 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1037 // Convert urls without bbcode elements
1038 if (!$matches AND $texturl) {
1039 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1041 // Yeah, a hack. I really hate regular expressions :)
1043 $matches[1] = $matches[2];
1047 $footer = add_page_info($matches[1], $no_photos);
1049 // Remove the link from the body if the link is attached at the end of the post
1050 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1051 $removedlink = trim(str_replace($matches[1], "", $body));
1052 if (($removedlink == "") OR strstr($body, $removedlink))
1053 $body = $removedlink;
1055 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1056 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1057 if (($removedlink == "") OR strstr($body, $removedlink))
1058 $body = $removedlink;
1061 // Add the page information to the bottom
1062 if (isset($footer) AND (trim($footer) != ""))
1068 function encode_rel_links($links) {
1070 if(! ((is_array($links)) && (count($links))))
1072 foreach($links as $link) {
1074 if($link['attribs']['']['rel'])
1075 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1076 if($link['attribs']['']['type'])
1077 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1078 if($link['attribs']['']['href'])
1079 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1080 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1081 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1082 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1083 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1084 $o .= ' />' . "\n" ;
1089 function add_guid($item) {
1090 $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
1094 q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
1095 dbesc($item["guid"]), dbesc($item["plink"]),
1096 dbesc($item["uri"]), dbesc($item["network"]));
1099 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1101 // If it is a posting where users should get notifications, then define it as wall posting
1104 $arr['type'] = 'wall';
1106 $arr['last-child'] = 1;
1107 $arr['network'] = NETWORK_DFRN;
1110 // If a Diaspora signature structure was passed in, pull it out of the
1111 // item array and set it aside for later storage.
1114 if(x($arr,'dsprsig')) {
1115 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1116 unset($arr['dsprsig']);
1119 // Converting the plink
1120 if ($arr['network'] == NETWORK_OSTATUS) {
1121 if (isset($arr['plink']))
1122 $arr['plink'] = ostatus_convert_href($arr['plink']);
1123 elseif (isset($arr['uri']))
1124 $arr['plink'] = ostatus_convert_href($arr['uri']);
1127 if(x($arr, 'gravity'))
1128 $arr['gravity'] = intval($arr['gravity']);
1129 elseif($arr['parent-uri'] === $arr['uri'])
1130 $arr['gravity'] = 0;
1131 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1132 $arr['gravity'] = 6;
1134 $arr['gravity'] = 6; // extensible catchall
1136 if(! x($arr,'type'))
1137 $arr['type'] = 'remote';
1141 /* check for create date and expire time */
1142 $uid = intval($arr['uid']);
1143 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
1145 $expire_interval = $r[0]['expire'];
1146 if ($expire_interval>0) {
1147 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1148 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1149 if ($created_date < $expire_date) {
1150 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1156 // If there is no guid then take the same guid that was taken before for the same uri
1157 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
1158 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1159 $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
1160 dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
1163 $arr['guid'] = $r[0]["guid"];
1164 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1168 // If there is no guid then take the same guid that was taken before for the same plink
1169 if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
1170 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
1171 $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
1172 dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
1175 $arr['guid'] = $r[0]["guid"];
1176 logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
1178 if ($r[0]["uri"] != $arr['uri'])
1179 logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
1183 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1184 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1185 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1186 // $arr['body'] = strip_tags($arr['body']);
1189 if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
1190 require_once('library/langdet/Text/LanguageDetect.php');
1191 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1192 $l = new Text_LanguageDetect;
1193 //$lng = $l->detectConfidence($naked_body);
1194 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1195 $lng = $l->detect($naked_body, 3);
1197 if (sizeof($lng) > 0) {
1200 foreach ($lng as $language => $score) {
1201 if ($postopts == "")
1202 $postopts = "lang=";
1206 $postopts .= $language.";".$score;
1208 $arr['postopts'] = $postopts;
1212 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1213 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : random_string());
1214 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1215 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1216 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1217 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1218 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1219 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1220 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1221 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1222 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1223 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1224 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1225 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1226 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1227 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1228 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1229 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1230 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1231 $arr['deleted'] = 0;
1232 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1233 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1234 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1235 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1236 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1237 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1238 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1239 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1240 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1241 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1242 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1243 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1244 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1245 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1246 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1247 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1248 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1249 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1250 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1251 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $arr['network']));
1252 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1253 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1254 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1255 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1256 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1258 if ($arr['plink'] == "") {
1260 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1263 if ($arr['network'] == "") {
1264 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1265 intval($arr['contact-id']),
1270 $arr['network'] = $r[0]["network"];
1272 // Fallback to friendica (why is it empty in some cases?)
1273 if ($arr['network'] == "")
1274 $arr['network'] = NETWORK_DFRN;
1276 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1279 if ($arr['guid'] != "") {
1280 // Checking if there is already an item with the same guid
1281 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1282 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1283 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1286 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1291 // Check for hashtags in the body and repair or add hashtag links
1292 item_body_set_hashtags($arr);
1294 $arr['thr-parent'] = $arr['parent-uri'];
1295 if($arr['parent-uri'] === $arr['uri']) {
1297 $parent_deleted = 0;
1298 $allow_cid = $arr['allow_cid'];
1299 $allow_gid = $arr['allow_gid'];
1300 $deny_cid = $arr['deny_cid'];
1301 $deny_gid = $arr['deny_gid'];
1302 $notify_type = 'wall-new';
1306 // find the parent and snarf the item id and ACLs
1307 // and anything else we need to inherit
1309 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1310 dbesc($arr['parent-uri']),
1316 // is the new message multi-level threaded?
1317 // even though we don't support it now, preserve the info
1318 // and re-attach to the conversation parent.
1320 if($r[0]['uri'] != $r[0]['parent-uri']) {
1321 $arr['parent-uri'] = $r[0]['parent-uri'];
1322 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1323 ORDER BY `id` ASC LIMIT 1",
1324 dbesc($r[0]['parent-uri']),
1325 dbesc($r[0]['parent-uri']),
1332 $parent_id = $r[0]['id'];
1333 $parent_deleted = $r[0]['deleted'];
1334 $allow_cid = $r[0]['allow_cid'];
1335 $allow_gid = $r[0]['allow_gid'];
1336 $deny_cid = $r[0]['deny_cid'];
1337 $deny_gid = $r[0]['deny_gid'];
1338 $arr['wall'] = $r[0]['wall'];
1339 $notify_type = 'comment-new';
1341 // if the parent is private, force privacy for the entire conversation
1342 // This differs from the above settings as it subtly allows comments from
1343 // email correspondents to be private even if the overall thread is not.
1345 if($r[0]['private'])
1346 $arr['private'] = $r[0]['private'];
1348 // Edge case. We host a public forum that was originally posted to privately.
1349 // The original author commented, but as this is a comment, the permissions
1350 // weren't fixed up so it will still show the comment as private unless we fix it here.
1352 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1353 $arr['private'] = 0;
1356 // If its a post from myself then tag the thread as "mention"
1357 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1358 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1361 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1362 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1363 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1364 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1365 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1371 // Allow one to see reply tweets from status.net even when
1372 // we don't have or can't see the original post.
1375 logger('item_store: $force_parent=true, reply converted to top-level post.');
1377 $arr['parent-uri'] = $arr['uri'];
1378 $arr['gravity'] = 0;
1381 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1385 $parent_deleted = 0;
1389 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1391 dbesc($arr['network']),
1394 if($r && count($r)) {
1395 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1399 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1400 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1401 dbesc($arr['body']),
1402 dbesc($arr['network']),
1403 dbesc($arr['created']),
1404 intval($arr['contact-id']),
1407 if($r && count($r)) {
1408 logger('duplicated item with the same body found. ' . print_r($arr,true));
1412 // Is this item available in the global items (with uid=0)?
1413 if ($arr["uid"] == 0) {
1414 $arr["global"] = true;
1416 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1418 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1420 $arr["global"] = (count($isglobal) > 0);
1423 // Fill the cache field
1424 put_item_in_cache($arr);
1426 call_hooks('post_remote',$arr);
1428 if(x($arr,'cancel')) {
1429 logger('item_store: post cancelled by plugin.');
1433 // Store the unescaped version
1438 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1440 $r = dbq("INSERT INTO `item` (`"
1441 . implode("`, `", array_keys($arr))
1443 . implode("', '", array_values($arr))
1449 // find the item we just created
1450 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1457 // Store the guid and other relevant data
1460 $current_post = $r[0]['id'];
1461 logger('item_store: created item ' . $current_post);
1463 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1464 // This can be used to filter for inactive contacts.
1465 // Only do this for public postings to avoid privacy problems, since poco data is public.
1466 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1468 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1470 // Is it a forum? Then we don't care about the rules from above
1471 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1472 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1473 intval($arr['contact-id']));
1479 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1480 dbesc($arr['received']),
1481 dbesc($arr['received']),
1482 intval($arr['contact-id'])
1485 logger('item_store: could not locate created item');
1489 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1490 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1492 intval($arr['uid']),
1493 intval($current_post)
1497 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1498 $parent_id = $current_post;
1500 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1503 $private = $arr['private'];
1505 // Set parent id - and also make sure to inherit the parent's ACLs.
1507 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1508 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1515 intval($parent_deleted),
1516 intval($current_post)
1519 $arr['id'] = $current_post;
1520 $arr['parent'] = $parent_id;
1521 $arr['allow_cid'] = $allow_cid;
1522 $arr['allow_gid'] = $allow_gid;
1523 $arr['deny_cid'] = $deny_cid;
1524 $arr['deny_gid'] = $deny_gid;
1525 $arr['private'] = $private;
1526 $arr['deleted'] = $parent_deleted;
1528 // update the commented timestamp on the parent
1529 // Only update "commented" if it is really a comment
1530 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1531 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1532 dbesc(datetime_convert()),
1533 dbesc(datetime_convert()),
1537 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1538 dbesc(datetime_convert()),
1543 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1544 intval($current_post),
1545 dbesc($dsprsig->signed_text),
1546 dbesc($dsprsig->signature),
1547 dbesc($dsprsig->signer)
1553 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1556 if($arr['last-child']) {
1557 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1559 intval($arr['uid']),
1560 intval($current_post)
1564 $deleted = tag_deliver($arr['uid'],$current_post);
1566 // current post can be deleted if is for a community page and no mention are
1568 if (!$deleted AND !$dontcache) {
1570 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1571 if (count($r) == 1) {
1572 call_hooks('post_remote_end', $r[0]);
1574 logger('item_store: new item not found in DB, id ' . $current_post);
1577 // Add every contact of the post to the global contact table
1580 create_tags_from_item($current_post);
1581 create_files_from_item($current_post);
1583 // Only check for notifications on start posts
1584 if ($arr['parent-uri'] === $arr['uri']) {
1585 add_thread($current_post);
1586 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1588 // Send a notification for every new post?
1589 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1590 intval($arr['contact-id']),
1593 $send_notification = count($r);
1595 if (!$send_notification) {
1596 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1597 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1600 foreach ($tags AS $tag) {
1601 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1602 normalise_link($tag["url"]), intval($arr['uid']));
1604 $send_notification = true;
1609 if ($send_notification) {
1610 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1611 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1612 intval($arr['uid']));
1614 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1615 intval($current_post),
1621 require_once('include/enotify.php');
1623 'type' => NOTIFY_SHARE,
1624 'notify_flags' => $u[0]['notify-flags'],
1625 'language' => $u[0]['language'],
1626 'to_name' => $u[0]['username'],
1627 'to_email' => $u[0]['email'],
1628 'uid' => $u[0]['uid'],
1630 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1631 'source_name' => $item[0]['author-name'],
1632 'source_link' => $item[0]['author-link'],
1633 'source_photo' => $item[0]['author-avatar'],
1634 'verb' => ACTIVITY_TAG,
1636 'parent' => $arr['parent']
1638 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1641 update_thread($parent_id);
1642 add_shadow_entry($arr);
1646 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1648 return $current_post;
1651 function item_body_set_hashtags(&$item) {
1653 $tags = get_tags($item["body"]);
1659 // This sorting is important when there are hashtags that are part of other hashtags
1660 // Otherwise there could be problems with hashtags like #test and #test2
1665 $URLSearchString = "^\[\]";
1667 // All hashtags should point to the home server
1668 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1669 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1671 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1672 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1674 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1675 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1677 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1680 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1682 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1685 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1687 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1690 // Repair recursive urls
1691 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1692 "#$2", $item["body"]);
1695 foreach($tags as $tag) {
1696 if(strpos($tag,'#') !== 0)
1699 if(strpos($tag,'[url='))
1702 $basetag = str_replace('_',' ',substr($tag,1));
1704 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1706 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1708 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1709 if(strlen($item["tag"]))
1710 $item["tag"] = ','.$item["tag"];
1711 $item["tag"] = $newtag.$item["tag"];
1715 // Convert back the masked hashtags
1716 $item["body"] = str_replace("#", "#", $item["body"]);
1719 function get_item_guid($id) {
1720 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1722 return($r[0]["guid"]);
1727 function get_item_id($guid, $uid = 0) {
1733 $uid == local_user();
1735 // Does the given user have this item?
1737 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1738 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1739 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1742 $nick = $r[0]["nickname"];
1746 // Or is it anywhere on the server?
1748 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1749 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1750 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1751 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1752 AND `item`.`private` = 0 AND `item`.`wall` = 1
1753 AND `item`.`guid` = '%s'", dbesc($guid));
1756 $nick = $r[0]["nickname"];
1759 return(array("nick" => $nick, "id" => $id));
1763 function get_item_contact($item,$contacts) {
1764 if(! count($contacts) || (! is_array($item)))
1766 foreach($contacts as $contact) {
1767 if($contact['id'] == $item['contact-id']) {
1769 break; // NOTREACHED
1776 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1778 * @param int $item_id
1779 * @return bool true if item was deleted, else false
1781 function tag_deliver($uid,$item_id) {
1789 $u = q("select * from user where uid = %d limit 1",
1795 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1796 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1799 $i = q("select * from item where id = %d and uid = %d limit 1",
1808 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1810 // Diaspora uses their own hardwired link URL in @-tags
1811 // instead of the one we supply with webfinger
1813 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1815 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1817 foreach($matches as $mtch) {
1818 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1820 logger('tag_deliver: mention found: ' . $mtch[2]);
1826 if ( ($community_page || $prvgroup) &&
1827 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1828 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1830 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1831 q("DELETE FROM item WHERE id = %d and uid = %d",
1841 // send a notification
1843 // use a local photo if we have one
1845 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1846 intval($u[0]['uid']),
1847 dbesc(normalise_link($item['author-link']))
1849 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1852 require_once('include/enotify.php');
1854 'type' => NOTIFY_TAGSELF,
1855 'notify_flags' => $u[0]['notify-flags'],
1856 'language' => $u[0]['language'],
1857 'to_name' => $u[0]['username'],
1858 'to_email' => $u[0]['email'],
1859 'uid' => $u[0]['uid'],
1861 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1862 'source_name' => $item['author-name'],
1863 'source_link' => $item['author-link'],
1864 'source_photo' => $photo,
1865 'verb' => ACTIVITY_TAG,
1867 'parent' => $item['parent']
1871 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1873 call_hooks('tagged', $arr);
1875 if((! $community_page) && (! $prvgroup))
1879 // tgroup delivery - setup a second delivery chain
1880 // prevent delivery looping - only proceed
1881 // if the message originated elsewhere and is a top-level post
1883 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1886 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1889 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1890 intval($u[0]['uid'])
1895 // also reset all the privacy bits to the forum default permissions
1897 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1899 $forum_mode = (($prvgroup) ? 2 : 1);
1901 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1902 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1903 intval($forum_mode),
1904 dbesc($c[0]['name']),
1905 dbesc($c[0]['url']),
1906 dbesc($c[0]['thumb']),
1908 dbesc($u[0]['allow_cid']),
1909 dbesc($u[0]['allow_gid']),
1910 dbesc($u[0]['deny_cid']),
1911 dbesc($u[0]['deny_gid']),
1914 update_thread($item_id);
1916 proc_run('php','include/notifier.php','tgroup',$item_id);
1922 function tgroup_check($uid,$item) {
1928 // check that the message originated elsewhere and is a top-level post
1930 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1934 $u = q("select * from user where uid = %d limit 1",
1940 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1941 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1944 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1946 // Diaspora uses their own hardwired link URL in @-tags
1947 // instead of the one we supply with webfinger
1949 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1951 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1953 foreach($matches as $mtch) {
1954 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1956 logger('tgroup_check: mention found: ' . $mtch[2]);
1964 if((! $community_page) && (! $prvgroup))
1978 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1982 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1984 if($contact['duplex'] && $contact['dfrn-id'])
1985 $idtosend = '0:' . $orig_id;
1986 if($contact['duplex'] && $contact['issued-id'])
1987 $idtosend = '1:' . $orig_id;
1990 $rino = get_config('system','rino_encrypt');
1991 $rino = intval($rino);
1996 $ssl_val = intval(get_config('system','ssl_policy'));
2000 case SSL_POLICY_FULL:
2001 $ssl_policy = 'full';
2003 case SSL_POLICY_SELFSIGN:
2004 $ssl_policy = 'self';
2006 case SSL_POLICY_NONE:
2008 $ssl_policy = 'none';
2012 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
2014 logger('dfrn_deliver: ' . $url);
2016 $xml = fetch_url($url);
2018 $curl_stat = $a->get_curl_code();
2020 return(-1); // timed out
2022 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2027 if(strpos($xml,'<?xml') === false) {
2028 logger('dfrn_deliver: no valid XML returned');
2029 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2033 $res = parse_xml_string($xml);
2035 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2036 return (($res->status) ? $res->status : 3);
2038 $postvars = array();
2039 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2040 $challenge = hex2bin((string) $res->challenge);
2041 $perm = (($res->perm) ? $res->perm : null);
2042 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2043 $rino_remote_version = intval($res->rino);
2044 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2046 if($owner['page-flags'] == PAGE_PRVGROUP)
2049 $final_dfrn_id = '';
2052 if((($perm == 'rw') && (! intval($contact['writable'])))
2053 || (($perm == 'r') && (intval($contact['writable'])))) {
2054 q("update contact set writable = %d where id = %d",
2055 intval(($perm == 'rw') ? 1 : 0),
2056 intval($contact['id'])
2058 $contact['writable'] = (string) 1 - intval($contact['writable']);
2062 if(($contact['duplex'] && strlen($contact['pubkey']))
2063 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2064 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2065 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2066 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2069 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2070 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2073 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2075 if(strpos($final_dfrn_id,':') == 1)
2076 $final_dfrn_id = substr($final_dfrn_id,2);
2078 if($final_dfrn_id != $orig_id) {
2079 logger('dfrn_deliver: wrong dfrn_id.');
2080 // did not decode properly - cannot trust this site
2084 $postvars['dfrn_id'] = $idtosend;
2085 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2087 $postvars['dissolve'] = '1';
2090 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2091 $postvars['data'] = $atom;
2092 $postvars['perm'] = 'rw';
2095 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2096 $postvars['perm'] = 'r';
2099 $postvars['ssl_policy'] = $ssl_policy;
2102 $postvars['page'] = $page;
2105 if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
2106 logger('rino version: '. $rino_remote_version);
2108 switch($rino_remote_version) {
2110 // Deprecated rino version!
2111 $key = substr(random_string(),0,16);
2112 $data = aes_encrypt($postvars['data'],$key);
2115 // RINO 2 based on php-encryption
2117 $key = Crypto::createNewRandomKey();
2118 } catch (CryptoTestFailed $ex) {
2119 logger('Cannot safely create a key');
2121 } catch (CannotPerformOperation $ex) {
2122 logger('Cannot safely create a key');
2126 $data = Crypto::encrypt($postvars['data'], $key);
2127 } catch (CryptoTestFailed $ex) {
2128 logger('Cannot safely perform encryption');
2130 } catch (CannotPerformOperation $ex) {
2131 logger('Cannot safely perform encryption');
2136 logger("rino: invalid requested verision '$rino_remote_version'");
2140 $postvars['rino'] = $rino_remote_version;
2141 $postvars['data'] = bin2hex($data);
2143 #logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2146 if($dfrn_version >= 2.1) {
2147 if(($contact['duplex'] && strlen($contact['pubkey']))
2148 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2149 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2151 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2154 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2158 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2159 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2162 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2166 logger('md5 rawkey ' . md5($postvars['key']));
2168 $postvars['key'] = bin2hex($postvars['key']);
2172 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2174 $xml = post_url($contact['notify'],$postvars);
2176 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2178 $curl_stat = $a->get_curl_code();
2179 if((! $curl_stat) || (! strlen($xml)))
2180 return(-1); // timed out
2182 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2185 if(strpos($xml,'<?xml') === false) {
2186 logger('dfrn_deliver: phase 2: no valid XML returned');
2187 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2191 if($contact['term-date'] != '0000-00-00 00:00:00') {
2192 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2193 require_once('include/Contact.php');
2194 unmark_for_death($contact);
2197 $res = parse_xml_string($xml);
2199 return $res->status;
2204 This function returns true if $update has an edited timestamp newer
2205 than $existing, i.e. $update contains new data which should override
2206 what's already there. If there is no timestamp yet, the update is
2207 assumed to be newer. If the update has no timestamp, the existing
2208 item is assumed to be up-to-date. If the timestamps are equal it
2209 assumes the update has been seen before and should be ignored.
2211 function edited_timestamp_is_newer($existing, $update) {
2212 if (!x($existing,'edited') || !$existing['edited']) {
2215 if (!x($update,'edited') || !$update['edited']) {
2218 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2219 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2220 return (strcmp($existing_edited, $update_edited) < 0);
2225 * consume_feed - process atom feed and update anything/everything we might need to update
2227 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2229 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2230 * It is this person's stuff that is going to be updated.
2231 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2232 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2233 * have a contact record.
2234 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2235 * might not) try and subscribe to it.
2236 * $datedir sorts in reverse order
2237 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2238 * imported prior to its children being seen in the stream unless we are certain
2239 * of how the feed is arranged/ordered.
2240 * With $pass = 1, we only pull parent items out of the stream.
2241 * With $pass = 2, we only pull children (comments/likes).
2243 * So running this twice, first with pass 1 and then with pass 2 will do the right
2244 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2245 * model where comments can have sub-threads. That would require some massive sorting
2246 * to get all the feed items into a mostly linear ordering, and might still require
2250 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2251 if ($contact['network'] === NETWORK_OSTATUS) {
2253 // Test - remove before flight
2254 //$tempfile = tempnam(get_temppath(), "ostatus");
2255 //file_put_contents($tempfile, $xml);
2257 logger("Consume OStatus messages ", LOGGER_DEBUG);
2258 ostatus_import($xml,$importer,$contact, $hub);
2263 require_once('library/simplepie/simplepie.inc');
2264 require_once('include/contact_selectors.php');
2266 if(! strlen($xml)) {
2267 logger('consume_feed: empty input');
2271 $feed = new SimplePie();
2272 $feed->set_raw_data($xml);
2274 $feed->enable_order_by_date(true);
2276 $feed->enable_order_by_date(false);
2280 logger('consume_feed: Error parsing XML: ' . $feed->error());
2282 $permalink = $feed->get_permalink();
2284 // Check at the feed level for updated contact name and/or photo
2288 $photo_timestamp = '';
2291 $contact_updated = '';
2293 $hubs = $feed->get_links('hub');
2294 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2297 $hub = implode(',', $hubs);
2299 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2301 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2303 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2304 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2305 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2306 $new_name = $elems['name'][0]['data'];
2308 // Manually checking for changed contact names
2309 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2310 $name_updated = date("c");
2311 $photo_timestamp = date("c");
2314 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2315 if ($photo_timestamp == "")
2316 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2317 $photo_url = $elems['link'][0]['attribs']['']['href'];
2320 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2321 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2325 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2326 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2328 $contact_updated = $photo_timestamp;
2330 require_once("include/Photo.php");
2331 $photo_failure = false;
2332 $have_photo = false;
2334 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2335 intval($contact['id']),
2336 intval($contact['uid'])
2339 $resource_id = $r[0]['resource-id'];
2343 $resource_id = photo_new_resource();
2346 $img_str = fetch_url($photo_url,true);
2347 // guess mimetype from headers or filename
2348 $type = guess_image_type($photo_url,true);
2351 $img = new Photo($img_str, $type);
2352 if($img->is_valid()) {
2354 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2355 dbesc($resource_id),
2356 intval($contact['id']),
2357 intval($contact['uid'])
2361 $img->scaleImageSquare(175);
2363 $hash = $resource_id;
2364 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2366 $img->scaleImage(80);
2367 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2369 $img->scaleImage(48);
2370 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2374 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2375 WHERE `uid` = %d AND `id` = %d",
2376 dbesc(datetime_convert()),
2377 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2378 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2379 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2380 intval($contact['uid']),
2381 intval($contact['id'])
2386 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2387 if ($name_updated > $contact_updated)
2388 $contact_updated = $name_updated;
2390 $r = q("select * from contact where uid = %d and id = %d limit 1",
2391 intval($contact['uid']),
2392 intval($contact['id'])
2395 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2396 dbesc(notags(trim($new_name))),
2397 dbesc(datetime_convert()),
2398 intval($contact['uid']),
2399 intval($contact['id'])
2402 // do our best to update the name on content items
2405 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2406 dbesc(notags(trim($new_name))),
2407 dbesc($r[0]['name']),
2408 dbesc($r[0]['url']),
2409 intval($contact['uid'])
2414 if ($contact_updated AND $new_name AND $photo_url)
2415 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2417 if(strlen($birthday)) {
2418 if(substr($birthday,0,4) != $contact['bdyear']) {
2419 logger('consume_feed: updating birthday: ' . $birthday);
2423 * Add new birthday event for this person
2425 * $bdtext is just a readable placeholder in case the event is shared
2426 * with others. We will replace it during presentation to our $importer
2427 * to contain a sparkle link and perhaps a photo.
2431 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2432 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2435 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2436 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2437 intval($contact['uid']),
2438 intval($contact['id']),
2439 dbesc(datetime_convert()),
2440 dbesc(datetime_convert()),
2441 dbesc(datetime_convert('UTC','UTC', $birthday)),
2442 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2451 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2452 dbesc(substr($birthday,0,4)),
2453 intval($contact['uid']),
2454 intval($contact['id'])
2457 // This function is called twice without reloading the contact
2458 // Make sure we only create one event. This is why &$contact
2459 // is a reference var in this function
2461 $contact['bdyear'] = substr($birthday,0,4);
2465 $community_page = 0;
2466 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2468 $community_page = intval($rawtags[0]['data']);
2470 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2471 q("update contact set forum = %d where id = %d",
2472 intval($community_page),
2473 intval($contact['id'])
2475 $contact['forum'] = (string) $community_page;
2479 // process any deleted entries
2481 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2482 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2483 foreach($del_entries as $dentry) {
2485 if(isset($dentry['attribs']['']['ref'])) {
2486 $uri = $dentry['attribs']['']['ref'];
2488 if(isset($dentry['attribs']['']['when'])) {
2489 $when = $dentry['attribs']['']['when'];
2490 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2493 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2495 if($deleted && is_array($contact)) {
2496 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2497 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2499 intval($importer['uid']),
2500 intval($contact['id'])
2505 if(! $item['deleted'])
2506 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2508 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2509 $xo = parse_xml_string($item['object'],false);
2510 $xt = parse_xml_string($item['target'],false);
2511 if($xt->type === ACTIVITY_OBJ_NOTE) {
2512 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2514 intval($importer['importer_uid'])
2518 // For tags, the owner cannot remove the tag on the author's copy of the post.
2520 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2521 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2522 $author_copy = (($item['origin']) ? true : false);
2524 if($owner_remove && $author_copy)
2526 if($author_remove || $owner_remove) {
2527 $tags = explode(',',$i[0]['tag']);
2530 foreach($tags as $tag)
2531 if(trim($tag) !== trim($xo->body))
2532 $newtags[] = trim($tag);
2534 q("update item set tag = '%s' where id = %d",
2535 dbesc(implode(',',$newtags)),
2538 create_tags_from_item($i[0]['id']);
2544 if($item['uri'] == $item['parent-uri']) {
2545 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2546 `body` = '', `title` = ''
2547 WHERE `parent-uri` = '%s' AND `uid` = %d",
2549 dbesc(datetime_convert()),
2550 dbesc($item['uri']),
2551 intval($importer['uid'])
2553 create_tags_from_itemuri($item['uri'], $importer['uid']);
2554 create_files_from_itemuri($item['uri'], $importer['uid']);
2555 update_thread_uri($item['uri'], $importer['uid']);
2558 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2559 `body` = '', `title` = ''
2560 WHERE `uri` = '%s' AND `uid` = %d",
2562 dbesc(datetime_convert()),
2564 intval($importer['uid'])
2566 create_tags_from_itemuri($uri, $importer['uid']);
2567 create_files_from_itemuri($uri, $importer['uid']);
2568 if($item['last-child']) {
2569 // ensure that last-child is set in case the comment that had it just got wiped.
2570 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2571 dbesc(datetime_convert()),
2572 dbesc($item['parent-uri']),
2573 intval($item['uid'])
2575 // who is the last child now?
2576 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2577 ORDER BY `created` DESC LIMIT 1",
2578 dbesc($item['parent-uri']),
2579 intval($importer['uid'])
2582 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2593 // Now process the feed
2595 if($feed->get_item_quantity()) {
2597 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2599 // in inverse date order
2601 $items = array_reverse($feed->get_items());
2603 $items = $feed->get_items();
2606 foreach($items as $item) {
2609 $item_id = $item->get_id();
2610 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2611 if(isset($rawthread[0]['attribs']['']['ref'])) {
2613 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2616 if(($is_reply) && is_array($contact)) {
2621 // not allowed to post
2623 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2627 // Have we seen it? If not, import it.
2629 $item_id = $item->get_id();
2630 $datarray = get_atom_elements($feed, $item, $contact);
2632 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2633 $datarray['author-name'] = $contact['name'];
2634 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2635 $datarray['author-link'] = $contact['url'];
2636 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2637 $datarray['author-avatar'] = $contact['thumb'];
2639 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2640 logger('consume_feed: no author information! ' . print_r($datarray,true));
2644 $force_parent = false;
2645 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2646 if($contact['network'] === NETWORK_OSTATUS)
2647 $force_parent = true;
2648 if(strlen($datarray['title']))
2649 unset($datarray['title']);
2650 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2651 dbesc(datetime_convert()),
2653 intval($importer['uid'])
2655 $datarray['last-child'] = 1;
2656 update_thread_uri($parent_uri, $importer['uid']);
2660 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2662 intval($importer['uid'])
2665 // Update content if 'updated' changes
2668 if (edited_timestamp_is_newer($r[0], $datarray)) {
2670 // do not accept (ignore) an earlier edit than one we currently have.
2671 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2674 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2675 dbesc($datarray['title']),
2676 dbesc($datarray['body']),
2677 dbesc($datarray['tag']),
2678 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2679 dbesc(datetime_convert()),
2681 intval($importer['uid'])
2683 create_tags_from_itemuri($item_id, $importer['uid']);
2684 update_thread_uri($item_id, $importer['uid']);
2687 // update last-child if it changes
2689 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2690 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2691 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2692 dbesc(datetime_convert()),
2694 intval($importer['uid'])
2696 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2697 intval($allow[0]['data']),
2698 dbesc(datetime_convert()),
2700 intval($importer['uid'])
2702 update_thread_uri($item_id, $importer['uid']);
2708 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2709 // one way feed - no remote comment ability
2710 $datarray['last-child'] = 0;
2712 $datarray['parent-uri'] = $parent_uri;
2713 $datarray['uid'] = $importer['uid'];
2714 $datarray['contact-id'] = $contact['id'];
2715 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2716 $datarray['type'] = 'activity';
2717 $datarray['gravity'] = GRAVITY_LIKE;
2718 // only one like or dislike per person
2719 // splitted into two queries for performance issues
2720 $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",
2721 intval($datarray['uid']),
2722 intval($datarray['contact-id']),
2723 dbesc($datarray['verb']),
2729 $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",
2730 intval($datarray['uid']),
2731 intval($datarray['contact-id']),
2732 dbesc($datarray['verb']),
2739 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2740 $xo = parse_xml_string($datarray['object'],false);
2741 $xt = parse_xml_string($datarray['target'],false);
2743 if($xt->type == ACTIVITY_OBJ_NOTE) {
2744 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2746 intval($importer['importer_uid'])
2751 // extract tag, if not duplicate, add to parent item
2752 if($xo->id && $xo->content) {
2753 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2754 if(! (stristr($r[0]['tag'],$newtag))) {
2755 q("UPDATE item SET tag = '%s' WHERE id = %d",
2756 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2759 create_tags_from_item($r[0]['id']);
2765 $r = item_store($datarray,$force_parent);
2771 // Head post of a conversation. Have we seen it? If not, import it.
2773 $item_id = $item->get_id();
2775 $datarray = get_atom_elements($feed, $item, $contact);
2777 if(is_array($contact)) {
2778 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2779 $datarray['author-name'] = $contact['name'];
2780 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2781 $datarray['author-link'] = $contact['url'];
2782 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2783 $datarray['author-avatar'] = $contact['thumb'];
2786 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2787 logger('consume_feed: no author information! ' . print_r($datarray,true));
2791 // special handling for events
2793 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2794 $ev = bbtoevent($datarray['body']);
2795 if(x($ev,'desc') && x($ev,'start')) {
2796 $ev['uid'] = $importer['uid'];
2797 $ev['uri'] = $item_id;
2798 $ev['edited'] = $datarray['edited'];
2799 $ev['private'] = $datarray['private'];
2801 if(is_array($contact))
2802 $ev['cid'] = $contact['id'];
2803 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2805 intval($importer['uid'])
2808 $ev['id'] = $r[0]['id'];
2809 $xyz = event_store($ev);
2814 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2815 if(strlen($datarray['title']))
2816 unset($datarray['title']);
2817 $datarray['last-child'] = 1;
2821 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2823 intval($importer['uid'])
2826 // Update content if 'updated' changes
2829 if (edited_timestamp_is_newer($r[0], $datarray)) {
2831 // do not accept (ignore) an earlier edit than one we currently have.
2832 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2835 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2836 dbesc($datarray['title']),
2837 dbesc($datarray['body']),
2838 dbesc($datarray['tag']),
2839 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2840 dbesc(datetime_convert()),
2842 intval($importer['uid'])
2844 create_tags_from_itemuri($item_id, $importer['uid']);
2845 update_thread_uri($item_id, $importer['uid']);
2848 // update last-child if it changes
2850 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2851 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2852 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2853 intval($allow[0]['data']),
2854 dbesc(datetime_convert()),
2856 intval($importer['uid'])
2858 update_thread_uri($item_id, $importer['uid']);
2863 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2864 logger('consume-feed: New follower');
2865 new_follower($importer,$contact,$datarray,$item);
2868 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2869 lose_follower($importer,$contact,$datarray,$item);
2873 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2874 logger('consume-feed: New friend request');
2875 new_follower($importer,$contact,$datarray,$item,true);
2878 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2879 lose_sharer($importer,$contact,$datarray,$item);
2884 if(! is_array($contact))
2888 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2889 // one way feed - no remote comment ability
2890 $datarray['last-child'] = 0;
2892 if($contact['network'] === NETWORK_FEED)
2893 $datarray['private'] = 2;
2895 $datarray['parent-uri'] = $item_id;
2896 $datarray['uid'] = $importer['uid'];
2897 $datarray['contact-id'] = $contact['id'];
2899 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2900 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2901 // but otherwise there's a possible data mixup on the sender's system.
2902 // the tgroup delivery code called from item_store will correct it if it's a forum,
2903 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2904 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2905 $datarray['owner-name'] = $contact['name'];
2906 $datarray['owner-link'] = $contact['url'];
2907 $datarray['owner-avatar'] = $contact['thumb'];
2910 // We've allowed "followers" to reach this point so we can decide if they are
2911 // posting an @-tag delivery, which followers are allowed to do for certain
2912 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2914 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2917 // This is my contact on another system, but it's really me.
2918 // Turn this into a wall post.
2919 $notify = item_is_remote_self($contact, $datarray);
2921 $r = item_store($datarray, false, $notify);
2922 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2930 function item_is_remote_self($contact, &$datarray) {
2933 if (!$contact['remote_self'])
2936 // Prevent the forwarding of posts that are forwarded
2937 if ($datarray["extid"] == NETWORK_DFRN)
2940 // Prevent to forward already forwarded posts
2941 if ($datarray["app"] == $a->get_hostname())
2944 // Only forward posts
2945 if ($datarray["verb"] != ACTIVITY_POST)
2948 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2951 $datarray2 = $datarray;
2952 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2953 if ($contact['remote_self'] == 2) {
2954 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2955 intval($contact['uid']));
2957 $datarray['contact-id'] = $r[0]["id"];
2959 $datarray['owner-name'] = $r[0]["name"];
2960 $datarray['owner-link'] = $r[0]["url"];
2961 $datarray['owner-avatar'] = $r[0]["thumb"];
2963 $datarray['author-name'] = $datarray['owner-name'];
2964 $datarray['author-link'] = $datarray['owner-link'];
2965 $datarray['author-avatar'] = $datarray['owner-avatar'];
2968 if ($contact['network'] != NETWORK_FEED) {
2969 $datarray["guid"] = get_guid(32);
2970 unset($datarray["plink"]);
2971 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid']);
2972 $datarray["parent-uri"] = $datarray["uri"];
2973 $datarray["extid"] = $contact['network'];
2974 $urlpart = parse_url($datarray2['author-link']);
2975 $datarray["app"] = $urlpart["host"];
2977 $datarray['private'] = 0;
2980 //if (!isset($datarray["app"]) OR ($datarray["app"] == ""))
2981 // $datarray["app"] = network_to_name($contact['network']);
2983 if ($contact['network'] != NETWORK_FEED) {
2984 // Store the original post
2985 $r = item_store($datarray2, false, false);
2986 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2988 $datarray["app"] = "Feed";
2993 function local_delivery($importer,$data) {
2996 logger(__function__, LOGGER_TRACE);
2998 if($importer['readonly']) {
2999 // We aren't receiving stuff from this person. But we will quietly ignore them
3000 // rather than a blatant "go away" message.
3001 logger('local_delivery: ignoring');
3006 // Consume notification feed. This may differ from consuming a public feed in several ways
3007 // - might contain email or friend suggestions
3008 // - might contain remote followup to our message
3009 // - in which case we need to accept it and then notify other conversants
3010 // - we may need to send various email notifications
3012 $feed = new SimplePie();
3013 $feed->set_raw_data($data);
3014 $feed->enable_order_by_date(false);
3019 logger('local_delivery: Error parsing XML: ' . $feed->error());
3022 // Check at the feed level for updated contact name and/or photo
3026 $photo_timestamp = '';
3028 $contact_updated = '';
3031 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
3033 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
3035 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
3038 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
3039 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
3040 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
3041 $new_name = $elems['name'][0]['data'];
3043 // Manually checking for changed contact names
3044 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
3045 $name_updated = date("c");
3046 $photo_timestamp = date("c");
3049 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
3050 if ($photo_timestamp == "")
3051 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
3052 $photo_url = $elems['link'][0]['attribs']['']['href'];
3056 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3058 $contact_updated = $photo_timestamp;
3060 logger('local_delivery: Updating photo for ' . $importer['name']);
3061 require_once("include/Photo.php");
3062 $photo_failure = false;
3063 $have_photo = false;
3065 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
3066 intval($importer['id']),
3067 intval($importer['importer_uid'])
3070 $resource_id = $r[0]['resource-id'];
3074 $resource_id = photo_new_resource();
3077 $img_str = fetch_url($photo_url,true);
3078 // guess mimetype from headers or filename
3079 $type = guess_image_type($photo_url,true);
3082 $img = new Photo($img_str, $type);
3083 if($img->is_valid()) {
3085 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
3086 dbesc($resource_id),
3087 intval($importer['id']),
3088 intval($importer['importer_uid'])
3092 $img->scaleImageSquare(175);
3094 $hash = $resource_id;
3095 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
3097 $img->scaleImage(80);
3098 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
3100 $img->scaleImage(48);
3101 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3105 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3106 WHERE `uid` = %d AND `id` = %d",
3107 dbesc(datetime_convert()),
3108 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3109 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3110 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3111 intval($importer['importer_uid']),
3112 intval($importer['id'])
3117 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3118 if ($name_updated > $contact_updated)
3119 $contact_updated = $name_updated;
3121 $r = q("select * from contact where uid = %d and id = %d limit 1",
3122 intval($importer['importer_uid']),
3123 intval($importer['id'])
3126 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3127 dbesc(notags(trim($new_name))),
3128 dbesc(datetime_convert()),
3129 intval($importer['importer_uid']),
3130 intval($importer['id'])
3133 // do our best to update the name on content items
3136 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3137 dbesc(notags(trim($new_name))),
3138 dbesc($r[0]['name']),
3139 dbesc($r[0]['url']),
3140 intval($importer['importer_uid'])
3145 if ($contact_updated AND $new_name AND $photo_url)
3146 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3148 // Currently unsupported - needs a lot of work
3149 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3150 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3151 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3153 $newloc['uid'] = $importer['importer_uid'];
3154 $newloc['cid'] = $importer['id'];
3155 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3156 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3157 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3158 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3159 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3160 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3161 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3162 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3163 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3164 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3165 /** relocated user must have original key pair */
3166 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3167 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3169 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3172 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3173 intval($importer['id']),
3174 intval($importer['importer_uid']));
3179 $x = q("UPDATE contact SET
3190 `site-pubkey` = '%s'
3191 WHERE id=%d AND uid=%d;",
3192 dbesc($newloc['name']),
3193 dbesc($newloc['photo']),
3194 dbesc($newloc['thumb']),
3195 dbesc($newloc['micro']),
3196 dbesc($newloc['url']),
3197 dbesc(normalise_link($newloc['url'])),
3198 dbesc($newloc['request']),
3199 dbesc($newloc['confirm']),
3200 dbesc($newloc['notify']),
3201 dbesc($newloc['poll']),
3202 dbesc($newloc['sitepubkey']),
3203 intval($importer['id']),
3204 intval($importer['importer_uid']));
3210 'owner-link' => array($old['url'], $newloc['url']),
3211 'author-link' => array($old['url'], $newloc['url']),
3212 'owner-avatar' => array($old['photo'], $newloc['photo']),
3213 'author-avatar' => array($old['photo'], $newloc['photo']),
3215 foreach ($fields as $n=>$f){
3216 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3219 intval($importer['importer_uid']));
3225 // merge with current record, current contents have priority
3226 // update record, set url-updated
3227 // update profile photos
3233 // handle friend suggestion notification
3235 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3236 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3237 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3239 $fsugg['uid'] = $importer['importer_uid'];
3240 $fsugg['cid'] = $importer['id'];
3241 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3242 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3243 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3244 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3245 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3247 // Does our member already have a friend matching this description?
3249 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3250 dbesc($fsugg['name']),
3251 dbesc(normalise_link($fsugg['url'])),
3252 intval($fsugg['uid'])
3257 // Do we already have an fcontact record for this person?
3260 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3261 dbesc($fsugg['url']),
3262 dbesc($fsugg['name']),
3263 dbesc($fsugg['request'])
3268 // OK, we do. Do we already have an introduction for this person ?
3269 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3270 intval($fsugg['uid']),
3277 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3278 dbesc($fsugg['name']),
3279 dbesc($fsugg['url']),
3280 dbesc($fsugg['photo']),
3281 dbesc($fsugg['request'])
3283 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3284 dbesc($fsugg['url']),
3285 dbesc($fsugg['name']),
3286 dbesc($fsugg['request'])
3291 // database record did not get created. Quietly give up.
3296 $hash = random_string();
3298 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3299 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3300 intval($fsugg['uid']),
3302 intval($fsugg['cid']),
3303 dbesc($fsugg['body']),
3305 dbesc(datetime_convert()),
3310 'type' => NOTIFY_SUGGEST,
3311 'notify_flags' => $importer['notify-flags'],
3312 'language' => $importer['language'],
3313 'to_name' => $importer['username'],
3314 'to_email' => $importer['email'],
3315 'uid' => $importer['importer_uid'],
3317 'link' => $a->get_baseurl() . '/notifications/intros',
3318 'source_name' => $importer['name'],
3319 'source_link' => $importer['url'],
3320 'source_photo' => $importer['photo'],
3321 'verb' => ACTIVITY_REQ_FRIEND,
3330 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3331 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3333 logger('local_delivery: private message received');
3336 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3339 $msg['uid'] = $importer['importer_uid'];
3340 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3341 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3342 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3343 $msg['contact-id'] = $importer['id'];
3344 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3345 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3347 $msg['replied'] = 0;
3348 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3349 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3350 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3354 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3355 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3357 // send notifications.
3359 require_once('include/enotify.php');
3361 $notif_params = array(
3362 'type' => NOTIFY_MAIL,
3363 'notify_flags' => $importer['notify-flags'],
3364 'language' => $importer['language'],
3365 'to_name' => $importer['username'],
3366 'to_email' => $importer['email'],
3367 'uid' => $importer['importer_uid'],
3369 'source_name' => $msg['from-name'],
3370 'source_link' => $importer['url'],
3371 'source_photo' => $importer['thumb'],
3372 'verb' => ACTIVITY_POST,
3376 notification($notif_params);
3382 $community_page = 0;
3383 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3385 $community_page = intval($rawtags[0]['data']);
3387 if(intval($importer['forum']) != $community_page) {
3388 q("update contact set forum = %d where id = %d",
3389 intval($community_page),
3390 intval($importer['id'])
3392 $importer['forum'] = (string) $community_page;
3395 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3397 // process any deleted entries
3399 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3400 if(is_array($del_entries) && count($del_entries)) {
3401 foreach($del_entries as $dentry) {
3403 if(isset($dentry['attribs']['']['ref'])) {
3404 $uri = $dentry['attribs']['']['ref'];
3406 if(isset($dentry['attribs']['']['when'])) {
3407 $when = $dentry['attribs']['']['when'];
3408 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3411 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3415 // check for relayed deletes to our conversation
3418 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3420 intval($importer['importer_uid'])
3423 $parent_uri = $r[0]['parent-uri'];
3424 if($r[0]['id'] != $r[0]['parent'])
3431 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3434 logger('local_delivery: possible community delete');
3437 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3439 // was the top-level post for this reply written by somebody on this site?
3440 // Specifically, the recipient?
3442 $is_a_remote_delete = false;
3444 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3445 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3446 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3447 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3448 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3449 AND `item`.`uid` = %d
3455 intval($importer['importer_uid'])
3458 $is_a_remote_delete = true;
3460 // Does this have the characteristics of a community or private group comment?
3461 // If it's a reply to a wall post on a community/prvgroup page it's a
3462 // valid community comment. Also forum_mode makes it valid for sure.
3463 // If neither, it's not.
3465 if($is_a_remote_delete && $community) {
3466 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3467 $is_a_remote_delete = false;
3468 logger('local_delivery: not a community delete');
3472 if($is_a_remote_delete) {
3473 logger('local_delivery: received remote delete');
3477 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3478 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3480 intval($importer['importer_uid']),
3481 intval($importer['id'])
3487 if($item['deleted'])
3490 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3492 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3493 $xo = parse_xml_string($item['object'],false);
3494 $xt = parse_xml_string($item['target'],false);
3496 if($xt->type === ACTIVITY_OBJ_NOTE) {
3497 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3499 intval($importer['importer_uid'])
3503 // For tags, the owner cannot remove the tag on the author's copy of the post.
3505 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3506 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3507 $author_copy = (($item['origin']) ? true : false);
3509 if($owner_remove && $author_copy)
3511 if($author_remove || $owner_remove) {
3512 $tags = explode(',',$i[0]['tag']);
3515 foreach($tags as $tag)
3516 if(trim($tag) !== trim($xo->body))
3517 $newtags[] = trim($tag);
3519 q("update item set tag = '%s' where id = %d",
3520 dbesc(implode(',',$newtags)),
3523 create_tags_from_item($i[0]['id']);
3529 if($item['uri'] == $item['parent-uri']) {
3530 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3531 `body` = '', `title` = ''
3532 WHERE `parent-uri` = '%s' AND `uid` = %d",
3534 dbesc(datetime_convert()),
3535 dbesc($item['uri']),
3536 intval($importer['importer_uid'])
3538 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3539 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3540 update_thread_uri($item['uri'], $importer['importer_uid']);
3543 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3544 `body` = '', `title` = ''
3545 WHERE `uri` = '%s' AND `uid` = %d",
3547 dbesc(datetime_convert()),
3549 intval($importer['importer_uid'])
3551 create_tags_from_itemuri($uri, $importer['importer_uid']);
3552 create_files_from_itemuri($uri, $importer['importer_uid']);
3553 update_thread_uri($uri, $importer['importer_uid']);
3554 if($item['last-child']) {
3555 // ensure that last-child is set in case the comment that had it just got wiped.
3556 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3557 dbesc(datetime_convert()),
3558 dbesc($item['parent-uri']),
3559 intval($item['uid'])
3561 // who is the last child now?
3562 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3563 ORDER BY `created` DESC LIMIT 1",
3564 dbesc($item['parent-uri']),
3565 intval($importer['importer_uid'])
3568 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3573 // if this is a relayed delete, propagate it to other recipients
3575 if($is_a_remote_delete)
3576 proc_run('php',"include/notifier.php","drop",$item['id']);
3584 foreach($feed->get_items() as $item) {
3587 $item_id = $item->get_id();
3588 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3589 if(isset($rawthread[0]['attribs']['']['ref'])) {
3591 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3597 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3600 logger('local_delivery: possible community reply');
3603 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3605 // was the top-level post for this reply written by somebody on this site?
3606 // Specifically, the recipient?
3608 $is_a_remote_comment = false;
3609 $top_uri = $parent_uri;
3611 $r = q("select `item`.`parent-uri` from `item`
3612 WHERE `item`.`uri` = '%s'
3616 if($r && count($r)) {
3617 $top_uri = $r[0]['parent-uri'];
3619 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3620 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3621 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3622 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3623 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3624 AND `item`.`uid` = %d
3630 intval($importer['importer_uid'])
3633 $is_a_remote_comment = true;
3636 // Does this have the characteristics of a community or private group comment?
3637 // If it's a reply to a wall post on a community/prvgroup page it's a
3638 // valid community comment. Also forum_mode makes it valid for sure.
3639 // If neither, it's not.
3641 if($is_a_remote_comment && $community) {
3642 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3643 $is_a_remote_comment = false;
3644 logger('local_delivery: not a community reply');
3648 if($is_a_remote_comment) {
3649 logger('local_delivery: received remote comment');
3651 // remote reply to our post. Import and then notify everybody else.
3653 $datarray = get_atom_elements($feed, $item);
3655 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3657 intval($importer['importer_uid'])
3660 // Update content if 'updated' changes
3664 if (edited_timestamp_is_newer($r[0], $datarray)) {
3666 // do not accept (ignore) an earlier edit than one we currently have.
3667 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3670 logger('received updated comment' , LOGGER_DEBUG);
3671 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3672 dbesc($datarray['title']),
3673 dbesc($datarray['body']),
3674 dbesc($datarray['tag']),
3675 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3676 dbesc(datetime_convert()),
3678 intval($importer['importer_uid'])
3680 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3682 proc_run('php',"include/notifier.php","comment-import",$iid);
3691 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3692 intval($importer['importer_uid'])
3696 $datarray['type'] = 'remote-comment';
3697 $datarray['wall'] = 1;
3698 $datarray['parent-uri'] = $parent_uri;
3699 $datarray['uid'] = $importer['importer_uid'];
3700 $datarray['owner-name'] = $own[0]['name'];
3701 $datarray['owner-link'] = $own[0]['url'];
3702 $datarray['owner-avatar'] = $own[0]['thumb'];
3703 $datarray['contact-id'] = $importer['id'];
3705 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3707 $datarray['type'] = 'activity';
3708 $datarray['gravity'] = GRAVITY_LIKE;
3709 $datarray['last-child'] = 0;
3710 // only one like or dislike per person
3711 // splitted into two queries for performance issues
3712 $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",
3713 intval($datarray['uid']),
3714 intval($datarray['contact-id']),
3715 dbesc($datarray['verb']),
3716 dbesc($datarray['parent-uri'])
3722 $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",
3723 intval($datarray['uid']),
3724 intval($datarray['contact-id']),
3725 dbesc($datarray['verb']),
3726 dbesc($datarray['parent-uri'])
3733 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3735 $xo = parse_xml_string($datarray['object'],false);
3736 $xt = parse_xml_string($datarray['target'],false);
3738 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3740 // fetch the parent item
3742 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3744 intval($importer['importer_uid'])
3749 // extract tag, if not duplicate, and this user allows tags, add to parent item
3751 if($xo->id && $xo->content) {
3752 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3753 if(! (stristr($tagp[0]['tag'],$newtag))) {
3754 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3755 intval($importer['importer_uid'])
3757 if(count($i) && ! intval($i[0]['blocktags'])) {
3758 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3759 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3760 intval($tagp[0]['id']),
3761 dbesc(datetime_convert()),
3762 dbesc(datetime_convert())
3764 create_tags_from_item($tagp[0]['id']);
3772 $posted_id = item_store($datarray);
3777 $datarray["id"] = $posted_id;
3779 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3781 intval($importer['importer_uid'])
3784 $parent = $r[0]['parent'];
3785 $parent_uri = $r[0]['parent-uri'];
3789 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3790 dbesc(datetime_convert()),
3791 intval($importer['importer_uid']),
3792 intval($r[0]['parent'])
3795 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3796 dbesc(datetime_convert()),
3797 intval($importer['importer_uid']),
3802 if($posted_id && $parent) {
3804 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3806 if((! $is_like) && (! $importer['self'])) {
3808 require_once('include/enotify.php');
3811 'type' => NOTIFY_COMMENT,
3812 'notify_flags' => $importer['notify-flags'],
3813 'language' => $importer['language'],
3814 'to_name' => $importer['username'],
3815 'to_email' => $importer['email'],
3816 'uid' => $importer['importer_uid'],
3817 'item' => $datarray,
3818 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3819 'source_name' => stripslashes($datarray['author-name']),
3820 'source_link' => $datarray['author-link'],
3821 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3822 ? $importer['thumb'] : $datarray['author-avatar']),
3823 'verb' => ACTIVITY_POST,
3825 'parent' => $parent,
3826 'parent_uri' => $parent_uri,
3838 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3840 $item_id = $item->get_id();
3841 $datarray = get_atom_elements($feed,$item);
3843 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3846 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3848 intval($importer['importer_uid'])
3851 // Update content if 'updated' changes
3854 if (edited_timestamp_is_newer($r[0], $datarray)) {
3856 // do not accept (ignore) an earlier edit than one we currently have.
3857 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3860 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3861 dbesc($datarray['title']),
3862 dbesc($datarray['body']),
3863 dbesc($datarray['tag']),
3864 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3865 dbesc(datetime_convert()),
3867 intval($importer['importer_uid'])
3869 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3872 // update last-child if it changes
3874 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3875 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3876 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3877 dbesc(datetime_convert()),
3879 intval($importer['importer_uid'])
3881 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3882 intval($allow[0]['data']),
3883 dbesc(datetime_convert()),
3885 intval($importer['importer_uid'])
3891 $datarray['parent-uri'] = $parent_uri;
3892 $datarray['uid'] = $importer['importer_uid'];
3893 $datarray['contact-id'] = $importer['id'];
3894 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3895 $datarray['type'] = 'activity';
3896 $datarray['gravity'] = GRAVITY_LIKE;
3897 // only one like or dislike per person
3898 // splitted into two queries for performance issues
3899 $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",
3900 intval($datarray['uid']),
3901 intval($datarray['contact-id']),
3902 dbesc($datarray['verb']),
3908 $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",
3909 intval($datarray['uid']),
3910 intval($datarray['contact-id']),
3911 dbesc($datarray['verb']),
3919 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3921 $xo = parse_xml_string($datarray['object'],false);
3922 $xt = parse_xml_string($datarray['target'],false);
3924 if($xt->type == ACTIVITY_OBJ_NOTE) {
3925 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3927 intval($importer['importer_uid'])
3932 // extract tag, if not duplicate, add to parent item
3934 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3935 q("UPDATE item SET tag = '%s' WHERE id = %d",
3936 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3939 create_tags_from_item($r[0]['id']);
3945 $posted_id = item_store($datarray);
3947 // find out if our user is involved in this conversation and wants to be notified.
3949 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3951 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3953 intval($importer['importer_uid'])
3956 if(count($myconv)) {
3957 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3959 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3960 if(! link_compare($datarray['author-link'],$importer_url)) {
3963 foreach($myconv as $conv) {
3965 // now if we find a match, it means we're in this conversation
3967 if(! link_compare($conv['author-link'],$importer_url))
3970 require_once('include/enotify.php');
3972 $conv_parent = $conv['parent'];
3975 'type' => NOTIFY_COMMENT,
3976 'notify_flags' => $importer['notify-flags'],
3977 'language' => $importer['language'],
3978 'to_name' => $importer['username'],
3979 'to_email' => $importer['email'],
3980 'uid' => $importer['importer_uid'],
3981 'item' => $datarray,
3982 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3983 'source_name' => stripslashes($datarray['author-name']),
3984 'source_link' => $datarray['author-link'],
3985 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3986 ? $importer['thumb'] : $datarray['author-avatar']),
3987 'verb' => ACTIVITY_POST,
3989 'parent' => $conv_parent,
3990 'parent_uri' => $parent_uri
3994 // only send one notification
4006 // Head post of a conversation. Have we seen it? If not, import it.
4009 $item_id = $item->get_id();
4010 $datarray = get_atom_elements($feed,$item);
4012 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
4013 $ev = bbtoevent($datarray['body']);
4014 if(x($ev,'desc') && x($ev,'start')) {
4015 $ev['cid'] = $importer['id'];
4016 $ev['uid'] = $importer['uid'];
4017 $ev['uri'] = $item_id;
4018 $ev['edited'] = $datarray['edited'];
4019 $ev['private'] = $datarray['private'];
4021 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4023 intval($importer['uid'])
4026 $ev['id'] = $r[0]['id'];
4027 $xyz = event_store($ev);
4032 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4034 intval($importer['importer_uid'])
4037 // Update content if 'updated' changes
4040 if (edited_timestamp_is_newer($r[0], $datarray)) {
4042 // do not accept (ignore) an earlier edit than one we currently have.
4043 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4046 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4047 dbesc($datarray['title']),
4048 dbesc($datarray['body']),
4049 dbesc($datarray['tag']),
4050 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4051 dbesc(datetime_convert()),
4053 intval($importer['importer_uid'])
4055 create_tags_from_itemuri($item_id, $importer['importer_uid']);
4056 update_thread_uri($item_id, $importer['importer_uid']);
4059 // update last-child if it changes
4061 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4062 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4063 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4064 intval($allow[0]['data']),
4065 dbesc(datetime_convert()),
4067 intval($importer['importer_uid'])
4073 $datarray['parent-uri'] = $item_id;
4074 $datarray['uid'] = $importer['importer_uid'];
4075 $datarray['contact-id'] = $importer['id'];
4078 if(! link_compare($datarray['owner-link'],$importer['url'])) {
4079 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4080 // but otherwise there's a possible data mixup on the sender's system.
4081 // the tgroup delivery code called from item_store will correct it if it's a forum,
4082 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4083 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4084 $datarray['owner-name'] = $importer['senderName'];
4085 $datarray['owner-link'] = $importer['url'];
4086 $datarray['owner-avatar'] = $importer['thumb'];
4089 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4092 // This is my contact on another system, but it's really me.
4093 // Turn this into a wall post.
4094 $notify = item_is_remote_self($importer, $datarray);
4096 $posted_id = item_store($datarray, false, $notify);
4098 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4099 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4102 $xo = parse_xml_string($datarray['object'],false);
4104 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4106 // somebody was poked/prodded. Was it me?
4108 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4110 foreach($links->link as $l) {
4111 $atts = $l->attributes();
4112 switch($atts['rel']) {
4114 $Blink = $atts['href'];
4120 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4122 // send a notification
4123 require_once('include/enotify.php');
4126 'type' => NOTIFY_POKE,
4127 'notify_flags' => $importer['notify-flags'],
4128 'language' => $importer['language'],
4129 'to_name' => $importer['username'],
4130 'to_email' => $importer['email'],
4131 'uid' => $importer['importer_uid'],
4132 'item' => $datarray,
4133 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4134 'source_name' => stripslashes($datarray['author-name']),
4135 'source_link' => $datarray['author-link'],
4136 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4137 ? $importer['thumb'] : $datarray['author-avatar']),
4138 'verb' => $datarray['verb'],
4139 'otype' => 'person',
4140 'activity' => $verb,
4141 'parent' => $datarray['parent']
4157 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4158 $url = notags(trim($datarray['author-link']));
4159 $name = notags(trim($datarray['author-name']));
4160 $photo = notags(trim($datarray['author-avatar']));
4162 if (is_object($item)) {
4163 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4164 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4165 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4169 if(is_array($contact)) {
4170 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4171 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4172 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4173 intval(CONTACT_IS_FRIEND),
4174 intval($contact['id']),
4175 intval($importer['uid'])
4178 // send email notification to owner?
4182 // create contact record
4184 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4185 `blocked`, `readonly`, `pending`, `writable` )
4186 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4187 intval($importer['uid']),
4188 dbesc(datetime_convert()),
4190 dbesc(normalise_link($url)),
4194 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4195 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4197 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4198 intval($importer['uid']),
4202 $contact_record = $r[0];
4204 // create notification
4205 $hash = random_string();
4207 if(is_array($contact_record)) {
4208 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4209 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4210 intval($importer['uid']),
4211 intval($contact_record['id']),
4213 dbesc(datetime_convert())
4217 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4218 intval($importer['uid'])
4223 if(intval($r[0]['def_gid'])) {
4224 require_once('include/group.php');
4225 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4228 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4229 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4232 'type' => NOTIFY_INTRO,
4233 'notify_flags' => $r[0]['notify-flags'],
4234 'language' => $r[0]['language'],
4235 'to_name' => $r[0]['username'],
4236 'to_email' => $r[0]['email'],
4237 'uid' => $r[0]['uid'],
4238 'link' => $a->get_baseurl() . '/notifications/intro',
4239 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4240 'source_link' => $contact_record['url'],
4241 'source_photo' => $contact_record['photo'],
4242 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4251 function lose_follower($importer,$contact,$datarray,$item) {
4253 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4254 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4255 intval(CONTACT_IS_SHARING),
4256 intval($contact['id'])
4260 contact_remove($contact['id']);
4264 function lose_sharer($importer,$contact,$datarray,$item) {
4266 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4267 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4268 intval(CONTACT_IS_FOLLOWER),
4269 intval($contact['id'])
4273 contact_remove($contact['id']);
4278 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4282 if(is_array($importer)) {
4283 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4284 intval($importer['uid'])
4288 // Diaspora has different message-ids in feeds than they do
4289 // through the direct Diaspora protocol. If we try and use
4290 // the feed, we'll get duplicates. So don't.
4292 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4295 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4297 // Use a single verify token, even if multiple hubs
4299 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4301 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4303 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4305 if(! strlen($contact['hub-verify'])) {
4306 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4307 dbesc($verify_token),
4308 intval($contact['id'])
4312 post_url($url,$params);
4314 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4321 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4325 $name = xmlify($name);
4326 $uri = xmlify($uri);
4329 $photo = xmlify($photo);
4333 $o .= "\t<name>$name</name>\r\n";
4334 $o .= "\t<uri>$uri</uri>\r\n";
4335 $o .= "\t".'<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4336 $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4338 if ($tag == "author") {
4339 $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
4340 `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
4341 `profile`.`homepage`,`contact`.`nick` FROM `profile`
4342 INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
4343 INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
4344 WHERE `profile`.`is-default` AND `contact`.`self` AND
4345 NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
4346 dbesc(normalise_link($uri)));
4349 if($r[0]['locality'])
4350 $location .= $r[0]['locality'];
4351 if($r[0]['region']) {
4354 $location .= $r[0]['region'];
4356 if($r[0]['country-name']) {
4359 $location .= $r[0]['country-name'];
4362 $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
4363 $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
4364 $o .= "\t<poco:note>".xmlify($r[0]["about"])."</poco:note>\r\n";
4365 $o .= "\t<poco:address>\r\n";
4366 $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
4367 $o .= "\t</poco:address>\r\n";
4368 $o .= "\t<poco:urls>\r\n";
4369 $o .= "\t<poco:type>homepage</poco:type>\r\n";
4370 $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
4371 $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
4372 $o .= "\t</poco:urls>\r\n";
4376 call_hooks('atom_author', $o);
4378 $o .= "</$tag>\r\n";
4382 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4386 if(! $item['parent'])
4389 if($item['deleted'])
4390 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4393 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4394 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4396 $body = $item['body'];
4399 $o = "\r\n\r\n<entry>\r\n";
4401 if(is_array($author))
4402 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4404 $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']));
4405 if(strlen($item['owner-name']))
4406 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4408 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4409 $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
4410 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4411 $o .= '<thr:in-reply-to ref="'.xmlify($parent_item).'" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$parent[0]['guid']).'" />'."\r\n";
4416 if ($item['title'] != "")
4417 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4419 $htmlbody = bbcode($htmlbody, false, false, 7);
4421 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4422 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4423 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4424 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4425 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4426 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4427 $o .= '<link rel="alternate" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$item['guid']).'" />'."\r\n";
4429 $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
4432 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4434 if($item['location']) {
4435 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4436 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4440 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4442 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4443 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4446 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4447 if($item['bookmark'])
4448 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4451 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4454 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4456 if($item['signed_text']) {
4457 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4458 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4461 $verb = construct_verb($item);
4462 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4463 $actobj = construct_activity_object($item);
4466 $actarg = construct_activity_target($item);
4470 $tags = item_getfeedtags($item);
4472 foreach($tags as $t)
4473 if (($type != 'html') OR ($t[0] != "@"))
4474 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4478 // To support these elements, the API needs to be enhanced
4479 //$o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
4480 //$o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4481 //$o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4483 $o .= item_get_attachment($item);
4485 $o .= item_getfeedattach($item);
4487 $mentioned = get_mentions($item);
4491 call_hooks('atom_entry', $o);
4493 $o .= '</entry>' . "\r\n";
4498 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4500 if(get_config('system','disable_embedded'))
4505 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4506 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4511 $img_start = strpos($orig_body, '[img');
4512 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4513 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4514 while( ($img_st_close !== false) && ($img_len !== false) ) {
4516 $img_st_close++; // make it point to AFTER the closing bracket
4517 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4519 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4522 if(stristr($image , $site . '/photo/')) {
4523 // Only embed locally hosted photos
4525 $i = basename($image);
4526 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4527 $x = strpos($i,'-');
4530 $res = substr($i,$x+1);
4531 $i = substr($i,0,$x);
4532 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4539 // Check to see if we should replace this photo link with an embedded image
4540 // 1. No need to do so if the photo is public
4541 // 2. If there's a contact-id provided, see if they're in the access list
4542 // for the photo. If so, embed it.
4543 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4544 // permissions, regardless of order but first check to see if they're an exact
4545 // match to save some processing overhead.
4547 if(has_permissions($r[0])) {
4549 $recips = enumerate_permissions($r[0]);
4550 if(in_array($cid, $recips)) {
4555 if(compare_permissions($item,$r[0]))
4560 $data = $r[0]['data'];
4561 $type = $r[0]['type'];
4563 // If a custom width and height were specified, apply before embedding
4564 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4565 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4567 $width = intval($match[1]);
4568 $height = intval($match[2]);
4570 $ph = new Photo($data, $type);
4571 if($ph->is_valid()) {
4572 $ph->scaleImage(max($width, $height));
4573 $data = $ph->imageString();
4574 $type = $ph->getType();
4578 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4579 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4580 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4586 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4587 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4588 if($orig_body === false)
4591 $img_start = strpos($orig_body, '[img');
4592 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4593 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4596 $new_body = $new_body . $orig_body;
4602 function has_permissions($obj) {
4603 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4608 function compare_permissions($obj1,$obj2) {
4609 // first part is easy. Check that these are exactly the same.
4610 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4611 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4612 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4613 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4616 // This is harder. Parse all the permissions and compare the resulting set.
4618 $recipients1 = enumerate_permissions($obj1);
4619 $recipients2 = enumerate_permissions($obj2);
4622 if($recipients1 == $recipients2)
4627 // returns an array of contact-ids that are allowed to see this object
4629 function enumerate_permissions($obj) {
4630 require_once('include/group.php');
4631 $allow_people = expand_acl($obj['allow_cid']);
4632 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4633 $deny_people = expand_acl($obj['deny_cid']);
4634 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4635 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4636 $deny = array_unique(array_merge($deny_people,$deny_groups));
4637 $recipients = array_diff($recipients,$deny);
4641 function item_getfeedtags($item) {
4644 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4646 for($x = 0; $x < $cnt; $x ++) {
4648 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4652 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4654 for($x = 0; $x < $cnt; $x ++) {
4656 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4662 function item_get_attachment($item) {
4664 $siteinfo = get_attached_data($item["body"]);
4666 switch($siteinfo["type"]) {
4668 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4671 $imgdata = get_photo_info($siteinfo["image"]);
4672 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
4675 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4684 function item_getfeedattach($item) {
4686 $arr = explode('[/attach],',$item['attach']);
4688 foreach($arr as $r) {
4690 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4692 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4693 if(intval($matches[2]))
4694 $ret .= 'length="' . intval($matches[2]) . '" ';
4695 if($matches[4] !== ' ')
4696 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4697 $ret .= ' />' . "\r\n";
4706 function item_expire($uid, $days, $network = "", $force = false) {
4708 if((! $uid) || ($days < 1))
4711 // $expire_network_only = save your own wall posts
4712 // and just expire conversations started by others
4714 $expire_network_only = get_pconfig($uid,'expire','network_only');
4715 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4717 if ($network != "") {
4718 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4719 // There is an index "uid_network_received" but not "uid_network_created"
4720 // This avoids the creation of another index just for one purpose.
4721 // And it doesn't really matter wether to look at "received" or "created"
4722 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4724 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4726 $r = q("SELECT * FROM `item`
4727 WHERE `uid` = %d $range
4738 $expire_items = get_pconfig($uid, 'expire','items');
4739 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4741 // Forcing expiring of items - but not notes and marked items
4743 $expire_items = true;
4745 $expire_notes = get_pconfig($uid, 'expire','notes');
4746 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4748 $expire_starred = get_pconfig($uid, 'expire','starred');
4749 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4751 $expire_photos = get_pconfig($uid, 'expire','photos');
4752 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4754 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4756 foreach($r as $item) {
4758 // don't expire filed items
4760 if(strpos($item['file'],'[') !== false)
4763 // Only expire posts, not photos and photo comments
4765 if($expire_photos==0 && strlen($item['resource-id']))
4767 if($expire_starred==0 && intval($item['starred']))
4769 if($expire_notes==0 && $item['type']=='note')
4771 if($expire_items==0 && $item['type']!='note')
4774 drop_item($item['id'],false);
4777 proc_run('php',"include/notifier.php","expire","$uid");
4782 function drop_items($items) {
4785 if(! local_user() && ! remote_user())
4789 foreach($items as $item) {
4790 $owner = drop_item($item,false);
4791 if($owner && ! $uid)
4796 // multiple threads may have been deleted, send an expire notification
4799 proc_run('php',"include/notifier.php","expire","$uid");
4803 function drop_item($id,$interactive = true) {
4807 // locate item to be deleted
4809 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4816 notice( t('Item not found.') . EOL);
4817 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4822 $owner = $item['uid'];
4826 // check if logged in user is either the author or owner of this item
4828 if(is_array($_SESSION['remote'])) {
4829 foreach($_SESSION['remote'] as $visitor) {
4830 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4831 $cid = $visitor['cid'];
4838 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4840 // Check if we should do HTML-based delete confirmation
4841 if($_REQUEST['confirm']) {
4842 // <form> can't take arguments in its "action" parameter
4843 // so add any arguments as hidden inputs
4844 $query = explode_querystring($a->query_string);
4846 foreach($query['args'] as $arg) {
4847 if(strpos($arg, 'confirm=') === false) {
4848 $arg_parts = explode('=', $arg);
4849 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4853 return replace_macros(get_markup_template('confirm.tpl'), array(
4855 '$message' => t('Do you really want to delete this item?'),
4856 '$extra_inputs' => $inputs,
4857 '$confirm' => t('Yes'),
4858 '$confirm_url' => $query['base'],
4859 '$confirm_name' => 'confirmed',
4860 '$cancel' => t('Cancel'),
4863 // Now check how the user responded to the confirmation query
4864 if($_REQUEST['canceled']) {
4865 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4868 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4871 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4872 dbesc(datetime_convert()),
4873 dbesc(datetime_convert()),
4876 create_tags_from_item($item['id']);
4877 create_files_from_item($item['id']);
4878 delete_thread($item['id'], $item['parent-uri']);
4880 // clean up categories and tags so they don't end up as orphans
4883 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4885 foreach($matches as $mtch) {
4886 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4892 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4894 foreach($matches as $mtch) {
4895 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4899 // If item is a link to a photo resource, nuke all the associated photos
4900 // (visitors will not have photo resources)
4901 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4902 // generate a resource-id and therefore aren't intimately linked to the item.
4904 if(strlen($item['resource-id'])) {
4905 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4906 dbesc($item['resource-id']),
4907 intval($item['uid'])
4909 // ignore the result
4912 // If item is a link to an event, nuke the event record.
4914 if(intval($item['event-id'])) {
4915 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4916 intval($item['event-id']),
4917 intval($item['uid'])
4919 // ignore the result
4922 // If item has attachments, drop them
4924 foreach(explode(",",$item['attach']) as $attach){
4925 preg_match("|attach/(\d+)|", $attach, $matches);
4926 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4927 intval($matches[1]),
4930 // ignore the result
4934 // clean up item_id and sign meta-data tables
4937 // Old code - caused very long queries and warning entries in the mysql logfiles:
4939 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4940 intval($item['id']),
4941 intval($item['uid'])
4944 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4945 intval($item['id']),
4946 intval($item['uid'])
4950 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4952 // Creating list of parents
4953 $r = q("select id from item where parent = %d and uid = %d",
4954 intval($item['id']),
4955 intval($item['uid'])
4960 foreach ($r AS $row) {
4961 if ($parentid != "")
4964 $parentid .= $row["id"];
4968 if ($parentid != "") {
4969 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4971 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4974 // If it's the parent of a comment thread, kill all the kids
4976 if($item['uri'] == $item['parent-uri']) {
4977 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4978 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4979 dbesc(datetime_convert()),
4980 dbesc(datetime_convert()),
4981 dbesc($item['parent-uri']),
4982 intval($item['uid'])
4984 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4985 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4986 delete_thread_uri($item['parent-uri'], $item['uid']);
4987 // ignore the result
4990 // ensure that last-child is set in case the comment that had it just got wiped.
4991 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
4992 dbesc(datetime_convert()),
4993 dbesc($item['parent-uri']),
4994 intval($item['uid'])
4996 // who is the last child now?
4997 $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",
4998 dbesc($item['parent-uri']),
4999 intval($item['uid'])
5002 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
5007 // Add a relayable_retraction signature for Diaspora.
5008 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
5011 $drop_id = intval($item['id']);
5013 // send the notification upstream/downstream as the case may be
5015 proc_run('php',"include/notifier.php","drop","$drop_id");
5019 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5025 notice( t('Permission denied.') . EOL);
5026 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5033 function first_post_date($uid,$wall = false) {
5034 $r = q("select id, created from item
5035 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
5037 order by created asc limit 1",
5039 intval($wall ? 1 : 0)
5042 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
5043 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
5048 /* modified posted_dates() {below} to arrange the list in years */
5049 function list_post_dates($uid, $wall) {
5050 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5052 $dthen = first_post_date($uid, $wall);
5056 // Set the start and end date to the beginning of the month
5057 $dnow = substr($dnow,0,8).'01';
5058 $dthen = substr($dthen,0,8).'01';
5062 // Starting with the current month, get the first and last days of every
5063 // month down to and including the month of the first post
5064 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5065 $dyear = intval(substr($dnow,0,4));
5066 $dstart = substr($dnow,0,8) . '01';
5067 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5068 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5069 $end_month = datetime_convert('','',$dend,'Y-m-d');
5070 $str = day_translate(datetime_convert('','',$dnow,'F'));
5072 $ret[$dyear] = array();
5073 $ret[$dyear][] = array($str,$end_month,$start_month);
5074 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5079 function posted_dates($uid,$wall) {
5080 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5082 $dthen = first_post_date($uid,$wall);
5086 // Set the start and end date to the beginning of the month
5087 $dnow = substr($dnow,0,8).'01';
5088 $dthen = substr($dthen,0,8).'01';
5091 // Starting with the current month, get the first and last days of every
5092 // month down to and including the month of the first post
5093 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5094 $dstart = substr($dnow,0,8) . '01';
5095 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5096 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5097 $end_month = datetime_convert('','',$dend,'Y-m-d');
5098 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5099 $ret[] = array($str,$end_month,$start_month);
5100 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5106 function posted_date_widget($url,$uid,$wall) {
5109 if(! feature_enabled($uid,'archives'))
5112 // For former Facebook folks that left because of "timeline"
5114 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5117 $visible_years = get_pconfig($uid,'system','archive_visible_years');
5118 if(! $visible_years)
5121 $ret = list_post_dates($uid,$wall);
5126 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5127 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5129 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5130 '$title' => t('Archives'),
5131 '$size' => $visible_years,
5132 '$cutoff_year' => $cutoff_year,
5133 '$cutoff' => $cutoff,
5136 '$showmore' => t('show more')
5142 function store_diaspora_retract_sig($item, $user, $baseurl) {
5143 // Note that we can't add a target_author_signature
5144 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5145 // the comment, that means we're the home of the post, and Diaspora will only
5146 // check the parent_author_signature of retractions that it doesn't have to relay further
5148 // I don't think this function gets called for an "unlike," but I'll check anyway
5150 $enabled = intval(get_config('system','diaspora_enabled'));
5152 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5156 logger('drop_item: storing diaspora retraction signature');
5158 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5160 if(local_user() == $item['uid']) {
5162 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5163 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5166 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5167 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5170 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5171 // only handles DFRN deletes
5172 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5173 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5174 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5180 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5181 intval($item['id']),
5182 dbesc($signed_text),