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), CACHE_DAY);
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;
1215 $guid_prefix = $arr['network'];
1217 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1218 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
1219 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
1220 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1221 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1222 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1223 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1224 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1225 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1226 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1227 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1228 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1229 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1230 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1231 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1232 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1233 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1234 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1235 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1236 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1237 $arr['deleted'] = 0;
1238 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1239 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1240 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1241 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1242 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1243 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1244 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1245 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1246 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1247 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1248 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1249 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1250 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1251 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1252 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1253 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1254 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1255 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1256 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1257 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1258 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1259 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1260 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1261 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1263 if ($arr['plink'] == "") {
1265 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1268 if ($arr['network'] == "") {
1269 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1270 intval($arr['contact-id']),
1275 $arr['network'] = $r[0]["network"];
1277 // Fallback to friendica (why is it empty in some cases?)
1278 if ($arr['network'] == "")
1279 $arr['network'] = NETWORK_DFRN;
1281 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1284 if ($arr['guid'] != "") {
1285 // Checking if there is already an item with the same guid
1286 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1287 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1288 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1291 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1296 // Check for hashtags in the body and repair or add hashtag links
1297 item_body_set_hashtags($arr);
1299 $arr['thr-parent'] = $arr['parent-uri'];
1300 if($arr['parent-uri'] === $arr['uri']) {
1302 $parent_deleted = 0;
1303 $allow_cid = $arr['allow_cid'];
1304 $allow_gid = $arr['allow_gid'];
1305 $deny_cid = $arr['deny_cid'];
1306 $deny_gid = $arr['deny_gid'];
1307 $notify_type = 'wall-new';
1311 // find the parent and snarf the item id and ACLs
1312 // and anything else we need to inherit
1314 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1315 dbesc($arr['parent-uri']),
1321 // is the new message multi-level threaded?
1322 // even though we don't support it now, preserve the info
1323 // and re-attach to the conversation parent.
1325 if($r[0]['uri'] != $r[0]['parent-uri']) {
1326 $arr['parent-uri'] = $r[0]['parent-uri'];
1327 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1328 ORDER BY `id` ASC LIMIT 1",
1329 dbesc($r[0]['parent-uri']),
1330 dbesc($r[0]['parent-uri']),
1337 $parent_id = $r[0]['id'];
1338 $parent_deleted = $r[0]['deleted'];
1339 $allow_cid = $r[0]['allow_cid'];
1340 $allow_gid = $r[0]['allow_gid'];
1341 $deny_cid = $r[0]['deny_cid'];
1342 $deny_gid = $r[0]['deny_gid'];
1343 $arr['wall'] = $r[0]['wall'];
1344 $notify_type = 'comment-new';
1346 // if the parent is private, force privacy for the entire conversation
1347 // This differs from the above settings as it subtly allows comments from
1348 // email correspondents to be private even if the overall thread is not.
1350 if($r[0]['private'])
1351 $arr['private'] = $r[0]['private'];
1353 // Edge case. We host a public forum that was originally posted to privately.
1354 // The original author commented, but as this is a comment, the permissions
1355 // weren't fixed up so it will still show the comment as private unless we fix it here.
1357 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1358 $arr['private'] = 0;
1361 // If its a post from myself then tag the thread as "mention"
1362 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1363 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1366 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1367 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1368 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1369 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1370 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1376 // Allow one to see reply tweets from status.net even when
1377 // we don't have or can't see the original post.
1380 logger('item_store: $force_parent=true, reply converted to top-level post.');
1382 $arr['parent-uri'] = $arr['uri'];
1383 $arr['gravity'] = 0;
1386 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1390 $parent_deleted = 0;
1394 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1396 dbesc($arr['network']),
1399 if($r && count($r)) {
1400 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1404 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1405 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1406 dbesc($arr['body']),
1407 dbesc($arr['network']),
1408 dbesc($arr['created']),
1409 intval($arr['contact-id']),
1412 if($r && count($r)) {
1413 logger('duplicated item with the same body found. ' . print_r($arr,true));
1417 // Is this item available in the global items (with uid=0)?
1418 if ($arr["uid"] == 0) {
1419 $arr["global"] = true;
1421 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1423 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1425 $arr["global"] = (count($isglobal) > 0);
1428 // Fill the cache field
1429 put_item_in_cache($arr);
1432 call_hooks('post_local',$arr);
1434 call_hooks('post_remote',$arr);
1436 if(x($arr,'cancel')) {
1437 logger('item_store: post cancelled by plugin.');
1441 // Store the unescaped version
1446 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1448 $r = dbq("INSERT INTO `item` (`"
1449 . implode("`, `", array_keys($arr))
1451 . implode("', '", array_values($arr))
1457 // find the item we just created
1458 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1465 // Store the guid and other relevant data
1468 $current_post = $r[0]['id'];
1469 logger('item_store: created item ' . $current_post);
1471 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1472 // This can be used to filter for inactive contacts.
1473 // Only do this for public postings to avoid privacy problems, since poco data is public.
1474 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1476 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1478 // Is it a forum? Then we don't care about the rules from above
1479 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1480 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1481 intval($arr['contact-id']));
1487 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1488 dbesc($arr['received']),
1489 dbesc($arr['received']),
1490 intval($arr['contact-id'])
1493 logger('item_store: could not locate created item');
1497 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1498 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1500 intval($arr['uid']),
1501 intval($current_post)
1505 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1506 $parent_id = $current_post;
1508 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1511 $private = $arr['private'];
1513 // Set parent id - and also make sure to inherit the parent's ACLs.
1515 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1516 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1523 intval($parent_deleted),
1524 intval($current_post)
1527 $arr['id'] = $current_post;
1528 $arr['parent'] = $parent_id;
1529 $arr['allow_cid'] = $allow_cid;
1530 $arr['allow_gid'] = $allow_gid;
1531 $arr['deny_cid'] = $deny_cid;
1532 $arr['deny_gid'] = $deny_gid;
1533 $arr['private'] = $private;
1534 $arr['deleted'] = $parent_deleted;
1536 // update the commented timestamp on the parent
1537 // Only update "commented" if it is really a comment
1538 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1539 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1540 dbesc(datetime_convert()),
1541 dbesc(datetime_convert()),
1545 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1546 dbesc(datetime_convert()),
1551 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1552 intval($current_post),
1553 dbesc($dsprsig->signed_text),
1554 dbesc($dsprsig->signature),
1555 dbesc($dsprsig->signer)
1561 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1564 if($arr['last-child']) {
1565 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1567 intval($arr['uid']),
1568 intval($current_post)
1572 $deleted = tag_deliver($arr['uid'],$current_post);
1574 // current post can be deleted if is for a community page and no mention are
1576 if (!$deleted AND !$dontcache) {
1578 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1579 if (count($r) == 1) {
1581 call_hooks('post_local_end', $r[0]);
1583 call_hooks('post_remote_end', $r[0]);
1585 logger('item_store: new item not found in DB, id ' . $current_post);
1588 // Add every contact of the post to the global contact table
1591 create_tags_from_item($current_post);
1592 create_files_from_item($current_post);
1594 // Only check for notifications on start posts
1595 if ($arr['parent-uri'] === $arr['uri']) {
1596 add_thread($current_post);
1597 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1599 // Send a notification for every new post?
1600 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1601 intval($arr['contact-id']),
1604 $send_notification = count($r);
1606 if (!$send_notification) {
1607 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1608 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1611 foreach ($tags AS $tag) {
1612 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1613 normalise_link($tag["url"]), intval($arr['uid']));
1615 $send_notification = true;
1620 if ($send_notification) {
1621 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1622 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1623 intval($arr['uid']));
1625 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1626 intval($current_post),
1632 require_once('include/enotify.php');
1634 'type' => NOTIFY_SHARE,
1635 'notify_flags' => $u[0]['notify-flags'],
1636 'language' => $u[0]['language'],
1637 'to_name' => $u[0]['username'],
1638 'to_email' => $u[0]['email'],
1639 'uid' => $u[0]['uid'],
1641 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1642 'source_name' => $item[0]['author-name'],
1643 'source_link' => $item[0]['author-link'],
1644 'source_photo' => $item[0]['author-avatar'],
1645 'verb' => ACTIVITY_TAG,
1647 'parent' => $arr['parent']
1649 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1652 update_thread($parent_id);
1653 add_shadow_entry($arr);
1657 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1659 return $current_post;
1662 function item_body_set_hashtags(&$item) {
1664 $tags = get_tags($item["body"]);
1670 // This sorting is important when there are hashtags that are part of other hashtags
1671 // Otherwise there could be problems with hashtags like #test and #test2
1676 $URLSearchString = "^\[\]";
1678 // All hashtags should point to the home server
1679 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1680 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1682 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1683 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1685 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1686 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1688 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1691 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1693 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1696 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1698 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1701 // Repair recursive urls
1702 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1703 "#$2", $item["body"]);
1706 foreach($tags as $tag) {
1707 if(strpos($tag,'#') !== 0)
1710 if(strpos($tag,'[url='))
1713 $basetag = str_replace('_',' ',substr($tag,1));
1715 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1717 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1719 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1720 if(strlen($item["tag"]))
1721 $item["tag"] = ','.$item["tag"];
1722 $item["tag"] = $newtag.$item["tag"];
1726 // Convert back the masked hashtags
1727 $item["body"] = str_replace("#", "#", $item["body"]);
1730 function get_item_guid($id) {
1731 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1733 return($r[0]["guid"]);
1738 function get_item_id($guid, $uid = 0) {
1744 $uid == local_user();
1746 // Does the given user have this item?
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`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1753 $nick = $r[0]["nickname"];
1757 // Or is it anywhere on the server?
1759 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1760 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1761 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1762 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1763 AND `item`.`private` = 0 AND `item`.`wall` = 1
1764 AND `item`.`guid` = '%s'", dbesc($guid));
1767 $nick = $r[0]["nickname"];
1770 return(array("nick" => $nick, "id" => $id));
1774 function get_item_contact($item,$contacts) {
1775 if(! count($contacts) || (! is_array($item)))
1777 foreach($contacts as $contact) {
1778 if($contact['id'] == $item['contact-id']) {
1780 break; // NOTREACHED
1787 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1789 * @param int $item_id
1790 * @return bool true if item was deleted, else false
1792 function tag_deliver($uid,$item_id) {
1800 $u = q("select * from user where uid = %d limit 1",
1806 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1807 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1810 $i = q("select * from item where id = %d and uid = %d limit 1",
1819 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1821 // Diaspora uses their own hardwired link URL in @-tags
1822 // instead of the one we supply with webfinger
1824 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1826 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1828 foreach($matches as $mtch) {
1829 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1831 logger('tag_deliver: mention found: ' . $mtch[2]);
1837 if ( ($community_page || $prvgroup) &&
1838 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1839 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1841 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1842 q("DELETE FROM item WHERE id = %d and uid = %d",
1852 // send a notification
1854 // use a local photo if we have one
1856 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1857 intval($u[0]['uid']),
1858 dbesc(normalise_link($item['author-link']))
1860 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1863 require_once('include/enotify.php');
1865 'type' => NOTIFY_TAGSELF,
1866 'notify_flags' => $u[0]['notify-flags'],
1867 'language' => $u[0]['language'],
1868 'to_name' => $u[0]['username'],
1869 'to_email' => $u[0]['email'],
1870 'uid' => $u[0]['uid'],
1872 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1873 'source_name' => $item['author-name'],
1874 'source_link' => $item['author-link'],
1875 'source_photo' => $photo,
1876 'verb' => ACTIVITY_TAG,
1878 'parent' => $item['parent']
1882 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1884 call_hooks('tagged', $arr);
1886 if((! $community_page) && (! $prvgroup))
1890 // tgroup delivery - setup a second delivery chain
1891 // prevent delivery looping - only proceed
1892 // if the message originated elsewhere and is a top-level post
1894 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1897 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1900 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1901 intval($u[0]['uid'])
1906 // also reset all the privacy bits to the forum default permissions
1908 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1910 $forum_mode = (($prvgroup) ? 2 : 1);
1912 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1913 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1914 intval($forum_mode),
1915 dbesc($c[0]['name']),
1916 dbesc($c[0]['url']),
1917 dbesc($c[0]['thumb']),
1919 dbesc($u[0]['allow_cid']),
1920 dbesc($u[0]['allow_gid']),
1921 dbesc($u[0]['deny_cid']),
1922 dbesc($u[0]['deny_gid']),
1925 update_thread($item_id);
1927 proc_run('php','include/notifier.php','tgroup',$item_id);
1933 function tgroup_check($uid,$item) {
1939 // check that the message originated elsewhere and is a top-level post
1941 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1945 $u = q("select * from user where uid = %d limit 1",
1951 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1952 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1955 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1957 // Diaspora uses their own hardwired link URL in @-tags
1958 // instead of the one we supply with webfinger
1960 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1962 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1964 foreach($matches as $mtch) {
1965 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1967 logger('tgroup_check: mention found: ' . $mtch[2]);
1975 if((! $community_page) && (! $prvgroup))
1989 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
1993 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
1995 if($contact['duplex'] && $contact['dfrn-id'])
1996 $idtosend = '0:' . $orig_id;
1997 if($contact['duplex'] && $contact['issued-id'])
1998 $idtosend = '1:' . $orig_id;
2001 $rino = get_config('system','rino_encrypt');
2002 $rino = intval($rino);
2004 logger("Local rino version: ". $rino, LOGGER_DEBUG);
2006 $ssl_val = intval(get_config('system','ssl_policy'));
2010 case SSL_POLICY_FULL:
2011 $ssl_policy = 'full';
2013 case SSL_POLICY_SELFSIGN:
2014 $ssl_policy = 'self';
2016 case SSL_POLICY_NONE:
2018 $ssl_policy = 'none';
2022 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
2024 logger('dfrn_deliver: ' . $url);
2026 $xml = fetch_url($url);
2028 $curl_stat = $a->get_curl_code();
2030 return(-1); // timed out
2032 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2037 if(strpos($xml,'<?xml') === false) {
2038 logger('dfrn_deliver: no valid XML returned');
2039 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2043 $res = parse_xml_string($xml);
2045 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2046 return (($res->status) ? $res->status : 3);
2048 $postvars = array();
2049 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2050 $challenge = hex2bin((string) $res->challenge);
2051 $perm = (($res->perm) ? $res->perm : null);
2052 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2053 $rino_remote_version = intval($res->rino);
2054 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2056 logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG);
2058 if($owner['page-flags'] == PAGE_PRVGROUP)
2061 $final_dfrn_id = '';
2064 if((($perm == 'rw') && (! intval($contact['writable'])))
2065 || (($perm == 'r') && (intval($contact['writable'])))) {
2066 q("update contact set writable = %d where id = %d",
2067 intval(($perm == 'rw') ? 1 : 0),
2068 intval($contact['id'])
2070 $contact['writable'] = (string) 1 - intval($contact['writable']);
2074 if(($contact['duplex'] && strlen($contact['pubkey']))
2075 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2076 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2077 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2078 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2081 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2082 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2085 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2087 if(strpos($final_dfrn_id,':') == 1)
2088 $final_dfrn_id = substr($final_dfrn_id,2);
2090 if($final_dfrn_id != $orig_id) {
2091 logger('dfrn_deliver: wrong dfrn_id.');
2092 // did not decode properly - cannot trust this site
2096 $postvars['dfrn_id'] = $idtosend;
2097 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2099 $postvars['dissolve'] = '1';
2102 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2103 $postvars['data'] = $atom;
2104 $postvars['perm'] = 'rw';
2107 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2108 $postvars['perm'] = 'r';
2111 $postvars['ssl_policy'] = $ssl_policy;
2114 $postvars['page'] = $page;
2117 if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
2118 logger('rino version: '. $rino_remote_version);
2120 switch($rino_remote_version) {
2122 // Deprecated rino version!
2123 $key = substr(random_string(),0,16);
2124 $data = aes_encrypt($postvars['data'],$key);
2127 // RINO 2 based on php-encryption
2129 $key = Crypto::createNewRandomKey();
2130 } catch (CryptoTestFailed $ex) {
2131 logger('Cannot safely create a key');
2133 } catch (CannotPerformOperation $ex) {
2134 logger('Cannot safely create a key');
2138 $data = Crypto::encrypt($postvars['data'], $key);
2139 } catch (CryptoTestFailed $ex) {
2140 logger('Cannot safely perform encryption');
2142 } catch (CannotPerformOperation $ex) {
2143 logger('Cannot safely perform encryption');
2148 logger("rino: invalid requested verision '$rino_remote_version'");
2152 $postvars['rino'] = $rino_remote_version;
2153 $postvars['data'] = bin2hex($data);
2155 #logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2158 if($dfrn_version >= 2.1) {
2159 if(($contact['duplex'] && strlen($contact['pubkey']))
2160 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2161 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2163 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2166 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2170 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2171 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2174 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2178 logger('md5 rawkey ' . md5($postvars['key']));
2180 $postvars['key'] = bin2hex($postvars['key']);
2184 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2186 $xml = post_url($contact['notify'],$postvars);
2188 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2190 $curl_stat = $a->get_curl_code();
2191 if((! $curl_stat) || (! strlen($xml)))
2192 return(-1); // timed out
2194 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2197 if(strpos($xml,'<?xml') === false) {
2198 logger('dfrn_deliver: phase 2: no valid XML returned');
2199 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2203 if($contact['term-date'] != '0000-00-00 00:00:00') {
2204 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2205 require_once('include/Contact.php');
2206 unmark_for_death($contact);
2209 $res = parse_xml_string($xml);
2211 return $res->status;
2216 This function returns true if $update has an edited timestamp newer
2217 than $existing, i.e. $update contains new data which should override
2218 what's already there. If there is no timestamp yet, the update is
2219 assumed to be newer. If the update has no timestamp, the existing
2220 item is assumed to be up-to-date. If the timestamps are equal it
2221 assumes the update has been seen before and should be ignored.
2223 function edited_timestamp_is_newer($existing, $update) {
2224 if (!x($existing,'edited') || !$existing['edited']) {
2227 if (!x($update,'edited') || !$update['edited']) {
2230 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2231 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2232 return (strcmp($existing_edited, $update_edited) < 0);
2237 * consume_feed - process atom feed and update anything/everything we might need to update
2239 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2241 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2242 * It is this person's stuff that is going to be updated.
2243 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2244 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2245 * have a contact record.
2246 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2247 * might not) try and subscribe to it.
2248 * $datedir sorts in reverse order
2249 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2250 * imported prior to its children being seen in the stream unless we are certain
2251 * of how the feed is arranged/ordered.
2252 * With $pass = 1, we only pull parent items out of the stream.
2253 * With $pass = 2, we only pull children (comments/likes).
2255 * So running this twice, first with pass 1 and then with pass 2 will do the right
2256 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2257 * model where comments can have sub-threads. That would require some massive sorting
2258 * to get all the feed items into a mostly linear ordering, and might still require
2262 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2263 if ($contact['network'] === NETWORK_OSTATUS) {
2265 // Test - remove before flight
2266 //$tempfile = tempnam(get_temppath(), "ostatus");
2267 //file_put_contents($tempfile, $xml);
2269 logger("Consume OStatus messages ", LOGGER_DEBUG);
2270 ostatus_import($xml,$importer,$contact, $hub);
2275 require_once('library/simplepie/simplepie.inc');
2276 require_once('include/contact_selectors.php');
2278 if(! strlen($xml)) {
2279 logger('consume_feed: empty input');
2283 $feed = new SimplePie();
2284 $feed->set_raw_data($xml);
2286 $feed->enable_order_by_date(true);
2288 $feed->enable_order_by_date(false);
2292 logger('consume_feed: Error parsing XML: ' . $feed->error());
2294 $permalink = $feed->get_permalink();
2296 // Check at the feed level for updated contact name and/or photo
2300 $photo_timestamp = '';
2303 $contact_updated = '';
2305 $hubs = $feed->get_links('hub');
2306 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2309 $hub = implode(',', $hubs);
2311 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2313 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2315 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2316 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2317 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2318 $new_name = $elems['name'][0]['data'];
2320 // Manually checking for changed contact names
2321 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2322 $name_updated = date("c");
2323 $photo_timestamp = date("c");
2326 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2327 if ($photo_timestamp == "")
2328 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2329 $photo_url = $elems['link'][0]['attribs']['']['href'];
2332 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2333 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2337 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2338 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2340 $contact_updated = $photo_timestamp;
2342 require_once("include/Photo.php");
2343 $photo_failure = false;
2344 $have_photo = false;
2346 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2347 intval($contact['id']),
2348 intval($contact['uid'])
2351 $resource_id = $r[0]['resource-id'];
2355 $resource_id = photo_new_resource();
2358 $img_str = fetch_url($photo_url,true);
2359 // guess mimetype from headers or filename
2360 $type = guess_image_type($photo_url,true);
2363 $img = new Photo($img_str, $type);
2364 if($img->is_valid()) {
2366 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2367 dbesc($resource_id),
2368 intval($contact['id']),
2369 intval($contact['uid'])
2373 $img->scaleImageSquare(175);
2375 $hash = $resource_id;
2376 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2378 $img->scaleImage(80);
2379 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2381 $img->scaleImage(48);
2382 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2386 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2387 WHERE `uid` = %d AND `id` = %d",
2388 dbesc(datetime_convert()),
2389 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2390 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2391 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2392 intval($contact['uid']),
2393 intval($contact['id'])
2398 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2399 if ($name_updated > $contact_updated)
2400 $contact_updated = $name_updated;
2402 $r = q("select * from contact where uid = %d and id = %d limit 1",
2403 intval($contact['uid']),
2404 intval($contact['id'])
2407 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2408 dbesc(notags(trim($new_name))),
2409 dbesc(datetime_convert()),
2410 intval($contact['uid']),
2411 intval($contact['id'])
2414 // do our best to update the name on content items
2417 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2418 dbesc(notags(trim($new_name))),
2419 dbesc($r[0]['name']),
2420 dbesc($r[0]['url']),
2421 intval($contact['uid'])
2426 if ($contact_updated AND $new_name AND $photo_url)
2427 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2429 if(strlen($birthday)) {
2430 if(substr($birthday,0,4) != $contact['bdyear']) {
2431 logger('consume_feed: updating birthday: ' . $birthday);
2435 * Add new birthday event for this person
2437 * $bdtext is just a readable placeholder in case the event is shared
2438 * with others. We will replace it during presentation to our $importer
2439 * to contain a sparkle link and perhaps a photo.
2443 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2444 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2447 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2448 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2449 intval($contact['uid']),
2450 intval($contact['id']),
2451 dbesc(datetime_convert()),
2452 dbesc(datetime_convert()),
2453 dbesc(datetime_convert('UTC','UTC', $birthday)),
2454 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2463 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2464 dbesc(substr($birthday,0,4)),
2465 intval($contact['uid']),
2466 intval($contact['id'])
2469 // This function is called twice without reloading the contact
2470 // Make sure we only create one event. This is why &$contact
2471 // is a reference var in this function
2473 $contact['bdyear'] = substr($birthday,0,4);
2477 $community_page = 0;
2478 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2480 $community_page = intval($rawtags[0]['data']);
2482 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2483 q("update contact set forum = %d where id = %d",
2484 intval($community_page),
2485 intval($contact['id'])
2487 $contact['forum'] = (string) $community_page;
2491 // process any deleted entries
2493 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2494 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2495 foreach($del_entries as $dentry) {
2497 if(isset($dentry['attribs']['']['ref'])) {
2498 $uri = $dentry['attribs']['']['ref'];
2500 if(isset($dentry['attribs']['']['when'])) {
2501 $when = $dentry['attribs']['']['when'];
2502 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2505 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2507 if($deleted && is_array($contact)) {
2508 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2509 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2511 intval($importer['uid']),
2512 intval($contact['id'])
2517 if(! $item['deleted'])
2518 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2520 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2521 $xo = parse_xml_string($item['object'],false);
2522 $xt = parse_xml_string($item['target'],false);
2523 if($xt->type === ACTIVITY_OBJ_NOTE) {
2524 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2526 intval($importer['importer_uid'])
2530 // For tags, the owner cannot remove the tag on the author's copy of the post.
2532 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2533 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2534 $author_copy = (($item['origin']) ? true : false);
2536 if($owner_remove && $author_copy)
2538 if($author_remove || $owner_remove) {
2539 $tags = explode(',',$i[0]['tag']);
2542 foreach($tags as $tag)
2543 if(trim($tag) !== trim($xo->body))
2544 $newtags[] = trim($tag);
2546 q("update item set tag = '%s' where id = %d",
2547 dbesc(implode(',',$newtags)),
2550 create_tags_from_item($i[0]['id']);
2556 if($item['uri'] == $item['parent-uri']) {
2557 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2558 `body` = '', `title` = ''
2559 WHERE `parent-uri` = '%s' AND `uid` = %d",
2561 dbesc(datetime_convert()),
2562 dbesc($item['uri']),
2563 intval($importer['uid'])
2565 create_tags_from_itemuri($item['uri'], $importer['uid']);
2566 create_files_from_itemuri($item['uri'], $importer['uid']);
2567 update_thread_uri($item['uri'], $importer['uid']);
2570 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2571 `body` = '', `title` = ''
2572 WHERE `uri` = '%s' AND `uid` = %d",
2574 dbesc(datetime_convert()),
2576 intval($importer['uid'])
2578 create_tags_from_itemuri($uri, $importer['uid']);
2579 create_files_from_itemuri($uri, $importer['uid']);
2580 if($item['last-child']) {
2581 // ensure that last-child is set in case the comment that had it just got wiped.
2582 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2583 dbesc(datetime_convert()),
2584 dbesc($item['parent-uri']),
2585 intval($item['uid'])
2587 // who is the last child now?
2588 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2589 ORDER BY `created` DESC LIMIT 1",
2590 dbesc($item['parent-uri']),
2591 intval($importer['uid'])
2594 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2605 // Now process the feed
2607 if($feed->get_item_quantity()) {
2609 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2611 // in inverse date order
2613 $items = array_reverse($feed->get_items());
2615 $items = $feed->get_items();
2618 foreach($items as $item) {
2621 $item_id = $item->get_id();
2622 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2623 if(isset($rawthread[0]['attribs']['']['ref'])) {
2625 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2628 if(($is_reply) && is_array($contact)) {
2633 // not allowed to post
2635 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2639 // Have we seen it? If not, import it.
2641 $item_id = $item->get_id();
2642 $datarray = get_atom_elements($feed, $item, $contact);
2644 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2645 $datarray['author-name'] = $contact['name'];
2646 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2647 $datarray['author-link'] = $contact['url'];
2648 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2649 $datarray['author-avatar'] = $contact['thumb'];
2651 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2652 logger('consume_feed: no author information! ' . print_r($datarray,true));
2656 $force_parent = false;
2657 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2658 if($contact['network'] === NETWORK_OSTATUS)
2659 $force_parent = true;
2660 if(strlen($datarray['title']))
2661 unset($datarray['title']);
2662 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2663 dbesc(datetime_convert()),
2665 intval($importer['uid'])
2667 $datarray['last-child'] = 1;
2668 update_thread_uri($parent_uri, $importer['uid']);
2672 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2674 intval($importer['uid'])
2677 // Update content if 'updated' changes
2680 if (edited_timestamp_is_newer($r[0], $datarray)) {
2682 // do not accept (ignore) an earlier edit than one we currently have.
2683 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2686 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2687 dbesc($datarray['title']),
2688 dbesc($datarray['body']),
2689 dbesc($datarray['tag']),
2690 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2691 dbesc(datetime_convert()),
2693 intval($importer['uid'])
2695 create_tags_from_itemuri($item_id, $importer['uid']);
2696 update_thread_uri($item_id, $importer['uid']);
2699 // update last-child if it changes
2701 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2702 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2703 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2704 dbesc(datetime_convert()),
2706 intval($importer['uid'])
2708 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2709 intval($allow[0]['data']),
2710 dbesc(datetime_convert()),
2712 intval($importer['uid'])
2714 update_thread_uri($item_id, $importer['uid']);
2720 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2721 // one way feed - no remote comment ability
2722 $datarray['last-child'] = 0;
2724 $datarray['parent-uri'] = $parent_uri;
2725 $datarray['uid'] = $importer['uid'];
2726 $datarray['contact-id'] = $contact['id'];
2727 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2728 $datarray['type'] = 'activity';
2729 $datarray['gravity'] = GRAVITY_LIKE;
2730 // only one like or dislike per person
2731 // splitted into two queries for performance issues
2732 $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",
2733 intval($datarray['uid']),
2734 intval($datarray['contact-id']),
2735 dbesc($datarray['verb']),
2741 $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",
2742 intval($datarray['uid']),
2743 intval($datarray['contact-id']),
2744 dbesc($datarray['verb']),
2751 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2752 $xo = parse_xml_string($datarray['object'],false);
2753 $xt = parse_xml_string($datarray['target'],false);
2755 if($xt->type == ACTIVITY_OBJ_NOTE) {
2756 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2758 intval($importer['importer_uid'])
2763 // extract tag, if not duplicate, add to parent item
2764 if($xo->id && $xo->content) {
2765 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2766 if(! (stristr($r[0]['tag'],$newtag))) {
2767 q("UPDATE item SET tag = '%s' WHERE id = %d",
2768 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2771 create_tags_from_item($r[0]['id']);
2777 $r = item_store($datarray,$force_parent);
2783 // Head post of a conversation. Have we seen it? If not, import it.
2785 $item_id = $item->get_id();
2787 $datarray = get_atom_elements($feed, $item, $contact);
2789 if(is_array($contact)) {
2790 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2791 $datarray['author-name'] = $contact['name'];
2792 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2793 $datarray['author-link'] = $contact['url'];
2794 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2795 $datarray['author-avatar'] = $contact['thumb'];
2798 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2799 logger('consume_feed: no author information! ' . print_r($datarray,true));
2803 // special handling for events
2805 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2806 $ev = bbtoevent($datarray['body']);
2807 if(x($ev,'desc') && x($ev,'start')) {
2808 $ev['uid'] = $importer['uid'];
2809 $ev['uri'] = $item_id;
2810 $ev['edited'] = $datarray['edited'];
2811 $ev['private'] = $datarray['private'];
2813 if(is_array($contact))
2814 $ev['cid'] = $contact['id'];
2815 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2817 intval($importer['uid'])
2820 $ev['id'] = $r[0]['id'];
2821 $xyz = event_store($ev);
2826 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2827 if(strlen($datarray['title']))
2828 unset($datarray['title']);
2829 $datarray['last-child'] = 1;
2833 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2835 intval($importer['uid'])
2838 // Update content if 'updated' changes
2841 if (edited_timestamp_is_newer($r[0], $datarray)) {
2843 // do not accept (ignore) an earlier edit than one we currently have.
2844 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2847 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2848 dbesc($datarray['title']),
2849 dbesc($datarray['body']),
2850 dbesc($datarray['tag']),
2851 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2852 dbesc(datetime_convert()),
2854 intval($importer['uid'])
2856 create_tags_from_itemuri($item_id, $importer['uid']);
2857 update_thread_uri($item_id, $importer['uid']);
2860 // update last-child if it changes
2862 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2863 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2864 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2865 intval($allow[0]['data']),
2866 dbesc(datetime_convert()),
2868 intval($importer['uid'])
2870 update_thread_uri($item_id, $importer['uid']);
2875 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2876 logger('consume-feed: New follower');
2877 new_follower($importer,$contact,$datarray,$item);
2880 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2881 lose_follower($importer,$contact,$datarray,$item);
2885 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2886 logger('consume-feed: New friend request');
2887 new_follower($importer,$contact,$datarray,$item,true);
2890 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2891 lose_sharer($importer,$contact,$datarray,$item);
2896 if(! is_array($contact))
2900 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2901 // one way feed - no remote comment ability
2902 $datarray['last-child'] = 0;
2904 if($contact['network'] === NETWORK_FEED)
2905 $datarray['private'] = 2;
2907 $datarray['parent-uri'] = $item_id;
2908 $datarray['uid'] = $importer['uid'];
2909 $datarray['contact-id'] = $contact['id'];
2911 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2912 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2913 // but otherwise there's a possible data mixup on the sender's system.
2914 // the tgroup delivery code called from item_store will correct it if it's a forum,
2915 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2916 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2917 $datarray['owner-name'] = $contact['name'];
2918 $datarray['owner-link'] = $contact['url'];
2919 $datarray['owner-avatar'] = $contact['thumb'];
2922 // We've allowed "followers" to reach this point so we can decide if they are
2923 // posting an @-tag delivery, which followers are allowed to do for certain
2924 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2926 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2929 // This is my contact on another system, but it's really me.
2930 // Turn this into a wall post.
2931 $notify = item_is_remote_self($contact, $datarray);
2933 $r = item_store($datarray, false, $notify);
2934 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2942 function item_is_remote_self($contact, &$datarray) {
2945 if (!$contact['remote_self'])
2948 // Prevent the forwarding of posts that are forwarded
2949 if ($datarray["extid"] == NETWORK_DFRN)
2952 // Prevent to forward already forwarded posts
2953 if ($datarray["app"] == $a->get_hostname())
2956 // Only forward posts
2957 if ($datarray["verb"] != ACTIVITY_POST)
2960 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2963 $datarray2 = $datarray;
2964 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2965 if ($contact['remote_self'] == 2) {
2966 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2967 intval($contact['uid']));
2969 $datarray['contact-id'] = $r[0]["id"];
2971 $datarray['owner-name'] = $r[0]["name"];
2972 $datarray['owner-link'] = $r[0]["url"];
2973 $datarray['owner-avatar'] = $r[0]["thumb"];
2975 $datarray['author-name'] = $datarray['owner-name'];
2976 $datarray['author-link'] = $datarray['owner-link'];
2977 $datarray['author-avatar'] = $datarray['owner-avatar'];
2980 if ($contact['network'] != NETWORK_FEED) {
2981 $datarray["guid"] = get_guid(32);
2982 unset($datarray["plink"]);
2983 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
2984 $datarray["parent-uri"] = $datarray["uri"];
2985 $datarray["extid"] = $contact['network'];
2986 $urlpart = parse_url($datarray2['author-link']);
2987 $datarray["app"] = $urlpart["host"];
2989 $datarray['private'] = 0;
2992 if ($contact['network'] != NETWORK_FEED) {
2993 // Store the original post
2994 $r = item_store($datarray2, false, false);
2995 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
2997 $datarray["app"] = "Feed";
3002 function local_delivery($importer,$data) {
3005 logger(__function__, LOGGER_TRACE);
3007 if($importer['readonly']) {
3008 // We aren't receiving stuff from this person. But we will quietly ignore them
3009 // rather than a blatant "go away" message.
3010 logger('local_delivery: ignoring');
3015 // Consume notification feed. This may differ from consuming a public feed in several ways
3016 // - might contain email or friend suggestions
3017 // - might contain remote followup to our message
3018 // - in which case we need to accept it and then notify other conversants
3019 // - we may need to send various email notifications
3021 $feed = new SimplePie();
3022 $feed->set_raw_data($data);
3023 $feed->enable_order_by_date(false);
3028 logger('local_delivery: Error parsing XML: ' . $feed->error());
3031 // Check at the feed level for updated contact name and/or photo
3035 $photo_timestamp = '';
3037 $contact_updated = '';
3040 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
3042 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
3044 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
3047 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
3048 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
3049 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
3050 $new_name = $elems['name'][0]['data'];
3052 // Manually checking for changed contact names
3053 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
3054 $name_updated = date("c");
3055 $photo_timestamp = date("c");
3058 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
3059 if ($photo_timestamp == "")
3060 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
3061 $photo_url = $elems['link'][0]['attribs']['']['href'];
3065 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3067 $contact_updated = $photo_timestamp;
3069 logger('local_delivery: Updating photo for ' . $importer['name']);
3070 require_once("include/Photo.php");
3071 $photo_failure = false;
3072 $have_photo = false;
3074 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
3075 intval($importer['id']),
3076 intval($importer['importer_uid'])
3079 $resource_id = $r[0]['resource-id'];
3083 $resource_id = photo_new_resource();
3086 $img_str = fetch_url($photo_url,true);
3087 // guess mimetype from headers or filename
3088 $type = guess_image_type($photo_url,true);
3091 $img = new Photo($img_str, $type);
3092 if($img->is_valid()) {
3094 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
3095 dbesc($resource_id),
3096 intval($importer['id']),
3097 intval($importer['importer_uid'])
3101 $img->scaleImageSquare(175);
3103 $hash = $resource_id;
3104 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
3106 $img->scaleImage(80);
3107 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
3109 $img->scaleImage(48);
3110 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3114 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3115 WHERE `uid` = %d AND `id` = %d",
3116 dbesc(datetime_convert()),
3117 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3118 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3119 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3120 intval($importer['importer_uid']),
3121 intval($importer['id'])
3126 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3127 if ($name_updated > $contact_updated)
3128 $contact_updated = $name_updated;
3130 $r = q("select * from contact where uid = %d and id = %d limit 1",
3131 intval($importer['importer_uid']),
3132 intval($importer['id'])
3135 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3136 dbesc(notags(trim($new_name))),
3137 dbesc(datetime_convert()),
3138 intval($importer['importer_uid']),
3139 intval($importer['id'])
3142 // do our best to update the name on content items
3145 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3146 dbesc(notags(trim($new_name))),
3147 dbesc($r[0]['name']),
3148 dbesc($r[0]['url']),
3149 intval($importer['importer_uid'])
3154 if ($contact_updated AND $new_name AND $photo_url)
3155 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3157 // Currently unsupported - needs a lot of work
3158 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3159 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3160 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3162 $newloc['uid'] = $importer['importer_uid'];
3163 $newloc['cid'] = $importer['id'];
3164 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3165 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3166 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3167 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3168 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3169 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3170 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3171 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3172 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3173 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3174 /** relocated user must have original key pair */
3175 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3176 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3178 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3181 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3182 intval($importer['id']),
3183 intval($importer['importer_uid']));
3188 $x = q("UPDATE contact SET
3199 `site-pubkey` = '%s'
3200 WHERE id=%d AND uid=%d;",
3201 dbesc($newloc['name']),
3202 dbesc($newloc['photo']),
3203 dbesc($newloc['thumb']),
3204 dbesc($newloc['micro']),
3205 dbesc($newloc['url']),
3206 dbesc(normalise_link($newloc['url'])),
3207 dbesc($newloc['request']),
3208 dbesc($newloc['confirm']),
3209 dbesc($newloc['notify']),
3210 dbesc($newloc['poll']),
3211 dbesc($newloc['sitepubkey']),
3212 intval($importer['id']),
3213 intval($importer['importer_uid']));
3219 'owner-link' => array($old['url'], $newloc['url']),
3220 'author-link' => array($old['url'], $newloc['url']),
3221 'owner-avatar' => array($old['photo'], $newloc['photo']),
3222 'author-avatar' => array($old['photo'], $newloc['photo']),
3224 foreach ($fields as $n=>$f){
3225 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3228 intval($importer['importer_uid']));
3234 // merge with current record, current contents have priority
3235 // update record, set url-updated
3236 // update profile photos
3242 // handle friend suggestion notification
3244 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3245 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3246 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3248 $fsugg['uid'] = $importer['importer_uid'];
3249 $fsugg['cid'] = $importer['id'];
3250 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3251 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3252 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3253 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3254 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3256 // Does our member already have a friend matching this description?
3258 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3259 dbesc($fsugg['name']),
3260 dbesc(normalise_link($fsugg['url'])),
3261 intval($fsugg['uid'])
3266 // Do we already have an fcontact record for this person?
3269 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3270 dbesc($fsugg['url']),
3271 dbesc($fsugg['name']),
3272 dbesc($fsugg['request'])
3277 // OK, we do. Do we already have an introduction for this person ?
3278 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3279 intval($fsugg['uid']),
3286 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3287 dbesc($fsugg['name']),
3288 dbesc($fsugg['url']),
3289 dbesc($fsugg['photo']),
3290 dbesc($fsugg['request'])
3292 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3293 dbesc($fsugg['url']),
3294 dbesc($fsugg['name']),
3295 dbesc($fsugg['request'])
3300 // database record did not get created. Quietly give up.
3305 $hash = random_string();
3307 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3308 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3309 intval($fsugg['uid']),
3311 intval($fsugg['cid']),
3312 dbesc($fsugg['body']),
3314 dbesc(datetime_convert()),
3319 'type' => NOTIFY_SUGGEST,
3320 'notify_flags' => $importer['notify-flags'],
3321 'language' => $importer['language'],
3322 'to_name' => $importer['username'],
3323 'to_email' => $importer['email'],
3324 'uid' => $importer['importer_uid'],
3326 'link' => $a->get_baseurl() . '/notifications/intros',
3327 'source_name' => $importer['name'],
3328 'source_link' => $importer['url'],
3329 'source_photo' => $importer['photo'],
3330 'verb' => ACTIVITY_REQ_FRIEND,
3339 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3340 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3342 logger('local_delivery: private message received');
3345 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3348 $msg['uid'] = $importer['importer_uid'];
3349 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3350 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3351 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3352 $msg['contact-id'] = $importer['id'];
3353 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3354 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3356 $msg['replied'] = 0;
3357 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3358 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3359 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3363 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3364 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3366 // send notifications.
3368 require_once('include/enotify.php');
3370 $notif_params = array(
3371 'type' => NOTIFY_MAIL,
3372 'notify_flags' => $importer['notify-flags'],
3373 'language' => $importer['language'],
3374 'to_name' => $importer['username'],
3375 'to_email' => $importer['email'],
3376 'uid' => $importer['importer_uid'],
3378 'source_name' => $msg['from-name'],
3379 'source_link' => $importer['url'],
3380 'source_photo' => $importer['thumb'],
3381 'verb' => ACTIVITY_POST,
3385 notification($notif_params);
3391 $community_page = 0;
3392 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3394 $community_page = intval($rawtags[0]['data']);
3396 if(intval($importer['forum']) != $community_page) {
3397 q("update contact set forum = %d where id = %d",
3398 intval($community_page),
3399 intval($importer['id'])
3401 $importer['forum'] = (string) $community_page;
3404 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3406 // process any deleted entries
3408 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3409 if(is_array($del_entries) && count($del_entries)) {
3410 foreach($del_entries as $dentry) {
3412 if(isset($dentry['attribs']['']['ref'])) {
3413 $uri = $dentry['attribs']['']['ref'];
3415 if(isset($dentry['attribs']['']['when'])) {
3416 $when = $dentry['attribs']['']['when'];
3417 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3420 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3424 // check for relayed deletes to our conversation
3427 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3429 intval($importer['importer_uid'])
3432 $parent_uri = $r[0]['parent-uri'];
3433 if($r[0]['id'] != $r[0]['parent'])
3440 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3443 logger('local_delivery: possible community delete');
3446 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3448 // was the top-level post for this reply written by somebody on this site?
3449 // Specifically, the recipient?
3451 $is_a_remote_delete = false;
3453 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3454 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3455 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3456 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3457 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3458 AND `item`.`uid` = %d
3464 intval($importer['importer_uid'])
3467 $is_a_remote_delete = true;
3469 // Does this have the characteristics of a community or private group comment?
3470 // If it's a reply to a wall post on a community/prvgroup page it's a
3471 // valid community comment. Also forum_mode makes it valid for sure.
3472 // If neither, it's not.
3474 if($is_a_remote_delete && $community) {
3475 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3476 $is_a_remote_delete = false;
3477 logger('local_delivery: not a community delete');
3481 if($is_a_remote_delete) {
3482 logger('local_delivery: received remote delete');
3486 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3487 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3489 intval($importer['importer_uid']),
3490 intval($importer['id'])
3496 if($item['deleted'])
3499 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3501 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3502 $xo = parse_xml_string($item['object'],false);
3503 $xt = parse_xml_string($item['target'],false);
3505 if($xt->type === ACTIVITY_OBJ_NOTE) {
3506 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3508 intval($importer['importer_uid'])
3512 // For tags, the owner cannot remove the tag on the author's copy of the post.
3514 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3515 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3516 $author_copy = (($item['origin']) ? true : false);
3518 if($owner_remove && $author_copy)
3520 if($author_remove || $owner_remove) {
3521 $tags = explode(',',$i[0]['tag']);
3524 foreach($tags as $tag)
3525 if(trim($tag) !== trim($xo->body))
3526 $newtags[] = trim($tag);
3528 q("update item set tag = '%s' where id = %d",
3529 dbesc(implode(',',$newtags)),
3532 create_tags_from_item($i[0]['id']);
3538 if($item['uri'] == $item['parent-uri']) {
3539 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3540 `body` = '', `title` = ''
3541 WHERE `parent-uri` = '%s' AND `uid` = %d",
3543 dbesc(datetime_convert()),
3544 dbesc($item['uri']),
3545 intval($importer['importer_uid'])
3547 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3548 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3549 update_thread_uri($item['uri'], $importer['importer_uid']);
3552 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3553 `body` = '', `title` = ''
3554 WHERE `uri` = '%s' AND `uid` = %d",
3556 dbesc(datetime_convert()),
3558 intval($importer['importer_uid'])
3560 create_tags_from_itemuri($uri, $importer['importer_uid']);
3561 create_files_from_itemuri($uri, $importer['importer_uid']);
3562 update_thread_uri($uri, $importer['importer_uid']);
3563 if($item['last-child']) {
3564 // ensure that last-child is set in case the comment that had it just got wiped.
3565 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3566 dbesc(datetime_convert()),
3567 dbesc($item['parent-uri']),
3568 intval($item['uid'])
3570 // who is the last child now?
3571 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3572 ORDER BY `created` DESC LIMIT 1",
3573 dbesc($item['parent-uri']),
3574 intval($importer['importer_uid'])
3577 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3582 // if this is a relayed delete, propagate it to other recipients
3584 if($is_a_remote_delete)
3585 proc_run('php',"include/notifier.php","drop",$item['id']);
3593 foreach($feed->get_items() as $item) {
3596 $item_id = $item->get_id();
3597 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3598 if(isset($rawthread[0]['attribs']['']['ref'])) {
3600 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3606 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3609 logger('local_delivery: possible community reply');
3612 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3614 // was the top-level post for this reply written by somebody on this site?
3615 // Specifically, the recipient?
3617 $is_a_remote_comment = false;
3618 $top_uri = $parent_uri;
3620 $r = q("select `item`.`parent-uri` from `item`
3621 WHERE `item`.`uri` = '%s'
3625 if($r && count($r)) {
3626 $top_uri = $r[0]['parent-uri'];
3628 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3629 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3630 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3631 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3632 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3633 AND `item`.`uid` = %d
3639 intval($importer['importer_uid'])
3642 $is_a_remote_comment = true;
3645 // Does this have the characteristics of a community or private group comment?
3646 // If it's a reply to a wall post on a community/prvgroup page it's a
3647 // valid community comment. Also forum_mode makes it valid for sure.
3648 // If neither, it's not.
3650 if($is_a_remote_comment && $community) {
3651 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3652 $is_a_remote_comment = false;
3653 logger('local_delivery: not a community reply');
3657 if($is_a_remote_comment) {
3658 logger('local_delivery: received remote comment');
3660 // remote reply to our post. Import and then notify everybody else.
3662 $datarray = get_atom_elements($feed, $item);
3664 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3666 intval($importer['importer_uid'])
3669 // Update content if 'updated' changes
3673 if (edited_timestamp_is_newer($r[0], $datarray)) {
3675 // do not accept (ignore) an earlier edit than one we currently have.
3676 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3679 logger('received updated comment' , LOGGER_DEBUG);
3680 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3681 dbesc($datarray['title']),
3682 dbesc($datarray['body']),
3683 dbesc($datarray['tag']),
3684 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3685 dbesc(datetime_convert()),
3687 intval($importer['importer_uid'])
3689 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3691 proc_run('php',"include/notifier.php","comment-import",$iid);
3700 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3701 intval($importer['importer_uid'])
3705 $datarray['type'] = 'remote-comment';
3706 $datarray['wall'] = 1;
3707 $datarray['parent-uri'] = $parent_uri;
3708 $datarray['uid'] = $importer['importer_uid'];
3709 $datarray['owner-name'] = $own[0]['name'];
3710 $datarray['owner-link'] = $own[0]['url'];
3711 $datarray['owner-avatar'] = $own[0]['thumb'];
3712 $datarray['contact-id'] = $importer['id'];
3714 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3716 $datarray['type'] = 'activity';
3717 $datarray['gravity'] = GRAVITY_LIKE;
3718 $datarray['last-child'] = 0;
3719 // only one like or dislike per person
3720 // splitted into two queries for performance issues
3721 $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",
3722 intval($datarray['uid']),
3723 intval($datarray['contact-id']),
3724 dbesc($datarray['verb']),
3725 dbesc($datarray['parent-uri'])
3731 $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",
3732 intval($datarray['uid']),
3733 intval($datarray['contact-id']),
3734 dbesc($datarray['verb']),
3735 dbesc($datarray['parent-uri'])
3742 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3744 $xo = parse_xml_string($datarray['object'],false);
3745 $xt = parse_xml_string($datarray['target'],false);
3747 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3749 // fetch the parent item
3751 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3753 intval($importer['importer_uid'])
3758 // extract tag, if not duplicate, and this user allows tags, add to parent item
3760 if($xo->id && $xo->content) {
3761 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3762 if(! (stristr($tagp[0]['tag'],$newtag))) {
3763 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3764 intval($importer['importer_uid'])
3766 if(count($i) && ! intval($i[0]['blocktags'])) {
3767 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3768 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3769 intval($tagp[0]['id']),
3770 dbesc(datetime_convert()),
3771 dbesc(datetime_convert())
3773 create_tags_from_item($tagp[0]['id']);
3781 $posted_id = item_store($datarray);
3786 $datarray["id"] = $posted_id;
3788 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3790 intval($importer['importer_uid'])
3793 $parent = $r[0]['parent'];
3794 $parent_uri = $r[0]['parent-uri'];
3798 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3799 dbesc(datetime_convert()),
3800 intval($importer['importer_uid']),
3801 intval($r[0]['parent'])
3804 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3805 dbesc(datetime_convert()),
3806 intval($importer['importer_uid']),
3811 if($posted_id && $parent) {
3813 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3815 if((! $is_like) && (! $importer['self'])) {
3817 require_once('include/enotify.php');
3820 'type' => NOTIFY_COMMENT,
3821 'notify_flags' => $importer['notify-flags'],
3822 'language' => $importer['language'],
3823 'to_name' => $importer['username'],
3824 'to_email' => $importer['email'],
3825 'uid' => $importer['importer_uid'],
3826 'item' => $datarray,
3827 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3828 'source_name' => stripslashes($datarray['author-name']),
3829 'source_link' => $datarray['author-link'],
3830 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3831 ? $importer['thumb'] : $datarray['author-avatar']),
3832 'verb' => ACTIVITY_POST,
3834 'parent' => $parent,
3835 'parent_uri' => $parent_uri,
3847 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3849 $item_id = $item->get_id();
3850 $datarray = get_atom_elements($feed,$item);
3852 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3855 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3857 intval($importer['importer_uid'])
3860 // Update content if 'updated' changes
3863 if (edited_timestamp_is_newer($r[0], $datarray)) {
3865 // do not accept (ignore) an earlier edit than one we currently have.
3866 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3869 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3870 dbesc($datarray['title']),
3871 dbesc($datarray['body']),
3872 dbesc($datarray['tag']),
3873 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3874 dbesc(datetime_convert()),
3876 intval($importer['importer_uid'])
3878 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3881 // update last-child if it changes
3883 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3884 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3885 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3886 dbesc(datetime_convert()),
3888 intval($importer['importer_uid'])
3890 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3891 intval($allow[0]['data']),
3892 dbesc(datetime_convert()),
3894 intval($importer['importer_uid'])
3900 $datarray['parent-uri'] = $parent_uri;
3901 $datarray['uid'] = $importer['importer_uid'];
3902 $datarray['contact-id'] = $importer['id'];
3903 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3904 $datarray['type'] = 'activity';
3905 $datarray['gravity'] = GRAVITY_LIKE;
3906 // only one like or dislike per person
3907 // splitted into two queries for performance issues
3908 $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",
3909 intval($datarray['uid']),
3910 intval($datarray['contact-id']),
3911 dbesc($datarray['verb']),
3917 $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",
3918 intval($datarray['uid']),
3919 intval($datarray['contact-id']),
3920 dbesc($datarray['verb']),
3928 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3930 $xo = parse_xml_string($datarray['object'],false);
3931 $xt = parse_xml_string($datarray['target'],false);
3933 if($xt->type == ACTIVITY_OBJ_NOTE) {
3934 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3936 intval($importer['importer_uid'])
3941 // extract tag, if not duplicate, add to parent item
3943 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3944 q("UPDATE item SET tag = '%s' WHERE id = %d",
3945 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3948 create_tags_from_item($r[0]['id']);
3954 $posted_id = item_store($datarray);
3956 // find out if our user is involved in this conversation and wants to be notified.
3958 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3960 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3962 intval($importer['importer_uid'])
3965 if(count($myconv)) {
3966 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3968 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3969 if(! link_compare($datarray['author-link'],$importer_url)) {
3972 foreach($myconv as $conv) {
3974 // now if we find a match, it means we're in this conversation
3976 if(! link_compare($conv['author-link'],$importer_url))
3979 require_once('include/enotify.php');
3981 $conv_parent = $conv['parent'];
3984 'type' => NOTIFY_COMMENT,
3985 'notify_flags' => $importer['notify-flags'],
3986 'language' => $importer['language'],
3987 'to_name' => $importer['username'],
3988 'to_email' => $importer['email'],
3989 'uid' => $importer['importer_uid'],
3990 'item' => $datarray,
3991 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3992 'source_name' => stripslashes($datarray['author-name']),
3993 'source_link' => $datarray['author-link'],
3994 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3995 ? $importer['thumb'] : $datarray['author-avatar']),
3996 'verb' => ACTIVITY_POST,
3998 'parent' => $conv_parent,
3999 'parent_uri' => $parent_uri
4003 // only send one notification
4015 // Head post of a conversation. Have we seen it? If not, import it.
4018 $item_id = $item->get_id();
4019 $datarray = get_atom_elements($feed,$item);
4021 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
4022 $ev = bbtoevent($datarray['body']);
4023 if(x($ev,'desc') && x($ev,'start')) {
4024 $ev['cid'] = $importer['id'];
4025 $ev['uid'] = $importer['uid'];
4026 $ev['uri'] = $item_id;
4027 $ev['edited'] = $datarray['edited'];
4028 $ev['private'] = $datarray['private'];
4030 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4032 intval($importer['uid'])
4035 $ev['id'] = $r[0]['id'];
4036 $xyz = event_store($ev);
4041 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4043 intval($importer['importer_uid'])
4046 // Update content if 'updated' changes
4049 if (edited_timestamp_is_newer($r[0], $datarray)) {
4051 // do not accept (ignore) an earlier edit than one we currently have.
4052 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4055 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4056 dbesc($datarray['title']),
4057 dbesc($datarray['body']),
4058 dbesc($datarray['tag']),
4059 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4060 dbesc(datetime_convert()),
4062 intval($importer['importer_uid'])
4064 create_tags_from_itemuri($item_id, $importer['importer_uid']);
4065 update_thread_uri($item_id, $importer['importer_uid']);
4068 // update last-child if it changes
4070 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4071 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4072 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4073 intval($allow[0]['data']),
4074 dbesc(datetime_convert()),
4076 intval($importer['importer_uid'])
4082 $datarray['parent-uri'] = $item_id;
4083 $datarray['uid'] = $importer['importer_uid'];
4084 $datarray['contact-id'] = $importer['id'];
4087 if(! link_compare($datarray['owner-link'],$importer['url'])) {
4088 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4089 // but otherwise there's a possible data mixup on the sender's system.
4090 // the tgroup delivery code called from item_store will correct it if it's a forum,
4091 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4092 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4093 $datarray['owner-name'] = $importer['senderName'];
4094 $datarray['owner-link'] = $importer['url'];
4095 $datarray['owner-avatar'] = $importer['thumb'];
4098 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4101 // This is my contact on another system, but it's really me.
4102 // Turn this into a wall post.
4103 $notify = item_is_remote_self($importer, $datarray);
4105 $posted_id = item_store($datarray, false, $notify);
4107 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4108 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4111 $xo = parse_xml_string($datarray['object'],false);
4113 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4115 // somebody was poked/prodded. Was it me?
4117 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4119 foreach($links->link as $l) {
4120 $atts = $l->attributes();
4121 switch($atts['rel']) {
4123 $Blink = $atts['href'];
4129 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4131 // send a notification
4132 require_once('include/enotify.php');
4135 'type' => NOTIFY_POKE,
4136 'notify_flags' => $importer['notify-flags'],
4137 'language' => $importer['language'],
4138 'to_name' => $importer['username'],
4139 'to_email' => $importer['email'],
4140 'uid' => $importer['importer_uid'],
4141 'item' => $datarray,
4142 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4143 'source_name' => stripslashes($datarray['author-name']),
4144 'source_link' => $datarray['author-link'],
4145 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4146 ? $importer['thumb'] : $datarray['author-avatar']),
4147 'verb' => $datarray['verb'],
4148 'otype' => 'person',
4149 'activity' => $verb,
4150 'parent' => $datarray['parent']
4166 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4167 $url = notags(trim($datarray['author-link']));
4168 $name = notags(trim($datarray['author-name']));
4169 $photo = notags(trim($datarray['author-avatar']));
4171 if (is_object($item)) {
4172 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4173 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4174 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4178 if(is_array($contact)) {
4179 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4180 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4181 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4182 intval(CONTACT_IS_FRIEND),
4183 intval($contact['id']),
4184 intval($importer['uid'])
4187 // send email notification to owner?
4191 // create contact record
4193 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4194 `blocked`, `readonly`, `pending`, `writable` )
4195 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4196 intval($importer['uid']),
4197 dbesc(datetime_convert()),
4199 dbesc(normalise_link($url)),
4203 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4204 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4206 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4207 intval($importer['uid']),
4211 $contact_record = $r[0];
4213 // create notification
4214 $hash = random_string();
4216 if(is_array($contact_record)) {
4217 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4218 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4219 intval($importer['uid']),
4220 intval($contact_record['id']),
4222 dbesc(datetime_convert())
4226 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4227 intval($importer['uid'])
4232 if(intval($r[0]['def_gid'])) {
4233 require_once('include/group.php');
4234 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4237 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4238 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4241 'type' => NOTIFY_INTRO,
4242 'notify_flags' => $r[0]['notify-flags'],
4243 'language' => $r[0]['language'],
4244 'to_name' => $r[0]['username'],
4245 'to_email' => $r[0]['email'],
4246 'uid' => $r[0]['uid'],
4247 'link' => $a->get_baseurl() . '/notifications/intro',
4248 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4249 'source_link' => $contact_record['url'],
4250 'source_photo' => $contact_record['photo'],
4251 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4260 function lose_follower($importer,$contact,$datarray,$item) {
4262 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4263 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4264 intval(CONTACT_IS_SHARING),
4265 intval($contact['id'])
4269 contact_remove($contact['id']);
4273 function lose_sharer($importer,$contact,$datarray,$item) {
4275 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4276 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4277 intval(CONTACT_IS_FOLLOWER),
4278 intval($contact['id'])
4282 contact_remove($contact['id']);
4287 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4291 if(is_array($importer)) {
4292 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4293 intval($importer['uid'])
4297 // Diaspora has different message-ids in feeds than they do
4298 // through the direct Diaspora protocol. If we try and use
4299 // the feed, we'll get duplicates. So don't.
4301 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4304 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4306 // Use a single verify token, even if multiple hubs
4308 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4310 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4312 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4314 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
4315 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4316 dbesc($verify_token),
4317 intval($contact['id'])
4321 post_url($url,$params);
4323 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4330 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4334 $name = xmlify($name);
4335 $uri = xmlify($uri);
4338 $photo = xmlify($photo);
4342 $o .= "\t<name>$name</name>\r\n";
4343 $o .= "\t<uri>$uri</uri>\r\n";
4344 $o .= "\t".'<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4345 $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4347 if ($tag == "author") {
4348 $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
4349 `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
4350 `profile`.`homepage`,`contact`.`nick` FROM `profile`
4351 INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
4352 INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
4353 WHERE `profile`.`is-default` AND `contact`.`self` AND
4354 NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
4355 dbesc(normalise_link($uri)));
4358 if($r[0]['locality'])
4359 $location .= $r[0]['locality'];
4360 if($r[0]['region']) {
4363 $location .= $r[0]['region'];
4365 if($r[0]['country-name']) {
4368 $location .= $r[0]['country-name'];
4371 $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
4372 $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
4373 $o .= "\t<poco:note>".xmlify($r[0]["about"])."</poco:note>\r\n";
4374 $o .= "\t<poco:address>\r\n";
4375 $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
4376 $o .= "\t</poco:address>\r\n";
4377 $o .= "\t<poco:urls>\r\n";
4378 $o .= "\t<poco:type>homepage</poco:type>\r\n";
4379 $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
4380 $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
4381 $o .= "\t</poco:urls>\r\n";
4385 call_hooks('atom_author', $o);
4387 $o .= "</$tag>\r\n";
4391 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4395 if(! $item['parent'])
4398 if($item['deleted'])
4399 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4402 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4403 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4405 $body = $item['body'];
4408 $o = "\r\n\r\n<entry>\r\n";
4410 if(is_array($author))
4411 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4413 $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']));
4414 if(strlen($item['owner-name']))
4415 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4417 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4418 $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
4419 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4420 $o .= '<thr:in-reply-to ref="'.xmlify($parent_item).'" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$parent[0]['guid']).'" />'."\r\n";
4425 if ($item['title'] != "")
4426 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4428 $htmlbody = bbcode($htmlbody, false, false, 7);
4430 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4431 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4432 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4433 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4434 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4435 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4436 $o .= '<link rel="alternate" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$item['guid']).'" />'."\r\n";
4438 $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
4441 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4443 if($item['location']) {
4444 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4445 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4449 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4451 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4452 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4455 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4456 if($item['bookmark'])
4457 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4460 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4463 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4465 if($item['signed_text']) {
4466 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4467 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4470 $verb = construct_verb($item);
4471 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4472 $actobj = construct_activity_object($item);
4475 $actarg = construct_activity_target($item);
4479 $tags = item_getfeedtags($item);
4481 foreach($tags as $t)
4482 if (($type != 'html') OR ($t[0] != "@"))
4483 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4487 // To support these elements, the API needs to be enhanced
4488 //$o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
4489 //$o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4490 //$o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4492 $o .= item_get_attachment($item);
4494 $o .= item_getfeedattach($item);
4496 $mentioned = get_mentions($item);
4500 call_hooks('atom_entry', $o);
4502 $o .= '</entry>' . "\r\n";
4507 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4509 if(get_config('system','disable_embedded'))
4514 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4515 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4520 $img_start = strpos($orig_body, '[img');
4521 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4522 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4523 while( ($img_st_close !== false) && ($img_len !== false) ) {
4525 $img_st_close++; // make it point to AFTER the closing bracket
4526 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4528 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4531 if(stristr($image , $site . '/photo/')) {
4532 // Only embed locally hosted photos
4534 $i = basename($image);
4535 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4536 $x = strpos($i,'-');
4539 $res = substr($i,$x+1);
4540 $i = substr($i,0,$x);
4541 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4548 // Check to see if we should replace this photo link with an embedded image
4549 // 1. No need to do so if the photo is public
4550 // 2. If there's a contact-id provided, see if they're in the access list
4551 // for the photo. If so, embed it.
4552 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4553 // permissions, regardless of order but first check to see if they're an exact
4554 // match to save some processing overhead.
4556 if(has_permissions($r[0])) {
4558 $recips = enumerate_permissions($r[0]);
4559 if(in_array($cid, $recips)) {
4564 if(compare_permissions($item,$r[0]))
4569 $data = $r[0]['data'];
4570 $type = $r[0]['type'];
4572 // If a custom width and height were specified, apply before embedding
4573 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4574 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4576 $width = intval($match[1]);
4577 $height = intval($match[2]);
4579 $ph = new Photo($data, $type);
4580 if($ph->is_valid()) {
4581 $ph->scaleImage(max($width, $height));
4582 $data = $ph->imageString();
4583 $type = $ph->getType();
4587 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4588 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4589 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4595 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4596 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4597 if($orig_body === false)
4600 $img_start = strpos($orig_body, '[img');
4601 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4602 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4605 $new_body = $new_body . $orig_body;
4611 function has_permissions($obj) {
4612 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4617 function compare_permissions($obj1,$obj2) {
4618 // first part is easy. Check that these are exactly the same.
4619 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4620 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4621 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4622 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4625 // This is harder. Parse all the permissions and compare the resulting set.
4627 $recipients1 = enumerate_permissions($obj1);
4628 $recipients2 = enumerate_permissions($obj2);
4631 if($recipients1 == $recipients2)
4636 // returns an array of contact-ids that are allowed to see this object
4638 function enumerate_permissions($obj) {
4639 require_once('include/group.php');
4640 $allow_people = expand_acl($obj['allow_cid']);
4641 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4642 $deny_people = expand_acl($obj['deny_cid']);
4643 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4644 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4645 $deny = array_unique(array_merge($deny_people,$deny_groups));
4646 $recipients = array_diff($recipients,$deny);
4650 function item_getfeedtags($item) {
4653 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4655 for($x = 0; $x < $cnt; $x ++) {
4657 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4661 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4663 for($x = 0; $x < $cnt; $x ++) {
4665 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4671 function item_get_attachment($item) {
4673 $siteinfo = get_attached_data($item["body"]);
4675 switch($siteinfo["type"]) {
4677 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4680 $imgdata = get_photo_info($siteinfo["image"]);
4681 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
4684 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4693 function item_getfeedattach($item) {
4695 $arr = explode('[/attach],',$item['attach']);
4697 foreach($arr as $r) {
4699 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4701 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4702 if(intval($matches[2]))
4703 $ret .= 'length="' . intval($matches[2]) . '" ';
4704 if($matches[4] !== ' ')
4705 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4706 $ret .= ' />' . "\r\n";
4715 function item_expire($uid, $days, $network = "", $force = false) {
4717 if((! $uid) || ($days < 1))
4720 // $expire_network_only = save your own wall posts
4721 // and just expire conversations started by others
4723 $expire_network_only = get_pconfig($uid,'expire','network_only');
4724 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4726 if ($network != "") {
4727 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4728 // There is an index "uid_network_received" but not "uid_network_created"
4729 // This avoids the creation of another index just for one purpose.
4730 // And it doesn't really matter wether to look at "received" or "created"
4731 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4733 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4735 $r = q("SELECT * FROM `item`
4736 WHERE `uid` = %d $range
4747 $expire_items = get_pconfig($uid, 'expire','items');
4748 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4750 // Forcing expiring of items - but not notes and marked items
4752 $expire_items = true;
4754 $expire_notes = get_pconfig($uid, 'expire','notes');
4755 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4757 $expire_starred = get_pconfig($uid, 'expire','starred');
4758 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4760 $expire_photos = get_pconfig($uid, 'expire','photos');
4761 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4763 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4765 foreach($r as $item) {
4767 // don't expire filed items
4769 if(strpos($item['file'],'[') !== false)
4772 // Only expire posts, not photos and photo comments
4774 if($expire_photos==0 && strlen($item['resource-id']))
4776 if($expire_starred==0 && intval($item['starred']))
4778 if($expire_notes==0 && $item['type']=='note')
4780 if($expire_items==0 && $item['type']!='note')
4783 drop_item($item['id'],false);
4786 proc_run('php',"include/notifier.php","expire","$uid");
4791 function drop_items($items) {
4794 if(! local_user() && ! remote_user())
4798 foreach($items as $item) {
4799 $owner = drop_item($item,false);
4800 if($owner && ! $uid)
4805 // multiple threads may have been deleted, send an expire notification
4808 proc_run('php',"include/notifier.php","expire","$uid");
4812 function drop_item($id,$interactive = true) {
4816 // locate item to be deleted
4818 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4825 notice( t('Item not found.') . EOL);
4826 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4831 $owner = $item['uid'];
4835 // check if logged in user is either the author or owner of this item
4837 if(is_array($_SESSION['remote'])) {
4838 foreach($_SESSION['remote'] as $visitor) {
4839 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4840 $cid = $visitor['cid'];
4847 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4849 // Check if we should do HTML-based delete confirmation
4850 if($_REQUEST['confirm']) {
4851 // <form> can't take arguments in its "action" parameter
4852 // so add any arguments as hidden inputs
4853 $query = explode_querystring($a->query_string);
4855 foreach($query['args'] as $arg) {
4856 if(strpos($arg, 'confirm=') === false) {
4857 $arg_parts = explode('=', $arg);
4858 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4862 return replace_macros(get_markup_template('confirm.tpl'), array(
4864 '$message' => t('Do you really want to delete this item?'),
4865 '$extra_inputs' => $inputs,
4866 '$confirm' => t('Yes'),
4867 '$confirm_url' => $query['base'],
4868 '$confirm_name' => 'confirmed',
4869 '$cancel' => t('Cancel'),
4872 // Now check how the user responded to the confirmation query
4873 if($_REQUEST['canceled']) {
4874 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4877 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4880 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4881 dbesc(datetime_convert()),
4882 dbesc(datetime_convert()),
4885 create_tags_from_item($item['id']);
4886 create_files_from_item($item['id']);
4887 delete_thread($item['id'], $item['parent-uri']);
4889 // clean up categories and tags so they don't end up as orphans
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],true);
4901 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4903 foreach($matches as $mtch) {
4904 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4908 // If item is a link to a photo resource, nuke all the associated photos
4909 // (visitors will not have photo resources)
4910 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4911 // generate a resource-id and therefore aren't intimately linked to the item.
4913 if(strlen($item['resource-id'])) {
4914 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4915 dbesc($item['resource-id']),
4916 intval($item['uid'])
4918 // ignore the result
4921 // If item is a link to an event, nuke the event record.
4923 if(intval($item['event-id'])) {
4924 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4925 intval($item['event-id']),
4926 intval($item['uid'])
4928 // ignore the result
4931 // If item has attachments, drop them
4933 foreach(explode(",",$item['attach']) as $attach){
4934 preg_match("|attach/(\d+)|", $attach, $matches);
4935 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4936 intval($matches[1]),
4939 // ignore the result
4943 // clean up item_id and sign meta-data tables
4946 // Old code - caused very long queries and warning entries in the mysql logfiles:
4948 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4949 intval($item['id']),
4950 intval($item['uid'])
4953 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4954 intval($item['id']),
4955 intval($item['uid'])
4959 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4961 // Creating list of parents
4962 $r = q("select id from item where parent = %d and uid = %d",
4963 intval($item['id']),
4964 intval($item['uid'])
4969 foreach ($r AS $row) {
4970 if ($parentid != "")
4973 $parentid .= $row["id"];
4977 if ($parentid != "") {
4978 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
4980 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
4983 // If it's the parent of a comment thread, kill all the kids
4985 if($item['uri'] == $item['parent-uri']) {
4986 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
4987 WHERE `parent-uri` = '%s' AND `uid` = %d ",
4988 dbesc(datetime_convert()),
4989 dbesc(datetime_convert()),
4990 dbesc($item['parent-uri']),
4991 intval($item['uid'])
4993 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
4994 create_files_from_itemuri($item['parent-uri'], $item['uid']);
4995 delete_thread_uri($item['parent-uri'], $item['uid']);
4996 // ignore the result
4999 // ensure that last-child is set in case the comment that had it just got wiped.
5000 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
5001 dbesc(datetime_convert()),
5002 dbesc($item['parent-uri']),
5003 intval($item['uid'])
5005 // who is the last child now?
5006 $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",
5007 dbesc($item['parent-uri']),
5008 intval($item['uid'])
5011 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
5016 // Add a relayable_retraction signature for Diaspora.
5017 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
5020 $drop_id = intval($item['id']);
5022 // send the notification upstream/downstream as the case may be
5024 proc_run('php',"include/notifier.php","drop","$drop_id");
5028 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5034 notice( t('Permission denied.') . EOL);
5035 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5042 function first_post_date($uid,$wall = false) {
5043 $r = q("select id, created from item
5044 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
5046 order by created asc limit 1",
5048 intval($wall ? 1 : 0)
5051 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
5052 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
5057 /* modified posted_dates() {below} to arrange the list in years */
5058 function list_post_dates($uid, $wall) {
5059 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5061 $dthen = first_post_date($uid, $wall);
5065 // Set the start and end date to the beginning of the month
5066 $dnow = substr($dnow,0,8).'01';
5067 $dthen = substr($dthen,0,8).'01';
5071 // Starting with the current month, get the first and last days of every
5072 // month down to and including the month of the first post
5073 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5074 $dyear = intval(substr($dnow,0,4));
5075 $dstart = substr($dnow,0,8) . '01';
5076 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5077 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5078 $end_month = datetime_convert('','',$dend,'Y-m-d');
5079 $str = day_translate(datetime_convert('','',$dnow,'F'));
5081 $ret[$dyear] = array();
5082 $ret[$dyear][] = array($str,$end_month,$start_month);
5083 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5088 function posted_dates($uid,$wall) {
5089 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5091 $dthen = first_post_date($uid,$wall);
5095 // Set the start and end date to the beginning of the month
5096 $dnow = substr($dnow,0,8).'01';
5097 $dthen = substr($dthen,0,8).'01';
5100 // Starting with the current month, get the first and last days of every
5101 // month down to and including the month of the first post
5102 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5103 $dstart = substr($dnow,0,8) . '01';
5104 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5105 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5106 $end_month = datetime_convert('','',$dend,'Y-m-d');
5107 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5108 $ret[] = array($str,$end_month,$start_month);
5109 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5115 function posted_date_widget($url,$uid,$wall) {
5118 if(! feature_enabled($uid,'archives'))
5121 // For former Facebook folks that left because of "timeline"
5123 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5126 $visible_years = get_pconfig($uid,'system','archive_visible_years');
5127 if(! $visible_years)
5130 $ret = list_post_dates($uid,$wall);
5135 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5136 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5138 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5139 '$title' => t('Archives'),
5140 '$size' => $visible_years,
5141 '$cutoff_year' => $cutoff_year,
5142 '$cutoff' => $cutoff,
5145 '$showmore' => t('show more')
5151 function store_diaspora_retract_sig($item, $user, $baseurl) {
5152 // Note that we can't add a target_author_signature
5153 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5154 // the comment, that means we're the home of the post, and Diaspora will only
5155 // check the parent_author_signature of retractions that it doesn't have to relay further
5157 // I don't think this function gets called for an "unlike," but I'll check anyway
5159 $enabled = intval(get_config('system','diaspora_enabled'));
5161 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5165 logger('drop_item: storing diaspora retraction signature');
5167 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5169 if(local_user() == $item['uid']) {
5171 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5172 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5175 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5176 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5179 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5180 // only handles DFRN deletes
5181 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5182 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5183 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5189 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5190 intval($item['id']),
5191 dbesc($signed_text),