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 // Adds a "lang" specification in a "postopts" element of given $arr,
1100 // if possible and not already present.
1101 // Expects "body" element to exist in $arr.
1102 // TODO: add a parameter to request forcing override
1103 function item_add_language_opt(&$arr) {
1105 if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
1107 if ( x($arr, 'postopts') )
1109 if ( strstr($arr['postopts'], 'lang=') )
1112 // TODO: add parameter to request overriding
1115 $postopts = $arr['postopts'];
1122 require_once('library/langdet/Text/LanguageDetect.php');
1123 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1124 $l = new Text_LanguageDetect;
1125 //$lng = $l->detectConfidence($naked_body);
1126 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1127 $lng = $l->detect($naked_body, 3);
1129 if (sizeof($lng) > 0) {
1130 if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
1131 $postopts .= 'lang=';
1133 foreach ($lng as $language => $score) {
1134 $postopts .= $sep . $language.";".$score;
1137 $arr['postopts'] = $postopts;
1141 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1143 // If it is a posting where users should get notifications, then define it as wall posting
1146 $arr['type'] = 'wall';
1148 $arr['last-child'] = 1;
1149 $arr['network'] = NETWORK_DFRN;
1152 // If a Diaspora signature structure was passed in, pull it out of the
1153 // item array and set it aside for later storage.
1156 if(x($arr,'dsprsig')) {
1157 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1158 unset($arr['dsprsig']);
1161 // Converting the plink
1162 if ($arr['network'] == NETWORK_OSTATUS) {
1163 if (isset($arr['plink']))
1164 $arr['plink'] = ostatus_convert_href($arr['plink']);
1165 elseif (isset($arr['uri']))
1166 $arr['plink'] = ostatus_convert_href($arr['uri']);
1169 if(x($arr, 'gravity'))
1170 $arr['gravity'] = intval($arr['gravity']);
1171 elseif($arr['parent-uri'] === $arr['uri'])
1172 $arr['gravity'] = 0;
1173 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1174 $arr['gravity'] = 6;
1176 $arr['gravity'] = 6; // extensible catchall
1178 if(! x($arr,'type'))
1179 $arr['type'] = 'remote';
1183 /* check for create date and expire time */
1184 $uid = intval($arr['uid']);
1185 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
1187 $expire_interval = $r[0]['expire'];
1188 if ($expire_interval>0) {
1189 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1190 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1191 if ($created_date < $expire_date) {
1192 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1198 // If there is no guid then take the same guid that was taken before for the same uri
1199 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
1200 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1201 $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
1202 dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
1205 $arr['guid'] = $r[0]["guid"];
1206 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1210 // If there is no guid then take the same guid that was taken before for the same plink
1211 if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
1212 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
1213 $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
1214 dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
1217 $arr['guid'] = $r[0]["guid"];
1218 logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
1220 if ($r[0]["uri"] != $arr['uri'])
1221 logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
1225 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1226 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1227 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1228 // $arr['body'] = strip_tags($arr['body']);
1230 item_add_language_opt($arr);
1235 $guid_prefix = $arr['network'];
1237 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1238 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
1239 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
1240 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1241 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1242 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1243 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1244 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1245 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1246 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1247 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1248 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1249 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1250 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1251 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1252 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1253 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1254 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1255 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1256 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1257 $arr['deleted'] = 0;
1258 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1259 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1260 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1261 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1262 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1263 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1264 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1265 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1266 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1267 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1268 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1269 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1270 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1271 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1272 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1273 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1274 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1275 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1276 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1277 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1278 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1279 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1280 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1281 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1283 if ($arr['plink'] == "") {
1285 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1288 if ($arr['network'] == "") {
1289 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1290 intval($arr['contact-id']),
1295 $arr['network'] = $r[0]["network"];
1297 // Fallback to friendica (why is it empty in some cases?)
1298 if ($arr['network'] == "")
1299 $arr['network'] = NETWORK_DFRN;
1301 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1304 if ($arr['guid'] != "") {
1305 // Checking if there is already an item with the same guid
1306 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1307 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1308 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1311 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1316 // Check for hashtags in the body and repair or add hashtag links
1317 item_body_set_hashtags($arr);
1319 $arr['thr-parent'] = $arr['parent-uri'];
1320 if($arr['parent-uri'] === $arr['uri']) {
1322 $parent_deleted = 0;
1323 $allow_cid = $arr['allow_cid'];
1324 $allow_gid = $arr['allow_gid'];
1325 $deny_cid = $arr['deny_cid'];
1326 $deny_gid = $arr['deny_gid'];
1327 $notify_type = 'wall-new';
1331 // find the parent and snarf the item id and ACLs
1332 // and anything else we need to inherit
1334 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1335 dbesc($arr['parent-uri']),
1341 // is the new message multi-level threaded?
1342 // even though we don't support it now, preserve the info
1343 // and re-attach to the conversation parent.
1345 if($r[0]['uri'] != $r[0]['parent-uri']) {
1346 $arr['parent-uri'] = $r[0]['parent-uri'];
1347 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1348 ORDER BY `id` ASC LIMIT 1",
1349 dbesc($r[0]['parent-uri']),
1350 dbesc($r[0]['parent-uri']),
1357 $parent_id = $r[0]['id'];
1358 $parent_deleted = $r[0]['deleted'];
1359 $allow_cid = $r[0]['allow_cid'];
1360 $allow_gid = $r[0]['allow_gid'];
1361 $deny_cid = $r[0]['deny_cid'];
1362 $deny_gid = $r[0]['deny_gid'];
1363 $arr['wall'] = $r[0]['wall'];
1364 $notify_type = 'comment-new';
1366 // if the parent is private, force privacy for the entire conversation
1367 // This differs from the above settings as it subtly allows comments from
1368 // email correspondents to be private even if the overall thread is not.
1370 if($r[0]['private'])
1371 $arr['private'] = $r[0]['private'];
1373 // Edge case. We host a public forum that was originally posted to privately.
1374 // The original author commented, but as this is a comment, the permissions
1375 // weren't fixed up so it will still show the comment as private unless we fix it here.
1377 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1378 $arr['private'] = 0;
1381 // If its a post from myself then tag the thread as "mention"
1382 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1383 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1386 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1387 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1388 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1389 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1390 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1396 // Allow one to see reply tweets from status.net even when
1397 // we don't have or can't see the original post.
1400 logger('item_store: $force_parent=true, reply converted to top-level post.');
1402 $arr['parent-uri'] = $arr['uri'];
1403 $arr['gravity'] = 0;
1406 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1410 $parent_deleted = 0;
1414 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1416 dbesc($arr['network']),
1419 if($r && count($r)) {
1420 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1424 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1425 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1426 dbesc($arr['body']),
1427 dbesc($arr['network']),
1428 dbesc($arr['created']),
1429 intval($arr['contact-id']),
1432 if($r && count($r)) {
1433 logger('duplicated item with the same body found. ' . print_r($arr,true));
1437 // Is this item available in the global items (with uid=0)?
1438 if ($arr["uid"] == 0) {
1439 $arr["global"] = true;
1441 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1443 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1445 $arr["global"] = (count($isglobal) > 0);
1448 // Fill the cache field
1449 put_item_in_cache($arr);
1452 call_hooks('post_local',$arr);
1454 call_hooks('post_remote',$arr);
1456 if(x($arr,'cancel')) {
1457 logger('item_store: post cancelled by plugin.');
1461 // Store the unescaped version
1466 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1468 $r = dbq("INSERT INTO `item` (`"
1469 . implode("`, `", array_keys($arr))
1471 . implode("', '", array_values($arr))
1477 // find the item we just created
1478 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1485 // Store the guid and other relevant data
1488 $current_post = $r[0]['id'];
1489 logger('item_store: created item ' . $current_post);
1491 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1492 // This can be used to filter for inactive contacts.
1493 // Only do this for public postings to avoid privacy problems, since poco data is public.
1494 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1496 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1498 // Is it a forum? Then we don't care about the rules from above
1499 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1500 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1501 intval($arr['contact-id']));
1507 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1508 dbesc($arr['received']),
1509 dbesc($arr['received']),
1510 intval($arr['contact-id'])
1513 logger('item_store: could not locate created item');
1517 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1518 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1520 intval($arr['uid']),
1521 intval($current_post)
1525 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1526 $parent_id = $current_post;
1528 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1531 $private = $arr['private'];
1533 // Set parent id - and also make sure to inherit the parent's ACLs.
1535 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1536 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1543 intval($parent_deleted),
1544 intval($current_post)
1547 $arr['id'] = $current_post;
1548 $arr['parent'] = $parent_id;
1549 $arr['allow_cid'] = $allow_cid;
1550 $arr['allow_gid'] = $allow_gid;
1551 $arr['deny_cid'] = $deny_cid;
1552 $arr['deny_gid'] = $deny_gid;
1553 $arr['private'] = $private;
1554 $arr['deleted'] = $parent_deleted;
1556 // update the commented timestamp on the parent
1557 // Only update "commented" if it is really a comment
1558 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1559 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1560 dbesc(datetime_convert()),
1561 dbesc(datetime_convert()),
1565 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1566 dbesc(datetime_convert()),
1571 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1572 intval($current_post),
1573 dbesc($dsprsig->signed_text),
1574 dbesc($dsprsig->signature),
1575 dbesc($dsprsig->signer)
1581 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1584 if($arr['last-child']) {
1585 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1587 intval($arr['uid']),
1588 intval($current_post)
1592 $deleted = tag_deliver($arr['uid'],$current_post);
1594 // current post can be deleted if is for a community page and no mention are
1596 if (!$deleted AND !$dontcache) {
1598 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1599 if (count($r) == 1) {
1601 call_hooks('post_local_end', $r[0]);
1603 call_hooks('post_remote_end', $r[0]);
1605 logger('item_store: new item not found in DB, id ' . $current_post);
1608 // Add every contact of the post to the global contact table
1611 create_tags_from_item($current_post);
1612 create_files_from_item($current_post);
1614 // Only check for notifications on start posts
1615 if ($arr['parent-uri'] === $arr['uri']) {
1616 add_thread($current_post);
1617 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1619 // Send a notification for every new post?
1620 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1621 intval($arr['contact-id']),
1624 $send_notification = count($r);
1626 if (!$send_notification) {
1627 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1628 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1631 foreach ($tags AS $tag) {
1632 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1633 normalise_link($tag["url"]), intval($arr['uid']));
1635 $send_notification = true;
1640 if ($send_notification) {
1641 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1642 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1643 intval($arr['uid']));
1645 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1646 intval($current_post),
1652 require_once('include/enotify.php');
1654 'type' => NOTIFY_SHARE,
1655 'notify_flags' => $u[0]['notify-flags'],
1656 'language' => $u[0]['language'],
1657 'to_name' => $u[0]['username'],
1658 'to_email' => $u[0]['email'],
1659 'uid' => $u[0]['uid'],
1661 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1662 'source_name' => $item[0]['author-name'],
1663 'source_link' => $item[0]['author-link'],
1664 'source_photo' => $item[0]['author-avatar'],
1665 'verb' => ACTIVITY_TAG,
1667 'parent' => $arr['parent']
1669 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1672 update_thread($parent_id);
1673 add_shadow_entry($arr);
1677 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1679 return $current_post;
1682 function item_body_set_hashtags(&$item) {
1684 $tags = get_tags($item["body"]);
1690 // This sorting is important when there are hashtags that are part of other hashtags
1691 // Otherwise there could be problems with hashtags like #test and #test2
1696 $URLSearchString = "^\[\]";
1698 // All hashtags should point to the home server
1699 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1700 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1702 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1703 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1705 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1706 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1708 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1711 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1713 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1716 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1718 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1721 // Repair recursive urls
1722 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1723 "#$2", $item["body"]);
1726 foreach($tags as $tag) {
1727 if(strpos($tag,'#') !== 0)
1730 if(strpos($tag,'[url='))
1733 $basetag = str_replace('_',' ',substr($tag,1));
1735 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1737 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1739 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1740 if(strlen($item["tag"]))
1741 $item["tag"] = ','.$item["tag"];
1742 $item["tag"] = $newtag.$item["tag"];
1746 // Convert back the masked hashtags
1747 $item["body"] = str_replace("#", "#", $item["body"]);
1750 function get_item_guid($id) {
1751 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1753 return($r[0]["guid"]);
1758 function get_item_id($guid, $uid = 0) {
1764 $uid == local_user();
1766 // Does the given user have this item?
1768 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1769 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1770 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1773 $nick = $r[0]["nickname"];
1777 // Or is it anywhere on the server?
1779 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1780 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1781 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1782 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1783 AND `item`.`private` = 0 AND `item`.`wall` = 1
1784 AND `item`.`guid` = '%s'", dbesc($guid));
1787 $nick = $r[0]["nickname"];
1790 return(array("nick" => $nick, "id" => $id));
1794 function get_item_contact($item,$contacts) {
1795 if(! count($contacts) || (! is_array($item)))
1797 foreach($contacts as $contact) {
1798 if($contact['id'] == $item['contact-id']) {
1800 break; // NOTREACHED
1807 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1809 * @param int $item_id
1810 * @return bool true if item was deleted, else false
1812 function tag_deliver($uid,$item_id) {
1820 $u = q("select * from user where uid = %d limit 1",
1826 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1827 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1830 $i = q("select * from item where id = %d and uid = %d limit 1",
1839 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1841 // Diaspora uses their own hardwired link URL in @-tags
1842 // instead of the one we supply with webfinger
1844 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1846 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1848 foreach($matches as $mtch) {
1849 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1851 logger('tag_deliver: mention found: ' . $mtch[2]);
1857 if ( ($community_page || $prvgroup) &&
1858 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1859 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1861 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1862 q("DELETE FROM item WHERE id = %d and uid = %d",
1872 // send a notification
1874 // use a local photo if we have one
1876 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1877 intval($u[0]['uid']),
1878 dbesc(normalise_link($item['author-link']))
1880 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1883 require_once('include/enotify.php');
1885 'type' => NOTIFY_TAGSELF,
1886 'notify_flags' => $u[0]['notify-flags'],
1887 'language' => $u[0]['language'],
1888 'to_name' => $u[0]['username'],
1889 'to_email' => $u[0]['email'],
1890 'uid' => $u[0]['uid'],
1892 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1893 'source_name' => $item['author-name'],
1894 'source_link' => $item['author-link'],
1895 'source_photo' => $photo,
1896 'verb' => ACTIVITY_TAG,
1898 'parent' => $item['parent']
1902 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1904 call_hooks('tagged', $arr);
1906 if((! $community_page) && (! $prvgroup))
1910 // tgroup delivery - setup a second delivery chain
1911 // prevent delivery looping - only proceed
1912 // if the message originated elsewhere and is a top-level post
1914 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1917 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1920 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1921 intval($u[0]['uid'])
1926 // also reset all the privacy bits to the forum default permissions
1928 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1930 $forum_mode = (($prvgroup) ? 2 : 1);
1932 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1933 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1934 intval($forum_mode),
1935 dbesc($c[0]['name']),
1936 dbesc($c[0]['url']),
1937 dbesc($c[0]['thumb']),
1939 dbesc($u[0]['allow_cid']),
1940 dbesc($u[0]['allow_gid']),
1941 dbesc($u[0]['deny_cid']),
1942 dbesc($u[0]['deny_gid']),
1945 update_thread($item_id);
1947 proc_run('php','include/notifier.php','tgroup',$item_id);
1953 function tgroup_check($uid,$item) {
1959 // check that the message originated elsewhere and is a top-level post
1961 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1965 $u = q("select * from user where uid = %d limit 1",
1971 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1972 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1975 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1977 // Diaspora uses their own hardwired link URL in @-tags
1978 // instead of the one we supply with webfinger
1980 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1982 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1984 foreach($matches as $mtch) {
1985 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1987 logger('tgroup_check: mention found: ' . $mtch[2]);
1995 if((! $community_page) && (! $prvgroup))
2009 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
2013 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
2015 if($contact['duplex'] && $contact['dfrn-id'])
2016 $idtosend = '0:' . $orig_id;
2017 if($contact['duplex'] && $contact['issued-id'])
2018 $idtosend = '1:' . $orig_id;
2021 $rino = get_config('system','rino_encrypt');
2022 $rino = intval($rino);
2023 // use RINO1 if mcrypt isn't installed and RINO2 was selected
2024 if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
2026 logger("Local rino version: ". $rino, LOGGER_DEBUG);
2028 $ssl_val = intval(get_config('system','ssl_policy'));
2032 case SSL_POLICY_FULL:
2033 $ssl_policy = 'full';
2035 case SSL_POLICY_SELFSIGN:
2036 $ssl_policy = 'self';
2038 case SSL_POLICY_NONE:
2040 $ssl_policy = 'none';
2044 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
2046 logger('dfrn_deliver: ' . $url);
2048 $xml = fetch_url($url);
2050 $curl_stat = $a->get_curl_code();
2052 return(-1); // timed out
2054 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2059 if(strpos($xml,'<?xml') === false) {
2060 logger('dfrn_deliver: no valid XML returned');
2061 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2065 $res = parse_xml_string($xml);
2067 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2068 return (($res->status) ? $res->status : 3);
2070 $postvars = array();
2071 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2072 $challenge = hex2bin((string) $res->challenge);
2073 $perm = (($res->perm) ? $res->perm : null);
2074 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2075 $rino_remote_version = intval($res->rino);
2076 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2078 logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG);
2080 if($owner['page-flags'] == PAGE_PRVGROUP)
2083 $final_dfrn_id = '';
2086 if((($perm == 'rw') && (! intval($contact['writable'])))
2087 || (($perm == 'r') && (intval($contact['writable'])))) {
2088 q("update contact set writable = %d where id = %d",
2089 intval(($perm == 'rw') ? 1 : 0),
2090 intval($contact['id'])
2092 $contact['writable'] = (string) 1 - intval($contact['writable']);
2096 if(($contact['duplex'] && strlen($contact['pubkey']))
2097 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2098 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2099 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2100 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2103 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2104 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2107 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2109 if(strpos($final_dfrn_id,':') == 1)
2110 $final_dfrn_id = substr($final_dfrn_id,2);
2112 if($final_dfrn_id != $orig_id) {
2113 logger('dfrn_deliver: wrong dfrn_id.');
2114 // did not decode properly - cannot trust this site
2118 $postvars['dfrn_id'] = $idtosend;
2119 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2121 $postvars['dissolve'] = '1';
2124 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2125 $postvars['data'] = $atom;
2126 $postvars['perm'] = 'rw';
2129 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2130 $postvars['perm'] = 'r';
2133 $postvars['ssl_policy'] = $ssl_policy;
2136 $postvars['page'] = $page;
2139 if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
2140 logger('rino version: '. $rino_remote_version);
2142 switch($rino_remote_version) {
2144 // Deprecated rino version!
2145 $key = substr(random_string(),0,16);
2146 $data = aes_encrypt($postvars['data'],$key);
2149 // RINO 2 based on php-encryption
2151 $key = Crypto::createNewRandomKey();
2152 } catch (CryptoTestFailed $ex) {
2153 logger('Cannot safely create a key');
2155 } catch (CannotPerformOperation $ex) {
2156 logger('Cannot safely create a key');
2160 $data = Crypto::encrypt($postvars['data'], $key);
2161 } catch (CryptoTestFailed $ex) {
2162 logger('Cannot safely perform encryption');
2164 } catch (CannotPerformOperation $ex) {
2165 logger('Cannot safely perform encryption');
2170 logger("rino: invalid requested verision '$rino_remote_version'");
2174 $postvars['rino'] = $rino_remote_version;
2175 $postvars['data'] = bin2hex($data);
2177 #logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2180 if($dfrn_version >= 2.1) {
2181 if(($contact['duplex'] && strlen($contact['pubkey']))
2182 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2183 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2185 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2188 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2192 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2193 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2196 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2200 logger('md5 rawkey ' . md5($postvars['key']));
2202 $postvars['key'] = bin2hex($postvars['key']);
2206 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2208 $xml = post_url($contact['notify'],$postvars);
2210 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2212 $curl_stat = $a->get_curl_code();
2213 if((! $curl_stat) || (! strlen($xml)))
2214 return(-1); // timed out
2216 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2219 if(strpos($xml,'<?xml') === false) {
2220 logger('dfrn_deliver: phase 2: no valid XML returned');
2221 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2225 if($contact['term-date'] != '0000-00-00 00:00:00') {
2226 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2227 require_once('include/Contact.php');
2228 unmark_for_death($contact);
2231 $res = parse_xml_string($xml);
2233 return $res->status;
2238 This function returns true if $update has an edited timestamp newer
2239 than $existing, i.e. $update contains new data which should override
2240 what's already there. If there is no timestamp yet, the update is
2241 assumed to be newer. If the update has no timestamp, the existing
2242 item is assumed to be up-to-date. If the timestamps are equal it
2243 assumes the update has been seen before and should be ignored.
2245 function edited_timestamp_is_newer($existing, $update) {
2246 if (!x($existing,'edited') || !$existing['edited']) {
2249 if (!x($update,'edited') || !$update['edited']) {
2252 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2253 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2254 return (strcmp($existing_edited, $update_edited) < 0);
2259 * consume_feed - process atom feed and update anything/everything we might need to update
2261 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2263 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2264 * It is this person's stuff that is going to be updated.
2265 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2266 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2267 * have a contact record.
2268 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2269 * might not) try and subscribe to it.
2270 * $datedir sorts in reverse order
2271 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2272 * imported prior to its children being seen in the stream unless we are certain
2273 * of how the feed is arranged/ordered.
2274 * With $pass = 1, we only pull parent items out of the stream.
2275 * With $pass = 2, we only pull children (comments/likes).
2277 * So running this twice, first with pass 1 and then with pass 2 will do the right
2278 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2279 * model where comments can have sub-threads. That would require some massive sorting
2280 * to get all the feed items into a mostly linear ordering, and might still require
2284 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2285 if ($contact['network'] === NETWORK_OSTATUS) {
2287 // Test - remove before flight
2288 //$tempfile = tempnam(get_temppath(), "ostatus");
2289 //file_put_contents($tempfile, $xml);
2291 logger("Consume OStatus messages ", LOGGER_DEBUG);
2292 ostatus_import($xml,$importer,$contact, $hub);
2297 require_once('library/simplepie/simplepie.inc');
2298 require_once('include/contact_selectors.php');
2300 if(! strlen($xml)) {
2301 logger('consume_feed: empty input');
2305 $feed = new SimplePie();
2306 $feed->set_raw_data($xml);
2308 $feed->enable_order_by_date(true);
2310 $feed->enable_order_by_date(false);
2314 logger('consume_feed: Error parsing XML: ' . $feed->error());
2316 $permalink = $feed->get_permalink();
2318 // Check at the feed level for updated contact name and/or photo
2322 $photo_timestamp = '';
2325 $contact_updated = '';
2327 $hubs = $feed->get_links('hub');
2328 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2331 $hub = implode(',', $hubs);
2333 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2335 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2337 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2338 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2339 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2340 $new_name = $elems['name'][0]['data'];
2342 // Manually checking for changed contact names
2343 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2344 $name_updated = date("c");
2345 $photo_timestamp = date("c");
2348 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2349 if ($photo_timestamp == "")
2350 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2351 $photo_url = $elems['link'][0]['attribs']['']['href'];
2354 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2355 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2359 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2360 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2362 $contact_updated = $photo_timestamp;
2364 require_once("include/Photo.php");
2365 $photo_failure = false;
2366 $have_photo = false;
2368 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2369 intval($contact['id']),
2370 intval($contact['uid'])
2373 $resource_id = $r[0]['resource-id'];
2377 $resource_id = photo_new_resource();
2380 $img_str = fetch_url($photo_url,true);
2381 // guess mimetype from headers or filename
2382 $type = guess_image_type($photo_url,true);
2385 $img = new Photo($img_str, $type);
2386 if($img->is_valid()) {
2388 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2389 dbesc($resource_id),
2390 intval($contact['id']),
2391 intval($contact['uid'])
2395 $img->scaleImageSquare(175);
2397 $hash = $resource_id;
2398 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2400 $img->scaleImage(80);
2401 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2403 $img->scaleImage(48);
2404 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2408 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2409 WHERE `uid` = %d AND `id` = %d",
2410 dbesc(datetime_convert()),
2411 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2412 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2413 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2414 intval($contact['uid']),
2415 intval($contact['id'])
2420 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2421 if ($name_updated > $contact_updated)
2422 $contact_updated = $name_updated;
2424 $r = q("select * from contact where uid = %d and id = %d limit 1",
2425 intval($contact['uid']),
2426 intval($contact['id'])
2429 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2430 dbesc(notags(trim($new_name))),
2431 dbesc(datetime_convert()),
2432 intval($contact['uid']),
2433 intval($contact['id'])
2436 // do our best to update the name on content items
2439 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2440 dbesc(notags(trim($new_name))),
2441 dbesc($r[0]['name']),
2442 dbesc($r[0]['url']),
2443 intval($contact['uid'])
2448 if ($contact_updated AND $new_name AND $photo_url)
2449 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2451 if(strlen($birthday)) {
2452 if(substr($birthday,0,4) != $contact['bdyear']) {
2453 logger('consume_feed: updating birthday: ' . $birthday);
2457 * Add new birthday event for this person
2459 * $bdtext is just a readable placeholder in case the event is shared
2460 * with others. We will replace it during presentation to our $importer
2461 * to contain a sparkle link and perhaps a photo.
2465 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2466 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2469 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2470 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2471 intval($contact['uid']),
2472 intval($contact['id']),
2473 dbesc(datetime_convert()),
2474 dbesc(datetime_convert()),
2475 dbesc(datetime_convert('UTC','UTC', $birthday)),
2476 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2485 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2486 dbesc(substr($birthday,0,4)),
2487 intval($contact['uid']),
2488 intval($contact['id'])
2491 // This function is called twice without reloading the contact
2492 // Make sure we only create one event. This is why &$contact
2493 // is a reference var in this function
2495 $contact['bdyear'] = substr($birthday,0,4);
2499 $community_page = 0;
2500 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2502 $community_page = intval($rawtags[0]['data']);
2504 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2505 q("update contact set forum = %d where id = %d",
2506 intval($community_page),
2507 intval($contact['id'])
2509 $contact['forum'] = (string) $community_page;
2513 // process any deleted entries
2515 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2516 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2517 foreach($del_entries as $dentry) {
2519 if(isset($dentry['attribs']['']['ref'])) {
2520 $uri = $dentry['attribs']['']['ref'];
2522 if(isset($dentry['attribs']['']['when'])) {
2523 $when = $dentry['attribs']['']['when'];
2524 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2527 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2529 if($deleted && is_array($contact)) {
2530 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2531 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2533 intval($importer['uid']),
2534 intval($contact['id'])
2539 if(! $item['deleted'])
2540 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2542 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2543 $xo = parse_xml_string($item['object'],false);
2544 $xt = parse_xml_string($item['target'],false);
2545 if($xt->type === ACTIVITY_OBJ_NOTE) {
2546 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2548 intval($importer['importer_uid'])
2552 // For tags, the owner cannot remove the tag on the author's copy of the post.
2554 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2555 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2556 $author_copy = (($item['origin']) ? true : false);
2558 if($owner_remove && $author_copy)
2560 if($author_remove || $owner_remove) {
2561 $tags = explode(',',$i[0]['tag']);
2564 foreach($tags as $tag)
2565 if(trim($tag) !== trim($xo->body))
2566 $newtags[] = trim($tag);
2568 q("update item set tag = '%s' where id = %d",
2569 dbesc(implode(',',$newtags)),
2572 create_tags_from_item($i[0]['id']);
2578 if($item['uri'] == $item['parent-uri']) {
2579 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2580 `body` = '', `title` = ''
2581 WHERE `parent-uri` = '%s' AND `uid` = %d",
2583 dbesc(datetime_convert()),
2584 dbesc($item['uri']),
2585 intval($importer['uid'])
2587 create_tags_from_itemuri($item['uri'], $importer['uid']);
2588 create_files_from_itemuri($item['uri'], $importer['uid']);
2589 update_thread_uri($item['uri'], $importer['uid']);
2592 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2593 `body` = '', `title` = ''
2594 WHERE `uri` = '%s' AND `uid` = %d",
2596 dbesc(datetime_convert()),
2598 intval($importer['uid'])
2600 create_tags_from_itemuri($uri, $importer['uid']);
2601 create_files_from_itemuri($uri, $importer['uid']);
2602 if($item['last-child']) {
2603 // ensure that last-child is set in case the comment that had it just got wiped.
2604 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2605 dbesc(datetime_convert()),
2606 dbesc($item['parent-uri']),
2607 intval($item['uid'])
2609 // who is the last child now?
2610 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2611 ORDER BY `created` DESC LIMIT 1",
2612 dbesc($item['parent-uri']),
2613 intval($importer['uid'])
2616 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2627 // Now process the feed
2629 if($feed->get_item_quantity()) {
2631 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2633 // in inverse date order
2635 $items = array_reverse($feed->get_items());
2637 $items = $feed->get_items();
2640 foreach($items as $item) {
2643 $item_id = $item->get_id();
2644 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2645 if(isset($rawthread[0]['attribs']['']['ref'])) {
2647 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2650 if(($is_reply) && is_array($contact)) {
2655 // not allowed to post
2657 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2661 // Have we seen it? If not, import it.
2663 $item_id = $item->get_id();
2664 $datarray = get_atom_elements($feed, $item, $contact);
2666 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2667 $datarray['author-name'] = $contact['name'];
2668 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2669 $datarray['author-link'] = $contact['url'];
2670 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2671 $datarray['author-avatar'] = $contact['thumb'];
2673 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2674 logger('consume_feed: no author information! ' . print_r($datarray,true));
2678 $force_parent = false;
2679 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2680 if($contact['network'] === NETWORK_OSTATUS)
2681 $force_parent = true;
2682 if(strlen($datarray['title']))
2683 unset($datarray['title']);
2684 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2685 dbesc(datetime_convert()),
2687 intval($importer['uid'])
2689 $datarray['last-child'] = 1;
2690 update_thread_uri($parent_uri, $importer['uid']);
2694 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2696 intval($importer['uid'])
2699 // Update content if 'updated' changes
2702 if (edited_timestamp_is_newer($r[0], $datarray)) {
2704 // do not accept (ignore) an earlier edit than one we currently have.
2705 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2708 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2709 dbesc($datarray['title']),
2710 dbesc($datarray['body']),
2711 dbesc($datarray['tag']),
2712 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2713 dbesc(datetime_convert()),
2715 intval($importer['uid'])
2717 create_tags_from_itemuri($item_id, $importer['uid']);
2718 update_thread_uri($item_id, $importer['uid']);
2721 // update last-child if it changes
2723 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2724 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2725 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2726 dbesc(datetime_convert()),
2728 intval($importer['uid'])
2730 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2731 intval($allow[0]['data']),
2732 dbesc(datetime_convert()),
2734 intval($importer['uid'])
2736 update_thread_uri($item_id, $importer['uid']);
2742 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2743 // one way feed - no remote comment ability
2744 $datarray['last-child'] = 0;
2746 $datarray['parent-uri'] = $parent_uri;
2747 $datarray['uid'] = $importer['uid'];
2748 $datarray['contact-id'] = $contact['id'];
2749 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2750 $datarray['type'] = 'activity';
2751 $datarray['gravity'] = GRAVITY_LIKE;
2752 // only one like or dislike per person
2753 // splitted into two queries for performance issues
2754 $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",
2755 intval($datarray['uid']),
2756 intval($datarray['contact-id']),
2757 dbesc($datarray['verb']),
2763 $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",
2764 intval($datarray['uid']),
2765 intval($datarray['contact-id']),
2766 dbesc($datarray['verb']),
2773 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2774 $xo = parse_xml_string($datarray['object'],false);
2775 $xt = parse_xml_string($datarray['target'],false);
2777 if($xt->type == ACTIVITY_OBJ_NOTE) {
2778 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2780 intval($importer['importer_uid'])
2785 // extract tag, if not duplicate, add to parent item
2786 if($xo->id && $xo->content) {
2787 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2788 if(! (stristr($r[0]['tag'],$newtag))) {
2789 q("UPDATE item SET tag = '%s' WHERE id = %d",
2790 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2793 create_tags_from_item($r[0]['id']);
2799 $r = item_store($datarray,$force_parent);
2805 // Head post of a conversation. Have we seen it? If not, import it.
2807 $item_id = $item->get_id();
2809 $datarray = get_atom_elements($feed, $item, $contact);
2811 if(is_array($contact)) {
2812 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2813 $datarray['author-name'] = $contact['name'];
2814 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2815 $datarray['author-link'] = $contact['url'];
2816 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2817 $datarray['author-avatar'] = $contact['thumb'];
2820 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2821 logger('consume_feed: no author information! ' . print_r($datarray,true));
2825 // special handling for events
2827 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2828 $ev = bbtoevent($datarray['body']);
2829 if(x($ev,'desc') && x($ev,'start')) {
2830 $ev['uid'] = $importer['uid'];
2831 $ev['uri'] = $item_id;
2832 $ev['edited'] = $datarray['edited'];
2833 $ev['private'] = $datarray['private'];
2835 if(is_array($contact))
2836 $ev['cid'] = $contact['id'];
2837 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2839 intval($importer['uid'])
2842 $ev['id'] = $r[0]['id'];
2843 $xyz = event_store($ev);
2848 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2849 if(strlen($datarray['title']))
2850 unset($datarray['title']);
2851 $datarray['last-child'] = 1;
2855 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2857 intval($importer['uid'])
2860 // Update content if 'updated' changes
2863 if (edited_timestamp_is_newer($r[0], $datarray)) {
2865 // do not accept (ignore) an earlier edit than one we currently have.
2866 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2869 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2870 dbesc($datarray['title']),
2871 dbesc($datarray['body']),
2872 dbesc($datarray['tag']),
2873 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2874 dbesc(datetime_convert()),
2876 intval($importer['uid'])
2878 create_tags_from_itemuri($item_id, $importer['uid']);
2879 update_thread_uri($item_id, $importer['uid']);
2882 // update last-child if it changes
2884 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2885 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2886 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2887 intval($allow[0]['data']),
2888 dbesc(datetime_convert()),
2890 intval($importer['uid'])
2892 update_thread_uri($item_id, $importer['uid']);
2897 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2898 logger('consume-feed: New follower');
2899 new_follower($importer,$contact,$datarray,$item);
2902 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2903 lose_follower($importer,$contact,$datarray,$item);
2907 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2908 logger('consume-feed: New friend request');
2909 new_follower($importer,$contact,$datarray,$item,true);
2912 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2913 lose_sharer($importer,$contact,$datarray,$item);
2918 if(! is_array($contact))
2922 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2923 // one way feed - no remote comment ability
2924 $datarray['last-child'] = 0;
2926 if($contact['network'] === NETWORK_FEED)
2927 $datarray['private'] = 2;
2929 $datarray['parent-uri'] = $item_id;
2930 $datarray['uid'] = $importer['uid'];
2931 $datarray['contact-id'] = $contact['id'];
2933 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2934 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2935 // but otherwise there's a possible data mixup on the sender's system.
2936 // the tgroup delivery code called from item_store will correct it if it's a forum,
2937 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2938 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2939 $datarray['owner-name'] = $contact['name'];
2940 $datarray['owner-link'] = $contact['url'];
2941 $datarray['owner-avatar'] = $contact['thumb'];
2944 // We've allowed "followers" to reach this point so we can decide if they are
2945 // posting an @-tag delivery, which followers are allowed to do for certain
2946 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2948 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2951 // This is my contact on another system, but it's really me.
2952 // Turn this into a wall post.
2953 $notify = item_is_remote_self($contact, $datarray);
2955 $r = item_store($datarray, false, $notify);
2956 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2964 function item_is_remote_self($contact, &$datarray) {
2967 if (!$contact['remote_self'])
2970 // Prevent the forwarding of posts that are forwarded
2971 if ($datarray["extid"] == NETWORK_DFRN)
2974 // Prevent to forward already forwarded posts
2975 if ($datarray["app"] == $a->get_hostname())
2978 // Only forward posts
2979 if ($datarray["verb"] != ACTIVITY_POST)
2982 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2985 $datarray2 = $datarray;
2986 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2987 if ($contact['remote_self'] == 2) {
2988 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2989 intval($contact['uid']));
2991 $datarray['contact-id'] = $r[0]["id"];
2993 $datarray['owner-name'] = $r[0]["name"];
2994 $datarray['owner-link'] = $r[0]["url"];
2995 $datarray['owner-avatar'] = $r[0]["thumb"];
2997 $datarray['author-name'] = $datarray['owner-name'];
2998 $datarray['author-link'] = $datarray['owner-link'];
2999 $datarray['author-avatar'] = $datarray['owner-avatar'];
3002 if ($contact['network'] != NETWORK_FEED) {
3003 $datarray["guid"] = get_guid(32);
3004 unset($datarray["plink"]);
3005 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
3006 $datarray["parent-uri"] = $datarray["uri"];
3007 $datarray["extid"] = $contact['network'];
3008 $urlpart = parse_url($datarray2['author-link']);
3009 $datarray["app"] = $urlpart["host"];
3011 $datarray['private'] = 0;
3014 if ($contact['network'] != NETWORK_FEED) {
3015 // Store the original post
3016 $r = item_store($datarray2, false, false);
3017 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
3019 $datarray["app"] = "Feed";
3024 function local_delivery($importer,$data) {
3027 logger(__function__, LOGGER_TRACE);
3029 if($importer['readonly']) {
3030 // We aren't receiving stuff from this person. But we will quietly ignore them
3031 // rather than a blatant "go away" message.
3032 logger('local_delivery: ignoring');
3037 // Consume notification feed. This may differ from consuming a public feed in several ways
3038 // - might contain email or friend suggestions
3039 // - might contain remote followup to our message
3040 // - in which case we need to accept it and then notify other conversants
3041 // - we may need to send various email notifications
3043 $feed = new SimplePie();
3044 $feed->set_raw_data($data);
3045 $feed->enable_order_by_date(false);
3050 logger('local_delivery: Error parsing XML: ' . $feed->error());
3053 // Check at the feed level for updated contact name and/or photo
3057 $photo_timestamp = '';
3059 $contact_updated = '';
3062 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
3064 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
3066 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
3069 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
3070 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
3071 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
3072 $new_name = $elems['name'][0]['data'];
3074 // Manually checking for changed contact names
3075 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
3076 $name_updated = date("c");
3077 $photo_timestamp = date("c");
3080 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
3081 if ($photo_timestamp == "")
3082 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
3083 $photo_url = $elems['link'][0]['attribs']['']['href'];
3087 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3089 $contact_updated = $photo_timestamp;
3091 logger('local_delivery: Updating photo for ' . $importer['name']);
3092 require_once("include/Photo.php");
3093 $photo_failure = false;
3094 $have_photo = false;
3096 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
3097 intval($importer['id']),
3098 intval($importer['importer_uid'])
3101 $resource_id = $r[0]['resource-id'];
3105 $resource_id = photo_new_resource();
3108 $img_str = fetch_url($photo_url,true);
3109 // guess mimetype from headers or filename
3110 $type = guess_image_type($photo_url,true);
3113 $img = new Photo($img_str, $type);
3114 if($img->is_valid()) {
3116 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
3117 dbesc($resource_id),
3118 intval($importer['id']),
3119 intval($importer['importer_uid'])
3123 $img->scaleImageSquare(175);
3125 $hash = $resource_id;
3126 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
3128 $img->scaleImage(80);
3129 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
3131 $img->scaleImage(48);
3132 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3136 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3137 WHERE `uid` = %d AND `id` = %d",
3138 dbesc(datetime_convert()),
3139 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3140 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3141 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3142 intval($importer['importer_uid']),
3143 intval($importer['id'])
3148 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3149 if ($name_updated > $contact_updated)
3150 $contact_updated = $name_updated;
3152 $r = q("select * from contact where uid = %d and id = %d limit 1",
3153 intval($importer['importer_uid']),
3154 intval($importer['id'])
3157 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3158 dbesc(notags(trim($new_name))),
3159 dbesc(datetime_convert()),
3160 intval($importer['importer_uid']),
3161 intval($importer['id'])
3164 // do our best to update the name on content items
3167 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3168 dbesc(notags(trim($new_name))),
3169 dbesc($r[0]['name']),
3170 dbesc($r[0]['url']),
3171 intval($importer['importer_uid'])
3176 if ($contact_updated AND $new_name AND $photo_url)
3177 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3179 // Currently unsupported - needs a lot of work
3180 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3181 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3182 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3184 $newloc['uid'] = $importer['importer_uid'];
3185 $newloc['cid'] = $importer['id'];
3186 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3187 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3188 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3189 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3190 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3191 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3192 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3193 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3194 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3195 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3196 /** relocated user must have original key pair */
3197 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3198 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3200 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3203 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3204 intval($importer['id']),
3205 intval($importer['importer_uid']));
3210 $x = q("UPDATE contact SET
3221 `site-pubkey` = '%s'
3222 WHERE id=%d AND uid=%d;",
3223 dbesc($newloc['name']),
3224 dbesc($newloc['photo']),
3225 dbesc($newloc['thumb']),
3226 dbesc($newloc['micro']),
3227 dbesc($newloc['url']),
3228 dbesc(normalise_link($newloc['url'])),
3229 dbesc($newloc['request']),
3230 dbesc($newloc['confirm']),
3231 dbesc($newloc['notify']),
3232 dbesc($newloc['poll']),
3233 dbesc($newloc['sitepubkey']),
3234 intval($importer['id']),
3235 intval($importer['importer_uid']));
3241 'owner-link' => array($old['url'], $newloc['url']),
3242 'author-link' => array($old['url'], $newloc['url']),
3243 'owner-avatar' => array($old['photo'], $newloc['photo']),
3244 'author-avatar' => array($old['photo'], $newloc['photo']),
3246 foreach ($fields as $n=>$f){
3247 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3250 intval($importer['importer_uid']));
3256 // merge with current record, current contents have priority
3257 // update record, set url-updated
3258 // update profile photos
3264 // handle friend suggestion notification
3266 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3267 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3268 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3270 $fsugg['uid'] = $importer['importer_uid'];
3271 $fsugg['cid'] = $importer['id'];
3272 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3273 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3274 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3275 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3276 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3278 // Does our member already have a friend matching this description?
3280 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3281 dbesc($fsugg['name']),
3282 dbesc(normalise_link($fsugg['url'])),
3283 intval($fsugg['uid'])
3288 // Do we already have an fcontact record for this person?
3291 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3292 dbesc($fsugg['url']),
3293 dbesc($fsugg['name']),
3294 dbesc($fsugg['request'])
3299 // OK, we do. Do we already have an introduction for this person ?
3300 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3301 intval($fsugg['uid']),
3308 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3309 dbesc($fsugg['name']),
3310 dbesc($fsugg['url']),
3311 dbesc($fsugg['photo']),
3312 dbesc($fsugg['request'])
3314 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3315 dbesc($fsugg['url']),
3316 dbesc($fsugg['name']),
3317 dbesc($fsugg['request'])
3322 // database record did not get created. Quietly give up.
3327 $hash = random_string();
3329 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3330 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3331 intval($fsugg['uid']),
3333 intval($fsugg['cid']),
3334 dbesc($fsugg['body']),
3336 dbesc(datetime_convert()),
3341 'type' => NOTIFY_SUGGEST,
3342 'notify_flags' => $importer['notify-flags'],
3343 'language' => $importer['language'],
3344 'to_name' => $importer['username'],
3345 'to_email' => $importer['email'],
3346 'uid' => $importer['importer_uid'],
3348 'link' => $a->get_baseurl() . '/notifications/intros',
3349 'source_name' => $importer['name'],
3350 'source_link' => $importer['url'],
3351 'source_photo' => $importer['photo'],
3352 'verb' => ACTIVITY_REQ_FRIEND,
3361 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3362 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3364 logger('local_delivery: private message received');
3367 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3370 $msg['uid'] = $importer['importer_uid'];
3371 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3372 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3373 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3374 $msg['contact-id'] = $importer['id'];
3375 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3376 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3378 $msg['replied'] = 0;
3379 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3380 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3381 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3385 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3386 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3388 // send notifications.
3390 require_once('include/enotify.php');
3392 $notif_params = array(
3393 'type' => NOTIFY_MAIL,
3394 'notify_flags' => $importer['notify-flags'],
3395 'language' => $importer['language'],
3396 'to_name' => $importer['username'],
3397 'to_email' => $importer['email'],
3398 'uid' => $importer['importer_uid'],
3400 'source_name' => $msg['from-name'],
3401 'source_link' => $importer['url'],
3402 'source_photo' => $importer['thumb'],
3403 'verb' => ACTIVITY_POST,
3407 notification($notif_params);
3413 $community_page = 0;
3414 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3416 $community_page = intval($rawtags[0]['data']);
3418 if(intval($importer['forum']) != $community_page) {
3419 q("update contact set forum = %d where id = %d",
3420 intval($community_page),
3421 intval($importer['id'])
3423 $importer['forum'] = (string) $community_page;
3426 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3428 // process any deleted entries
3430 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3431 if(is_array($del_entries) && count($del_entries)) {
3432 foreach($del_entries as $dentry) {
3434 if(isset($dentry['attribs']['']['ref'])) {
3435 $uri = $dentry['attribs']['']['ref'];
3437 if(isset($dentry['attribs']['']['when'])) {
3438 $when = $dentry['attribs']['']['when'];
3439 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3442 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3446 // check for relayed deletes to our conversation
3449 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3451 intval($importer['importer_uid'])
3454 $parent_uri = $r[0]['parent-uri'];
3455 if($r[0]['id'] != $r[0]['parent'])
3462 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3465 logger('local_delivery: possible community delete');
3468 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3470 // was the top-level post for this reply written by somebody on this site?
3471 // Specifically, the recipient?
3473 $is_a_remote_delete = false;
3475 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3476 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3477 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3478 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3479 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3480 AND `item`.`uid` = %d
3486 intval($importer['importer_uid'])
3489 $is_a_remote_delete = true;
3491 // Does this have the characteristics of a community or private group comment?
3492 // If it's a reply to a wall post on a community/prvgroup page it's a
3493 // valid community comment. Also forum_mode makes it valid for sure.
3494 // If neither, it's not.
3496 if($is_a_remote_delete && $community) {
3497 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3498 $is_a_remote_delete = false;
3499 logger('local_delivery: not a community delete');
3503 if($is_a_remote_delete) {
3504 logger('local_delivery: received remote delete');
3508 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3509 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3511 intval($importer['importer_uid']),
3512 intval($importer['id'])
3518 if($item['deleted'])
3521 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3523 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3524 $xo = parse_xml_string($item['object'],false);
3525 $xt = parse_xml_string($item['target'],false);
3527 if($xt->type === ACTIVITY_OBJ_NOTE) {
3528 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3530 intval($importer['importer_uid'])
3534 // For tags, the owner cannot remove the tag on the author's copy of the post.
3536 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3537 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3538 $author_copy = (($item['origin']) ? true : false);
3540 if($owner_remove && $author_copy)
3542 if($author_remove || $owner_remove) {
3543 $tags = explode(',',$i[0]['tag']);
3546 foreach($tags as $tag)
3547 if(trim($tag) !== trim($xo->body))
3548 $newtags[] = trim($tag);
3550 q("update item set tag = '%s' where id = %d",
3551 dbesc(implode(',',$newtags)),
3554 create_tags_from_item($i[0]['id']);
3560 if($item['uri'] == $item['parent-uri']) {
3561 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3562 `body` = '', `title` = ''
3563 WHERE `parent-uri` = '%s' AND `uid` = %d",
3565 dbesc(datetime_convert()),
3566 dbesc($item['uri']),
3567 intval($importer['importer_uid'])
3569 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3570 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3571 update_thread_uri($item['uri'], $importer['importer_uid']);
3574 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3575 `body` = '', `title` = ''
3576 WHERE `uri` = '%s' AND `uid` = %d",
3578 dbesc(datetime_convert()),
3580 intval($importer['importer_uid'])
3582 create_tags_from_itemuri($uri, $importer['importer_uid']);
3583 create_files_from_itemuri($uri, $importer['importer_uid']);
3584 update_thread_uri($uri, $importer['importer_uid']);
3585 if($item['last-child']) {
3586 // ensure that last-child is set in case the comment that had it just got wiped.
3587 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3588 dbesc(datetime_convert()),
3589 dbesc($item['parent-uri']),
3590 intval($item['uid'])
3592 // who is the last child now?
3593 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3594 ORDER BY `created` DESC LIMIT 1",
3595 dbesc($item['parent-uri']),
3596 intval($importer['importer_uid'])
3599 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3604 // if this is a relayed delete, propagate it to other recipients
3606 if($is_a_remote_delete)
3607 proc_run('php',"include/notifier.php","drop",$item['id']);
3615 foreach($feed->get_items() as $item) {
3618 $item_id = $item->get_id();
3619 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3620 if(isset($rawthread[0]['attribs']['']['ref'])) {
3622 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3628 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3631 logger('local_delivery: possible community reply');
3634 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3636 // was the top-level post for this reply written by somebody on this site?
3637 // Specifically, the recipient?
3639 $is_a_remote_comment = false;
3640 $top_uri = $parent_uri;
3642 $r = q("select `item`.`parent-uri` from `item`
3643 WHERE `item`.`uri` = '%s'
3647 if($r && count($r)) {
3648 $top_uri = $r[0]['parent-uri'];
3650 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3651 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3652 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3653 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3654 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3655 AND `item`.`uid` = %d
3661 intval($importer['importer_uid'])
3664 $is_a_remote_comment = true;
3667 // Does this have the characteristics of a community or private group comment?
3668 // If it's a reply to a wall post on a community/prvgroup page it's a
3669 // valid community comment. Also forum_mode makes it valid for sure.
3670 // If neither, it's not.
3672 if($is_a_remote_comment && $community) {
3673 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3674 $is_a_remote_comment = false;
3675 logger('local_delivery: not a community reply');
3679 if($is_a_remote_comment) {
3680 logger('local_delivery: received remote comment');
3682 // remote reply to our post. Import and then notify everybody else.
3684 $datarray = get_atom_elements($feed, $item);
3686 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3688 intval($importer['importer_uid'])
3691 // Update content if 'updated' changes
3695 if (edited_timestamp_is_newer($r[0], $datarray)) {
3697 // do not accept (ignore) an earlier edit than one we currently have.
3698 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3701 logger('received updated comment' , LOGGER_DEBUG);
3702 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3703 dbesc($datarray['title']),
3704 dbesc($datarray['body']),
3705 dbesc($datarray['tag']),
3706 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3707 dbesc(datetime_convert()),
3709 intval($importer['importer_uid'])
3711 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3713 proc_run('php',"include/notifier.php","comment-import",$iid);
3722 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3723 intval($importer['importer_uid'])
3727 $datarray['type'] = 'remote-comment';
3728 $datarray['wall'] = 1;
3729 $datarray['parent-uri'] = $parent_uri;
3730 $datarray['uid'] = $importer['importer_uid'];
3731 $datarray['owner-name'] = $own[0]['name'];
3732 $datarray['owner-link'] = $own[0]['url'];
3733 $datarray['owner-avatar'] = $own[0]['thumb'];
3734 $datarray['contact-id'] = $importer['id'];
3736 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3738 $datarray['type'] = 'activity';
3739 $datarray['gravity'] = GRAVITY_LIKE;
3740 $datarray['last-child'] = 0;
3741 // only one like or dislike per person
3742 // splitted into two queries for performance issues
3743 $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",
3744 intval($datarray['uid']),
3745 intval($datarray['contact-id']),
3746 dbesc($datarray['verb']),
3747 dbesc($datarray['parent-uri'])
3753 $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",
3754 intval($datarray['uid']),
3755 intval($datarray['contact-id']),
3756 dbesc($datarray['verb']),
3757 dbesc($datarray['parent-uri'])
3764 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3766 $xo = parse_xml_string($datarray['object'],false);
3767 $xt = parse_xml_string($datarray['target'],false);
3769 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3771 // fetch the parent item
3773 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3775 intval($importer['importer_uid'])
3780 // extract tag, if not duplicate, and this user allows tags, add to parent item
3782 if($xo->id && $xo->content) {
3783 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3784 if(! (stristr($tagp[0]['tag'],$newtag))) {
3785 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3786 intval($importer['importer_uid'])
3788 if(count($i) && ! intval($i[0]['blocktags'])) {
3789 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3790 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3791 intval($tagp[0]['id']),
3792 dbesc(datetime_convert()),
3793 dbesc(datetime_convert())
3795 create_tags_from_item($tagp[0]['id']);
3803 $posted_id = item_store($datarray);
3808 $datarray["id"] = $posted_id;
3810 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3812 intval($importer['importer_uid'])
3815 $parent = $r[0]['parent'];
3816 $parent_uri = $r[0]['parent-uri'];
3820 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3821 dbesc(datetime_convert()),
3822 intval($importer['importer_uid']),
3823 intval($r[0]['parent'])
3826 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3827 dbesc(datetime_convert()),
3828 intval($importer['importer_uid']),
3833 if($posted_id && $parent) {
3835 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3837 if((! $is_like) && (! $importer['self'])) {
3839 require_once('include/enotify.php');
3842 'type' => NOTIFY_COMMENT,
3843 'notify_flags' => $importer['notify-flags'],
3844 'language' => $importer['language'],
3845 'to_name' => $importer['username'],
3846 'to_email' => $importer['email'],
3847 'uid' => $importer['importer_uid'],
3848 'item' => $datarray,
3849 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3850 'source_name' => stripslashes($datarray['author-name']),
3851 'source_link' => $datarray['author-link'],
3852 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3853 ? $importer['thumb'] : $datarray['author-avatar']),
3854 'verb' => ACTIVITY_POST,
3856 'parent' => $parent,
3857 'parent_uri' => $parent_uri,
3869 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3871 $item_id = $item->get_id();
3872 $datarray = get_atom_elements($feed,$item);
3874 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3877 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3879 intval($importer['importer_uid'])
3882 // Update content if 'updated' changes
3885 if (edited_timestamp_is_newer($r[0], $datarray)) {
3887 // do not accept (ignore) an earlier edit than one we currently have.
3888 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3891 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3892 dbesc($datarray['title']),
3893 dbesc($datarray['body']),
3894 dbesc($datarray['tag']),
3895 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3896 dbesc(datetime_convert()),
3898 intval($importer['importer_uid'])
3900 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3903 // update last-child if it changes
3905 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3906 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3907 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3908 dbesc(datetime_convert()),
3910 intval($importer['importer_uid'])
3912 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3913 intval($allow[0]['data']),
3914 dbesc(datetime_convert()),
3916 intval($importer['importer_uid'])
3922 $datarray['parent-uri'] = $parent_uri;
3923 $datarray['uid'] = $importer['importer_uid'];
3924 $datarray['contact-id'] = $importer['id'];
3925 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3926 $datarray['type'] = 'activity';
3927 $datarray['gravity'] = GRAVITY_LIKE;
3928 // only one like or dislike per person
3929 // splitted into two queries for performance issues
3930 $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",
3931 intval($datarray['uid']),
3932 intval($datarray['contact-id']),
3933 dbesc($datarray['verb']),
3939 $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",
3940 intval($datarray['uid']),
3941 intval($datarray['contact-id']),
3942 dbesc($datarray['verb']),
3950 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3952 $xo = parse_xml_string($datarray['object'],false);
3953 $xt = parse_xml_string($datarray['target'],false);
3955 if($xt->type == ACTIVITY_OBJ_NOTE) {
3956 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3958 intval($importer['importer_uid'])
3963 // extract tag, if not duplicate, add to parent item
3965 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3966 q("UPDATE item SET tag = '%s' WHERE id = %d",
3967 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3970 create_tags_from_item($r[0]['id']);
3976 $posted_id = item_store($datarray);
3978 // find out if our user is involved in this conversation and wants to be notified.
3980 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3982 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3984 intval($importer['importer_uid'])
3987 if(count($myconv)) {
3988 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3990 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3991 if(! link_compare($datarray['author-link'],$importer_url)) {
3994 foreach($myconv as $conv) {
3996 // now if we find a match, it means we're in this conversation
3998 if(! link_compare($conv['author-link'],$importer_url))
4001 require_once('include/enotify.php');
4003 $conv_parent = $conv['parent'];
4006 'type' => NOTIFY_COMMENT,
4007 'notify_flags' => $importer['notify-flags'],
4008 'language' => $importer['language'],
4009 'to_name' => $importer['username'],
4010 'to_email' => $importer['email'],
4011 'uid' => $importer['importer_uid'],
4012 'item' => $datarray,
4013 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4014 'source_name' => stripslashes($datarray['author-name']),
4015 'source_link' => $datarray['author-link'],
4016 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4017 ? $importer['thumb'] : $datarray['author-avatar']),
4018 'verb' => ACTIVITY_POST,
4020 'parent' => $conv_parent,
4021 'parent_uri' => $parent_uri
4025 // only send one notification
4037 // Head post of a conversation. Have we seen it? If not, import it.
4040 $item_id = $item->get_id();
4041 $datarray = get_atom_elements($feed,$item);
4043 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
4044 $ev = bbtoevent($datarray['body']);
4045 if(x($ev,'desc') && x($ev,'start')) {
4046 $ev['cid'] = $importer['id'];
4047 $ev['uid'] = $importer['uid'];
4048 $ev['uri'] = $item_id;
4049 $ev['edited'] = $datarray['edited'];
4050 $ev['private'] = $datarray['private'];
4052 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4054 intval($importer['uid'])
4057 $ev['id'] = $r[0]['id'];
4058 $xyz = event_store($ev);
4063 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4065 intval($importer['importer_uid'])
4068 // Update content if 'updated' changes
4071 if (edited_timestamp_is_newer($r[0], $datarray)) {
4073 // do not accept (ignore) an earlier edit than one we currently have.
4074 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4077 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4078 dbesc($datarray['title']),
4079 dbesc($datarray['body']),
4080 dbesc($datarray['tag']),
4081 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4082 dbesc(datetime_convert()),
4084 intval($importer['importer_uid'])
4086 create_tags_from_itemuri($item_id, $importer['importer_uid']);
4087 update_thread_uri($item_id, $importer['importer_uid']);
4090 // update last-child if it changes
4092 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4093 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4094 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4095 intval($allow[0]['data']),
4096 dbesc(datetime_convert()),
4098 intval($importer['importer_uid'])
4104 $datarray['parent-uri'] = $item_id;
4105 $datarray['uid'] = $importer['importer_uid'];
4106 $datarray['contact-id'] = $importer['id'];
4109 if(! link_compare($datarray['owner-link'],$importer['url'])) {
4110 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4111 // but otherwise there's a possible data mixup on the sender's system.
4112 // the tgroup delivery code called from item_store will correct it if it's a forum,
4113 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4114 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4115 $datarray['owner-name'] = $importer['senderName'];
4116 $datarray['owner-link'] = $importer['url'];
4117 $datarray['owner-avatar'] = $importer['thumb'];
4120 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4123 // This is my contact on another system, but it's really me.
4124 // Turn this into a wall post.
4125 $notify = item_is_remote_self($importer, $datarray);
4127 $posted_id = item_store($datarray, false, $notify);
4129 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4130 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4133 $xo = parse_xml_string($datarray['object'],false);
4135 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4137 // somebody was poked/prodded. Was it me?
4139 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4141 foreach($links->link as $l) {
4142 $atts = $l->attributes();
4143 switch($atts['rel']) {
4145 $Blink = $atts['href'];
4151 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4153 // send a notification
4154 require_once('include/enotify.php');
4157 'type' => NOTIFY_POKE,
4158 'notify_flags' => $importer['notify-flags'],
4159 'language' => $importer['language'],
4160 'to_name' => $importer['username'],
4161 'to_email' => $importer['email'],
4162 'uid' => $importer['importer_uid'],
4163 'item' => $datarray,
4164 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4165 'source_name' => stripslashes($datarray['author-name']),
4166 'source_link' => $datarray['author-link'],
4167 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4168 ? $importer['thumb'] : $datarray['author-avatar']),
4169 'verb' => $datarray['verb'],
4170 'otype' => 'person',
4171 'activity' => $verb,
4172 'parent' => $datarray['parent']
4188 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4189 $url = notags(trim($datarray['author-link']));
4190 $name = notags(trim($datarray['author-name']));
4191 $photo = notags(trim($datarray['author-avatar']));
4193 if (is_object($item)) {
4194 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4195 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4196 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4200 if(is_array($contact)) {
4201 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4202 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4203 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4204 intval(CONTACT_IS_FRIEND),
4205 intval($contact['id']),
4206 intval($importer['uid'])
4209 // send email notification to owner?
4213 // create contact record
4215 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4216 `blocked`, `readonly`, `pending`, `writable` )
4217 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4218 intval($importer['uid']),
4219 dbesc(datetime_convert()),
4221 dbesc(normalise_link($url)),
4225 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4226 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4228 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4229 intval($importer['uid']),
4233 $contact_record = $r[0];
4235 // create notification
4236 $hash = random_string();
4238 if(is_array($contact_record)) {
4239 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4240 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4241 intval($importer['uid']),
4242 intval($contact_record['id']),
4244 dbesc(datetime_convert())
4248 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4249 intval($importer['uid'])
4254 if(intval($r[0]['def_gid'])) {
4255 require_once('include/group.php');
4256 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4259 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4260 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4263 'type' => NOTIFY_INTRO,
4264 'notify_flags' => $r[0]['notify-flags'],
4265 'language' => $r[0]['language'],
4266 'to_name' => $r[0]['username'],
4267 'to_email' => $r[0]['email'],
4268 'uid' => $r[0]['uid'],
4269 'link' => $a->get_baseurl() . '/notifications/intro',
4270 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4271 'source_link' => $contact_record['url'],
4272 'source_photo' => $contact_record['photo'],
4273 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4282 function lose_follower($importer,$contact,$datarray,$item) {
4284 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4285 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4286 intval(CONTACT_IS_SHARING),
4287 intval($contact['id'])
4291 contact_remove($contact['id']);
4295 function lose_sharer($importer,$contact,$datarray,$item) {
4297 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4298 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4299 intval(CONTACT_IS_FOLLOWER),
4300 intval($contact['id'])
4304 contact_remove($contact['id']);
4309 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4313 if(is_array($importer)) {
4314 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4315 intval($importer['uid'])
4319 // Diaspora has different message-ids in feeds than they do
4320 // through the direct Diaspora protocol. If we try and use
4321 // the feed, we'll get duplicates. So don't.
4323 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4326 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4328 // Use a single verify token, even if multiple hubs
4330 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4332 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4334 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4336 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
4337 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4338 dbesc($verify_token),
4339 intval($contact['id'])
4343 post_url($url,$params);
4345 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4352 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4356 $name = xmlify($name);
4357 $uri = xmlify($uri);
4360 $photo = xmlify($photo);
4364 $o .= "\t<name>$name</name>\r\n";
4365 $o .= "\t<uri>$uri</uri>\r\n";
4366 $o .= "\t".'<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4367 $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4369 if ($tag == "author") {
4370 $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
4371 `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
4372 `profile`.`homepage`,`contact`.`nick` FROM `profile`
4373 INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
4374 INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
4375 WHERE `profile`.`is-default` AND `contact`.`self` AND
4376 NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
4377 dbesc(normalise_link($uri)));
4380 if($r[0]['locality'])
4381 $location .= $r[0]['locality'];
4382 if($r[0]['region']) {
4385 $location .= $r[0]['region'];
4387 if($r[0]['country-name']) {
4390 $location .= $r[0]['country-name'];
4393 $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
4394 $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
4395 $o .= "\t<poco:note>".xmlify($r[0]["about"])."</poco:note>\r\n";
4396 $o .= "\t<poco:address>\r\n";
4397 $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
4398 $o .= "\t</poco:address>\r\n";
4399 $o .= "\t<poco:urls>\r\n";
4400 $o .= "\t<poco:type>homepage</poco:type>\r\n";
4401 $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
4402 $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
4403 $o .= "\t</poco:urls>\r\n";
4407 call_hooks('atom_author', $o);
4409 $o .= "</$tag>\r\n";
4413 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4417 if(! $item['parent'])
4420 if($item['deleted'])
4421 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4424 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4425 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4427 $body = $item['body'];
4430 $o = "\r\n\r\n<entry>\r\n";
4432 if(is_array($author))
4433 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4435 $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']));
4436 if(strlen($item['owner-name']))
4437 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4439 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4440 $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
4441 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4442 $o .= '<thr:in-reply-to ref="'.xmlify($parent_item).'" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$parent[0]['guid']).'" />'."\r\n";
4447 if ($item['title'] != "")
4448 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4450 $htmlbody = bbcode($htmlbody, false, false, 7);
4452 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4453 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4454 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4455 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4456 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4457 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4458 $o .= '<link rel="alternate" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$item['guid']).'" />'."\r\n";
4460 $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
4463 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4465 if($item['location']) {
4466 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4467 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4471 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4473 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4474 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4477 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4478 if($item['bookmark'])
4479 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4482 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4485 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4487 if($item['signed_text']) {
4488 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4489 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4492 $verb = construct_verb($item);
4493 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4494 $actobj = construct_activity_object($item);
4497 $actarg = construct_activity_target($item);
4501 $tags = item_getfeedtags($item);
4503 foreach($tags as $t)
4504 if (($type != 'html') OR ($t[0] != "@"))
4505 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4509 // To support these elements, the API needs to be enhanced
4510 //$o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
4511 //$o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4512 //$o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4514 $o .= item_get_attachment($item);
4516 $o .= item_getfeedattach($item);
4518 $mentioned = get_mentions($item);
4522 call_hooks('atom_entry', $o);
4524 $o .= '</entry>' . "\r\n";
4529 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4531 if(get_config('system','disable_embedded'))
4536 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4537 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4542 $img_start = strpos($orig_body, '[img');
4543 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4544 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4545 while( ($img_st_close !== false) && ($img_len !== false) ) {
4547 $img_st_close++; // make it point to AFTER the closing bracket
4548 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4550 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4553 if(stristr($image , $site . '/photo/')) {
4554 // Only embed locally hosted photos
4556 $i = basename($image);
4557 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4558 $x = strpos($i,'-');
4561 $res = substr($i,$x+1);
4562 $i = substr($i,0,$x);
4563 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4570 // Check to see if we should replace this photo link with an embedded image
4571 // 1. No need to do so if the photo is public
4572 // 2. If there's a contact-id provided, see if they're in the access list
4573 // for the photo. If so, embed it.
4574 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4575 // permissions, regardless of order but first check to see if they're an exact
4576 // match to save some processing overhead.
4578 if(has_permissions($r[0])) {
4580 $recips = enumerate_permissions($r[0]);
4581 if(in_array($cid, $recips)) {
4586 if(compare_permissions($item,$r[0]))
4591 $data = $r[0]['data'];
4592 $type = $r[0]['type'];
4594 // If a custom width and height were specified, apply before embedding
4595 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4596 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4598 $width = intval($match[1]);
4599 $height = intval($match[2]);
4601 $ph = new Photo($data, $type);
4602 if($ph->is_valid()) {
4603 $ph->scaleImage(max($width, $height));
4604 $data = $ph->imageString();
4605 $type = $ph->getType();
4609 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4610 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4611 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4617 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4618 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4619 if($orig_body === false)
4622 $img_start = strpos($orig_body, '[img');
4623 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4624 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4627 $new_body = $new_body . $orig_body;
4633 function has_permissions($obj) {
4634 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4639 function compare_permissions($obj1,$obj2) {
4640 // first part is easy. Check that these are exactly the same.
4641 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4642 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4643 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4644 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4647 // This is harder. Parse all the permissions and compare the resulting set.
4649 $recipients1 = enumerate_permissions($obj1);
4650 $recipients2 = enumerate_permissions($obj2);
4653 if($recipients1 == $recipients2)
4658 // returns an array of contact-ids that are allowed to see this object
4660 function enumerate_permissions($obj) {
4661 require_once('include/group.php');
4662 $allow_people = expand_acl($obj['allow_cid']);
4663 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4664 $deny_people = expand_acl($obj['deny_cid']);
4665 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4666 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4667 $deny = array_unique(array_merge($deny_people,$deny_groups));
4668 $recipients = array_diff($recipients,$deny);
4672 function item_getfeedtags($item) {
4675 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4677 for($x = 0; $x < $cnt; $x ++) {
4679 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4683 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4685 for($x = 0; $x < $cnt; $x ++) {
4687 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4693 function item_get_attachment($item) {
4695 $siteinfo = get_attached_data($item["body"]);
4697 switch($siteinfo["type"]) {
4699 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4702 $imgdata = get_photo_info($siteinfo["image"]);
4703 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
4706 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4715 function item_getfeedattach($item) {
4717 $arr = explode('[/attach],',$item['attach']);
4719 foreach($arr as $r) {
4721 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4723 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4724 if(intval($matches[2]))
4725 $ret .= 'length="' . intval($matches[2]) . '" ';
4726 if($matches[4] !== ' ')
4727 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4728 $ret .= ' />' . "\r\n";
4737 function item_expire($uid, $days, $network = "", $force = false) {
4739 if((! $uid) || ($days < 1))
4742 // $expire_network_only = save your own wall posts
4743 // and just expire conversations started by others
4745 $expire_network_only = get_pconfig($uid,'expire','network_only');
4746 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4748 if ($network != "") {
4749 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4750 // There is an index "uid_network_received" but not "uid_network_created"
4751 // This avoids the creation of another index just for one purpose.
4752 // And it doesn't really matter wether to look at "received" or "created"
4753 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4755 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4757 $r = q("SELECT * FROM `item`
4758 WHERE `uid` = %d $range
4769 $expire_items = get_pconfig($uid, 'expire','items');
4770 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4772 // Forcing expiring of items - but not notes and marked items
4774 $expire_items = true;
4776 $expire_notes = get_pconfig($uid, 'expire','notes');
4777 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4779 $expire_starred = get_pconfig($uid, 'expire','starred');
4780 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4782 $expire_photos = get_pconfig($uid, 'expire','photos');
4783 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4785 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4787 foreach($r as $item) {
4789 // don't expire filed items
4791 if(strpos($item['file'],'[') !== false)
4794 // Only expire posts, not photos and photo comments
4796 if($expire_photos==0 && strlen($item['resource-id']))
4798 if($expire_starred==0 && intval($item['starred']))
4800 if($expire_notes==0 && $item['type']=='note')
4802 if($expire_items==0 && $item['type']!='note')
4805 drop_item($item['id'],false);
4808 proc_run('php',"include/notifier.php","expire","$uid");
4813 function drop_items($items) {
4816 if(! local_user() && ! remote_user())
4820 foreach($items as $item) {
4821 $owner = drop_item($item,false);
4822 if($owner && ! $uid)
4827 // multiple threads may have been deleted, send an expire notification
4830 proc_run('php',"include/notifier.php","expire","$uid");
4834 function drop_item($id,$interactive = true) {
4838 // locate item to be deleted
4840 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4847 notice( t('Item not found.') . EOL);
4848 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4853 $owner = $item['uid'];
4857 // check if logged in user is either the author or owner of this item
4859 if(is_array($_SESSION['remote'])) {
4860 foreach($_SESSION['remote'] as $visitor) {
4861 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4862 $cid = $visitor['cid'];
4869 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4871 // Check if we should do HTML-based delete confirmation
4872 if($_REQUEST['confirm']) {
4873 // <form> can't take arguments in its "action" parameter
4874 // so add any arguments as hidden inputs
4875 $query = explode_querystring($a->query_string);
4877 foreach($query['args'] as $arg) {
4878 if(strpos($arg, 'confirm=') === false) {
4879 $arg_parts = explode('=', $arg);
4880 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4884 return replace_macros(get_markup_template('confirm.tpl'), array(
4886 '$message' => t('Do you really want to delete this item?'),
4887 '$extra_inputs' => $inputs,
4888 '$confirm' => t('Yes'),
4889 '$confirm_url' => $query['base'],
4890 '$confirm_name' => 'confirmed',
4891 '$cancel' => t('Cancel'),
4894 // Now check how the user responded to the confirmation query
4895 if($_REQUEST['canceled']) {
4896 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4899 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4902 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4903 dbesc(datetime_convert()),
4904 dbesc(datetime_convert()),
4907 create_tags_from_item($item['id']);
4908 create_files_from_item($item['id']);
4909 delete_thread($item['id'], $item['parent-uri']);
4911 // clean up categories and tags so they don't end up as orphans
4914 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4916 foreach($matches as $mtch) {
4917 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4923 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4925 foreach($matches as $mtch) {
4926 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4930 // If item is a link to a photo resource, nuke all the associated photos
4931 // (visitors will not have photo resources)
4932 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4933 // generate a resource-id and therefore aren't intimately linked to the item.
4935 if(strlen($item['resource-id'])) {
4936 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4937 dbesc($item['resource-id']),
4938 intval($item['uid'])
4940 // ignore the result
4943 // If item is a link to an event, nuke the event record.
4945 if(intval($item['event-id'])) {
4946 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4947 intval($item['event-id']),
4948 intval($item['uid'])
4950 // ignore the result
4953 // If item has attachments, drop them
4955 foreach(explode(",",$item['attach']) as $attach){
4956 preg_match("|attach/(\d+)|", $attach, $matches);
4957 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4958 intval($matches[1]),
4961 // ignore the result
4965 // clean up item_id and sign meta-data tables
4968 // Old code - caused very long queries and warning entries in the mysql logfiles:
4970 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4971 intval($item['id']),
4972 intval($item['uid'])
4975 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4976 intval($item['id']),
4977 intval($item['uid'])
4981 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4983 // Creating list of parents
4984 $r = q("select id from item where parent = %d and uid = %d",
4985 intval($item['id']),
4986 intval($item['uid'])
4991 foreach ($r AS $row) {
4992 if ($parentid != "")
4995 $parentid .= $row["id"];
4999 if ($parentid != "") {
5000 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
5002 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
5005 // If it's the parent of a comment thread, kill all the kids
5007 if($item['uri'] == $item['parent-uri']) {
5008 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
5009 WHERE `parent-uri` = '%s' AND `uid` = %d ",
5010 dbesc(datetime_convert()),
5011 dbesc(datetime_convert()),
5012 dbesc($item['parent-uri']),
5013 intval($item['uid'])
5015 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
5016 create_files_from_itemuri($item['parent-uri'], $item['uid']);
5017 delete_thread_uri($item['parent-uri'], $item['uid']);
5018 // ignore the result
5021 // ensure that last-child is set in case the comment that had it just got wiped.
5022 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
5023 dbesc(datetime_convert()),
5024 dbesc($item['parent-uri']),
5025 intval($item['uid'])
5027 // who is the last child now?
5028 $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",
5029 dbesc($item['parent-uri']),
5030 intval($item['uid'])
5033 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
5038 // Add a relayable_retraction signature for Diaspora.
5039 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
5042 $drop_id = intval($item['id']);
5044 // send the notification upstream/downstream as the case may be
5046 proc_run('php',"include/notifier.php","drop","$drop_id");
5050 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5056 notice( t('Permission denied.') . EOL);
5057 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5064 function first_post_date($uid,$wall = false) {
5065 $r = q("select id, created from item
5066 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
5068 order by created asc limit 1",
5070 intval($wall ? 1 : 0)
5073 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
5074 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
5079 /* modified posted_dates() {below} to arrange the list in years */
5080 function list_post_dates($uid, $wall) {
5081 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5083 $dthen = first_post_date($uid, $wall);
5087 // Set the start and end date to the beginning of the month
5088 $dnow = substr($dnow,0,8).'01';
5089 $dthen = substr($dthen,0,8).'01';
5093 // Starting with the current month, get the first and last days of every
5094 // month down to and including the month of the first post
5095 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5096 $dyear = intval(substr($dnow,0,4));
5097 $dstart = substr($dnow,0,8) . '01';
5098 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5099 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5100 $end_month = datetime_convert('','',$dend,'Y-m-d');
5101 $str = day_translate(datetime_convert('','',$dnow,'F'));
5103 $ret[$dyear] = array();
5104 $ret[$dyear][] = array($str,$end_month,$start_month);
5105 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5110 function posted_dates($uid,$wall) {
5111 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5113 $dthen = first_post_date($uid,$wall);
5117 // Set the start and end date to the beginning of the month
5118 $dnow = substr($dnow,0,8).'01';
5119 $dthen = substr($dthen,0,8).'01';
5122 // Starting with the current month, get the first and last days of every
5123 // month down to and including the month of the first post
5124 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5125 $dstart = substr($dnow,0,8) . '01';
5126 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5127 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5128 $end_month = datetime_convert('','',$dend,'Y-m-d');
5129 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5130 $ret[] = array($str,$end_month,$start_month);
5131 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5137 function posted_date_widget($url,$uid,$wall) {
5140 if(! feature_enabled($uid,'archives'))
5143 // For former Facebook folks that left because of "timeline"
5145 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5148 $visible_years = get_pconfig($uid,'system','archive_visible_years');
5149 if(! $visible_years)
5152 $ret = list_post_dates($uid,$wall);
5157 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5158 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5160 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5161 '$title' => t('Archives'),
5162 '$size' => $visible_years,
5163 '$cutoff_year' => $cutoff_year,
5164 '$cutoff' => $cutoff,
5167 '$showmore' => t('show more')
5173 function store_diaspora_retract_sig($item, $user, $baseurl) {
5174 // Note that we can't add a target_author_signature
5175 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5176 // the comment, that means we're the home of the post, and Diaspora will only
5177 // check the parent_author_signature of retractions that it doesn't have to relay further
5179 // I don't think this function gets called for an "unlike," but I'll check anyway
5181 $enabled = intval(get_config('system','diaspora_enabled'));
5183 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5187 logger('drop_item: storing diaspora retraction signature');
5189 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5191 if(local_user() == $item['uid']) {
5193 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5194 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5197 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5198 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5201 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5202 // only handles DFRN deletes
5203 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5204 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5205 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5211 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5212 intval($item['id']),
5213 dbesc($signed_text),