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('include/feed.php');
17 require_once('mod/share.php');
19 require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
22 function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0, $forpubsub = false) {
25 $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
26 $public_feed = (($dfrn_id) ? false : true);
27 $starred = false; // not yet implemented, possible security issues
30 if($public_feed && $a->argc > 2) {
31 for($x = 2; $x < $a->argc; $x++) {
32 if($a->argv[$x] == 'converse')
34 if($a->argv[$x] == 'starred')
36 if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
37 $category = $a->argv[$x+1];
43 // default permissions - anonymous user
45 $sql_extra = " AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' ";
47 $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
48 FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
49 WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
57 $owner_id = $owner['user_uid'];
58 $owner_nick = $owner['nickname'];
60 $birthday = feed_birthday($owner_id,$owner['timezone']);
70 $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
74 $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
75 $my_id = '1:' . $dfrn_id;
78 $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
79 $my_id = '0:' . $dfrn_id;
86 $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
94 require_once('include/security.php');
95 $groups = init_groups_visitor($contact['id']);
98 for($x = 0; $x < count($groups); $x ++)
99 $groups[$x] = '<' . intval($groups[$x]) . '>' ;
100 $gs = implode('|', $groups);
103 $gs = '<<>>' ; // Impossible to match
105 $sql_extra = sprintf("
106 AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' )
107 AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' )
108 AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' )
109 AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s')
111 intval($contact['id']),
112 intval($contact['id']),
123 // Include answers to status.net posts in pubsub feeds
125 $sql_post_table = "INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent`
126 LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid`";
127 $visibility = sprintf("AND (`item`.`parent` = `item`.`id`) OR (`item`.`network` = '%s' AND ((`thread`.`network`='%s') OR (`thritem`.`network` = '%s')))",
128 dbesc(NETWORK_DFRN), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS));
129 $date_field = "`received`";
130 $sql_order = "`item`.`received` DESC";
132 $date_field = "`changed`";
133 $sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
136 if(! strlen($last_update))
137 $last_update = 'now -30 days';
139 if(isset($category)) {
140 $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` ",
141 dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
142 //$sql_extra .= file_tag_file_query('item',$category,'category');
147 $sql_extra .= " AND `contact`.`self` = 1 ";
150 $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
152 // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
153 // dbesc($check_date),
155 $r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
156 `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
157 `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
158 `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
159 `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
160 `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
161 FROM `item` $sql_post_table
162 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
163 AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
164 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
165 WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
166 AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
168 ORDER BY $sql_order LIMIT 0, 300",
174 // Will check further below if this actually returned results.
175 // We will provide an empty feed if that is the case.
179 $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
183 $hubxml = feed_hublinks();
185 $salmon = feed_salmonlinks($owner_nick);
187 $alternatelink = $owner['url'];
190 $alternatelink .= "/category/".$category;
192 $atom .= replace_macros($feed_template, array(
193 '$version' => xmlify(FRIENDICA_VERSION),
194 '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
195 '$feed_title' => xmlify($owner['name']),
196 '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
198 '$salmon' => $salmon,
199 '$alternatelink' => xmlify($alternatelink),
200 '$name' => xmlify($owner['name']),
201 '$profile_page' => xmlify($owner['url']),
202 '$photo' => xmlify($owner['photo']),
203 '$thumb' => xmlify($owner['thumb']),
204 '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
205 '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) ,
206 '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) ,
207 '$birthday' => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
208 '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
211 call_hooks('atom_feed', $atom);
213 if(! count($items)) {
215 call_hooks('atom_feed_end', $atom);
217 $atom .= '</feed>' . "\r\n";
221 foreach($items as $item) {
223 // prevent private email from leaking.
224 if($item['network'] === NETWORK_MAIL)
227 // public feeds get html, our own nodes use bbcode
231 // catch any email that's in a public conversation and make sure it doesn't leak
239 $atom .= atom_entry($item,$type,null,$owner,true);
242 call_hooks('atom_feed_end', $atom);
244 $atom .= '</feed>' . "\r\n";
250 function construct_verb($item) {
252 return $item['verb'];
253 return ACTIVITY_POST;
256 function construct_activity_object($item) {
258 if($item['object']) {
259 $o = '<as:object>' . "\r\n";
260 $r = parse_xml_string($item['object'],false);
266 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
268 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
270 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
272 if(substr($r->link,0,1) === '<') {
273 // patch up some facebook "like" activity objects that got stored incorrectly
274 // for a couple of months prior to 9-Jun-2011 and generated bad XML.
275 // we can probably remove this hack here and in the following function in a few months time.
276 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
277 $r->link = str_replace('&','&', $r->link);
278 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
282 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
285 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
286 $o .= '</as:object>' . "\r\n";
293 function construct_activity_target($item) {
295 if($item['target']) {
296 $o = '<as:target>' . "\r\n";
297 $r = parse_xml_string($item['target'],false);
301 $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
303 $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
305 $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
307 if(substr($r->link,0,1) === '<') {
308 if(strstr($r->link,'&') && (! strstr($r->link,'&')))
309 $r->link = str_replace('&','&', $r->link);
310 $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
314 $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
317 $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
318 $o .= '</as:target>' . "\r\n";
327 * The purpose of this function is to apply system message length limits to
328 * imported messages without including any embedded photos in the length
330 if(! function_exists('limit_body_size')) {
331 function limit_body_size($body) {
333 // logger('limit_body_size: start', LOGGER_DEBUG);
335 $maxlen = get_max_import_size();
337 // If the length of the body, including the embedded images, is smaller
338 // than the maximum, then don't waste time looking for the images
339 if($maxlen && (strlen($body) > $maxlen)) {
341 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
348 $img_start = strpos($orig_body, '[img');
349 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
350 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
351 while(($img_st_close !== false) && ($img_end !== false)) {
353 $img_st_close++; // make it point to AFTER the closing bracket
354 $img_end += $img_start;
355 $img_end += strlen('[/img]');
357 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
358 // This is an embedded image
360 if( ($textlen + $img_start) > $maxlen ) {
361 if($textlen < $maxlen) {
362 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
363 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
368 $new_body = $new_body . substr($orig_body, 0, $img_start);
369 $textlen += $img_start;
372 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
376 if( ($textlen + $img_end) > $maxlen ) {
377 if($textlen < $maxlen) {
378 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
379 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
384 $new_body = $new_body . substr($orig_body, 0, $img_end);
385 $textlen += $img_end;
388 $orig_body = substr($orig_body, $img_end);
390 if($orig_body === false) // in case the body ends on a closing image tag
393 $img_start = strpos($orig_body, '[img');
394 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
395 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
398 if( ($textlen + strlen($orig_body)) > $maxlen) {
399 if($textlen < $maxlen) {
400 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
401 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
406 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
407 $new_body = $new_body . $orig_body;
408 $textlen += strlen($orig_body);
417 function title_is_body($title, $body) {
419 $title = strip_tags($title);
420 $title = trim($title);
421 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
422 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
424 $body = strip_tags($body);
426 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
427 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
429 if (strlen($title) < strlen($body))
430 $body = substr($body, 0, strlen($title));
432 if (($title != $body) and (substr($title, -3) == "...")) {
433 $pos = strrpos($title, "...");
435 $title = substr($title, 0, $pos);
436 $body = substr($body, 0, $pos);
440 return($title == $body);
445 function get_atom_elements($feed, $item, $contact = array()) {
447 require_once('library/HTMLPurifier.auto.php');
448 require_once('include/html2bbcode.php');
450 $best_photo = array();
454 $author = $item->get_author();
456 $res['author-name'] = unxmlify($author->get_name());
457 $res['author-link'] = unxmlify($author->get_link());
460 $res['author-name'] = unxmlify($feed->get_title());
461 $res['author-link'] = unxmlify($feed->get_permalink());
463 $res['uri'] = unxmlify($item->get_id());
464 $res['title'] = unxmlify($item->get_title());
465 $res['body'] = unxmlify($item->get_content());
466 $res['plink'] = unxmlify($item->get_link(0));
468 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
469 logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
471 $res['body'] = nl2br($res['body']);
474 // removing the content of the title if its identically to the body
475 // This helps with auto generated titles e.g. from tumblr
476 if (title_is_body($res["title"], $res["body"]))
480 $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
484 // look for a photo. We should check media size and find the best one,
485 // but for now let's just find any author photo
486 // Additionally we look for an alternate author link. On OStatus this one is the one we want.
488 $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"];
489 if (is_array($authorlinks)) {
490 foreach ($authorlinks as $link) {
491 $linkdata = array_shift($link["attribs"]);
493 if ($linkdata["rel"] == "alternate")
494 $res["author-link"] = $linkdata["href"];
498 $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
500 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
501 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
502 foreach($base as $link) {
503 if($link['attribs']['']['rel'] === 'alternate')
504 $res['author-link'] = unxmlify($link['attribs']['']['href']);
506 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
507 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
508 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
513 $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
515 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
516 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
517 if($base && count($base)) {
518 foreach($base as $link) {
519 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
520 $res['author-link'] = unxmlify($link['attribs']['']['href']);
521 if(!x($res, 'author-avatar') || !$res['author-avatar']) {
522 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
523 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
529 // No photo/profile-link on the item - look at the feed level
531 if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
532 $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
533 if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
534 $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
535 foreach($base as $link) {
536 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
537 $res['author-link'] = unxmlify($link['attribs']['']['href']);
538 if(! $res['author-avatar']) {
539 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
540 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
545 $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
547 if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
548 $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
550 if($base && count($base)) {
551 foreach($base as $link) {
552 if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
553 $res['author-link'] = unxmlify($link['attribs']['']['href']);
554 if(! (x($res,'author-avatar'))) {
555 if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
556 $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
563 $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
564 if($apps && $apps[0]['attribs']['']['source']) {
565 $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
566 if($res['app'] === 'web')
567 $res['app'] = 'OStatus';
570 // base64 encoded json structure representing Diaspora signature
572 $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
574 $res['dsprsig'] = unxmlify($dsig[0]['data']);
577 $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
579 $res['guid'] = unxmlify($dguid[0]['data']);
581 $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
583 $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
587 * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
590 $have_real_body = false;
592 $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
594 $have_real_body = true;
595 $res['body'] = $rawenv[0]['data'];
596 $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
597 // make sure nobody is trying to sneak some html tags by us
598 $res['body'] = notags(base64url_decode($res['body']));
602 $res['body'] = limit_body_size($res['body']);
604 // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
605 // the content type. Our own network only emits text normally, though it might have been converted to
606 // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
607 // have to assume it is all html and needs to be purified.
609 // It doesn't matter all that much security wise - because before this content is used anywhere, we are
610 // going to escape any tags we find regardless, but this lets us import a limited subset of html from
611 // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
614 if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
616 $res['body'] = reltoabs($res['body'],$base_url);
618 $res['body'] = html2bb_video($res['body']);
620 $res['body'] = oembed_html2bbcode($res['body']);
622 $config = HTMLPurifier_Config::createDefault();
623 $config->set('Cache.DefinitionImpl', null);
625 // we shouldn't need a whitelist, because the bbcode converter
626 // will strip out any unsupported tags.
628 $purifier = new HTMLPurifier($config);
629 $res['body'] = $purifier->purify($res['body']);
631 $res['body'] = @html2bbcode($res['body']);
635 elseif(! $have_real_body) {
637 // it's not one of our messages and it has no tags
638 // so it's probably just text. We'll escape it just to be safe.
640 $res['body'] = escape_tags($res['body']);
644 // this tag is obsolete but we keep it for really old sites
646 $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
647 if($allow && $allow[0]['data'] == 1)
648 $res['last-child'] = 1;
650 $res['last-child'] = 0;
652 $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
653 if($private && intval($private[0]['data']) > 0)
654 $res['private'] = intval($private[0]['data']);
658 $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
659 if($extid && $extid[0]['data'])
660 $res['extid'] = $extid[0]['data'];
662 $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
664 $res['location'] = unxmlify($rawlocation[0]['data']);
667 $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
669 $res['created'] = unxmlify($rawcreated[0]['data']);
672 $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
674 $res['edited'] = unxmlify($rawedited[0]['data']);
676 if((x($res,'edited')) && (! (x($res,'created'))))
677 $res['created'] = $res['edited'];
679 if(! $res['created'])
680 $res['created'] = $item->get_date('c');
683 $res['edited'] = $item->get_date('c');
686 // Disallow time travelling posts
688 $d1 = strtotime($res['created']);
689 $d2 = strtotime($res['edited']);
690 $d3 = strtotime('now');
693 $res['created'] = datetime_convert();
695 $res['edited'] = datetime_convert();
697 $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
698 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
699 $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
700 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
701 $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
702 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
703 $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
704 elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
705 $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
707 if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
708 $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
710 foreach($base as $link) {
711 if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
712 if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
713 $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
718 $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
720 $res['coord'] = unxmlify($rawgeo[0]['data']);
722 if ($contact["network"] == NETWORK_FEED) {
723 $res['verb'] = ACTIVITY_POST;
724 $res['object-type'] = ACTIVITY_OBJ_NOTE;
727 $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
729 // select between supported verbs
732 $res['verb'] = unxmlify($rawverb[0]['data']);
735 // translate OStatus unfollow to activity streams if it happened to get selected
737 if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
738 $res['verb'] = ACTIVITY_UNFOLLOW;
740 $cats = $item->get_categories();
743 foreach($cats as $cat) {
744 $term = $cat->get_term();
746 $term = $cat->get_label();
747 $scheme = $cat->get_scheme();
748 if($scheme && $term && stristr($scheme,'X-DFRN:'))
749 $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
751 $tag_arr[] = notags(trim($term));
753 $res['tag'] = implode(',', $tag_arr);
756 $attach = $item->get_enclosures();
759 foreach($attach as $att) {
760 $len = intval($att->get_length());
761 $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
762 $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
763 $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
764 if(strpos($type,';'))
765 $type = substr($type,0,strpos($type,';'));
766 if((! $link) || (strpos($link,'http') !== 0))
772 $type = 'application/octet-stream';
774 $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
776 $res['attach'] = implode(',', $att_arr);
779 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
782 $res['object'] = '<object>' . "\n";
783 $child = $rawobj[0]['child'];
784 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
785 $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
786 $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
788 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
789 $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
790 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
791 $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
792 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
793 $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
794 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
795 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
797 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
798 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
799 $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
800 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
802 $body = html2bb_video($body);
804 $config = HTMLPurifier_Config::createDefault();
805 $config->set('Cache.DefinitionImpl', null);
807 $purifier = new HTMLPurifier($config);
808 $body = $purifier->purify($body);
809 $body = html2bbcode($body);
812 $res['object'] .= '<content>' . $body . '</content>' . "\n";
815 $res['object'] .= '</object>' . "\n";
818 $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
821 $res['target'] = '<target>' . "\n";
822 $child = $rawobj[0]['child'];
823 if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
824 $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
826 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
827 $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
828 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
829 $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
830 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
831 $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
832 if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
833 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
835 $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
836 // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
837 $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
838 if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
840 $body = html2bb_video($body);
842 $config = HTMLPurifier_Config::createDefault();
843 $config->set('Cache.DefinitionImpl', null);
845 $purifier = new HTMLPurifier($config);
846 $body = $purifier->purify($body);
847 $body = html2bbcode($body);
850 $res['target'] .= '<content>' . $body . '</content>' . "\n";
853 $res['target'] .= '</target>' . "\n";
856 // This is some experimental stuff. By now retweets are shown with "RT:"
857 // But: There is data so that the message could be shown similar to native retweets
858 // There is some better way to parse this array - but it didn't worked for me.
859 $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"];
860 if (is_array($child)) {
861 logger('get_atom_elements: Looking for status.net repeated message');
863 $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
864 $orig_id = ostatus_convert_href($child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"]);
865 $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
866 $uri = $author["uri"][0]["data"];
867 $name = $author["name"][0]["data"];
868 $avatar = @array_shift($author["link"][2]["attribs"]);
869 $avatar = $avatar["href"];
871 if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
872 logger('get_atom_elements: fixing sender of repeated message. '.$orig_id, LOGGER_DEBUG);
874 if (!intval(get_config('system','wall-to-wall_share'))) {
875 $prefix = share_header($name, $uri, $avatar, "", "", $orig_link);
877 $res["body"] = $prefix.html2bbcode($message)."[/share]";
879 $res["owner-name"] = $res["author-name"];
880 $res["owner-link"] = $res["author-link"];
881 $res["owner-avatar"] = $res["author-avatar"];
883 $res["author-name"] = $name;
884 $res["author-link"] = $uri;
885 $res["author-avatar"] = $avatar;
887 $res["body"] = html2bbcode($message);
892 if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
895 // Handle enclosures and treat them as preview picture
897 foreach ($attach AS $attachment)
898 if ($attachment->type == "image/jpeg")
899 $preview = $attachment->link;
901 $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
902 $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
904 $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
905 unset($res["attach"]);
906 } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
907 $res["body"] = add_page_info_to_body($res["body"]);
908 elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
909 $res["body"] = add_page_info_to_body($res["body"]);
912 $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
914 call_hooks('parse_atom', $arr);
919 function add_page_info_data($data) {
920 call_hooks('page_info_data', $data);
922 // It maybe is a rich content, but if it does have everything that a link has,
923 // then treat it that way
924 if (($data["type"] == "rich") AND is_string($data["title"]) AND
925 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
926 $data["type"] = "link";
928 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
931 if ($no_photos AND ($data["type"] == "photo"))
934 // If the link contains BBCode stuff, make a short link out of this to avoid parsing problems
935 if (strpos($data["url"], '[') OR strpos($data["url"], ']')) {
936 require_once("include/network.php");
937 $data["url"] = short_link($data["url"]);
940 if (($data["type"] != "photo") AND is_string($data["title"]))
941 $text .= "[bookmark=".$data["url"]."]".trim($data["title"])."[/bookmark]";
943 if (($data["type"] != "video") AND ($photo != ""))
944 $text .= '[img]'.$photo.'[/img]';
945 elseif (($data["type"] != "video") AND (sizeof($data["images"]) > 0)) {
946 $imagedata = $data["images"][0];
947 $text .= '[img]'.$imagedata["src"].'[/img]';
950 if (($data["type"] != "photo") AND is_string($data["text"]))
951 $text .= "[quote]".$data["text"]."[/quote]";
954 if (isset($data["keywords"]) AND count($data["keywords"])) {
957 foreach ($data["keywords"] AS $keyword) {
958 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
959 array("","", "", "", "", ""), $keyword);
960 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
964 return("\n[class=type-".$data["type"]."]".$text."[/class]".$hashtags);
967 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
968 require_once("mod/parse_url.php");
970 $data = Cache::get("parse_url:".$url);
972 $data = parseurl_getsiteinfo($url, true);
973 Cache::set("parse_url:".$url,serialize($data), CACHE_DAY);
975 $data = unserialize($data);
978 $data["images"][0]["src"] = $photo;
980 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
982 if (!$keywords AND isset($data["keywords"]))
983 unset($data["keywords"]);
985 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
986 $list = explode(",", $keyword_blacklist);
987 foreach ($list AS $keyword) {
988 $keyword = trim($keyword);
989 $index = array_search($keyword, $data["keywords"]);
990 if ($index !== false)
991 unset($data["keywords"][$index]);
998 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
999 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1002 if (isset($data["keywords"]) AND count($data["keywords"])) {
1004 foreach ($data["keywords"] AS $keyword) {
1005 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
1006 array("","", "", "", "", ""), $keyword);
1011 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
1018 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
1019 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
1021 $text = add_page_info_data($data);
1026 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
1028 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
1030 $URLSearchString = "^\[\]";
1032 // Adding these spaces is a quick hack due to my problems with regular expressions :)
1033 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
1036 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
1038 // Convert urls without bbcode elements
1039 if (!$matches AND $texturl) {
1040 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
1042 // Yeah, a hack. I really hate regular expressions :)
1044 $matches[1] = $matches[2];
1048 $footer = add_page_info($matches[1], $no_photos);
1050 // Remove the link from the body if the link is attached at the end of the post
1051 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
1052 $removedlink = trim(str_replace($matches[1], "", $body));
1053 if (($removedlink == "") OR strstr($body, $removedlink))
1054 $body = $removedlink;
1056 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
1057 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
1058 if (($removedlink == "") OR strstr($body, $removedlink))
1059 $body = $removedlink;
1062 // Add the page information to the bottom
1063 if (isset($footer) AND (trim($footer) != ""))
1069 function encode_rel_links($links) {
1071 if(! ((is_array($links)) && (count($links))))
1073 foreach($links as $link) {
1075 if($link['attribs']['']['rel'])
1076 $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
1077 if($link['attribs']['']['type'])
1078 $o .= 'type="' . $link['attribs']['']['type'] . '" ';
1079 if($link['attribs']['']['href'])
1080 $o .= 'href="' . $link['attribs']['']['href'] . '" ';
1081 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
1082 $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
1083 if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
1084 $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
1085 $o .= ' />' . "\n" ;
1090 function add_guid($item) {
1091 $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
1095 q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
1096 dbesc($item["guid"]), dbesc($item["plink"]),
1097 dbesc($item["uri"]), dbesc($item["network"]));
1100 // Adds a "lang" specification in a "postopts" element of given $arr,
1101 // if possible and not already present.
1102 // Expects "body" element to exist in $arr.
1103 // TODO: add a parameter to request forcing override
1104 function item_add_language_opt(&$arr) {
1106 if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
1108 if ( x($arr, 'postopts') )
1110 if ( strstr($arr['postopts'], 'lang=') )
1113 // TODO: add parameter to request overriding
1116 $postopts = $arr['postopts'];
1123 require_once('library/langdet/Text/LanguageDetect.php');
1124 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
1125 $l = new Text_LanguageDetect;
1126 //$lng = $l->detectConfidence($naked_body);
1127 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
1128 $lng = $l->detect($naked_body, 3);
1130 if (sizeof($lng) > 0) {
1131 if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
1132 $postopts .= 'lang=';
1134 foreach ($lng as $language => $score) {
1135 $postopts .= $sep . $language.";".$score;
1138 $arr['postopts'] = $postopts;
1142 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
1144 // If it is a posting where users should get notifications, then define it as wall posting
1147 $arr['type'] = 'wall';
1149 $arr['last-child'] = 1;
1150 $arr['network'] = NETWORK_DFRN;
1153 // If a Diaspora signature structure was passed in, pull it out of the
1154 // item array and set it aside for later storage.
1157 if(x($arr,'dsprsig')) {
1158 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
1159 unset($arr['dsprsig']);
1162 // Converting the plink
1163 if ($arr['network'] == NETWORK_OSTATUS) {
1164 if (isset($arr['plink']))
1165 $arr['plink'] = ostatus_convert_href($arr['plink']);
1166 elseif (isset($arr['uri']))
1167 $arr['plink'] = ostatus_convert_href($arr['uri']);
1170 if(x($arr, 'gravity'))
1171 $arr['gravity'] = intval($arr['gravity']);
1172 elseif($arr['parent-uri'] === $arr['uri'])
1173 $arr['gravity'] = 0;
1174 elseif(activity_match($arr['verb'],ACTIVITY_POST))
1175 $arr['gravity'] = 6;
1177 $arr['gravity'] = 6; // extensible catchall
1179 if(! x($arr,'type'))
1180 $arr['type'] = 'remote';
1184 /* check for create date and expire time */
1185 $uid = intval($arr['uid']);
1186 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
1188 $expire_interval = $r[0]['expire'];
1189 if ($expire_interval>0) {
1190 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
1191 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
1192 if ($created_date < $expire_date) {
1193 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
1199 // Do we already have this item?
1200 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
1201 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
1202 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
1203 dbesc(trim($arr['uri'])),
1205 dbesc(NETWORK_DIASPORA),
1206 dbesc(NETWORK_DFRN),
1207 dbesc(NETWORK_OSTATUS)
1210 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
1212 logger("Item with uri ".$arr['uri']." already existed for user ".$uid." with id ".$r[0]["id"]." target network ".$r[0]["network"]." - new network: ".$arr['network']);
1213 return($r[0]["id"]);
1217 // If there is no guid then take the same guid that was taken before for the same uri
1218 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
1219 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1220 $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
1221 dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
1224 $arr['guid'] = $r[0]["guid"];
1225 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1229 // If there is no guid then take the same guid that was taken before for the same plink
1230 if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
1231 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
1232 $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
1233 dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
1236 $arr['guid'] = $r[0]["guid"];
1237 logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
1239 if ($r[0]["uri"] != $arr['uri'])
1240 logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
1244 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1245 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1246 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1247 // $arr['body'] = strip_tags($arr['body']);
1249 item_add_language_opt($arr);
1254 $guid_prefix = $arr['network'];
1256 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1257 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
1258 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
1259 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1260 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
1261 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1262 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1263 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
1264 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1265 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1266 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1267 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1268 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1269 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1270 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1271 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
1272 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
1273 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1274 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1275 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1276 $arr['deleted'] = 0;
1277 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1278 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1279 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1280 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1281 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1282 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1283 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1284 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1285 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1286 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1287 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1288 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1289 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1290 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1291 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1292 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1293 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1294 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1295 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1296 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1297 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1298 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1299 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1300 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1302 if ($arr['plink'] == "") {
1304 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1307 if ($arr['network'] == "") {
1308 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
1309 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
1310 dbesc(normalise_link($arr['author-link'])),
1315 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
1316 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
1317 dbesc(normalise_link($arr['author-link']))
1321 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1322 intval($arr['contact-id']),
1327 $arr['network'] = $r[0]["network"];
1329 // Fallback to friendica (why is it empty in some cases?)
1330 if ($arr['network'] == "")
1331 $arr['network'] = NETWORK_DFRN;
1333 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1336 if ($arr['guid'] != "") {
1337 // Checking if there is already an item with the same guid
1338 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1339 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1340 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1343 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1348 // Check for hashtags in the body and repair or add hashtag links
1349 item_body_set_hashtags($arr);
1351 $arr['thr-parent'] = $arr['parent-uri'];
1352 if($arr['parent-uri'] === $arr['uri']) {
1354 $parent_deleted = 0;
1355 $allow_cid = $arr['allow_cid'];
1356 $allow_gid = $arr['allow_gid'];
1357 $deny_cid = $arr['deny_cid'];
1358 $deny_gid = $arr['deny_gid'];
1359 $notify_type = 'wall-new';
1363 // find the parent and snarf the item id and ACLs
1364 // and anything else we need to inherit
1366 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1367 dbesc($arr['parent-uri']),
1373 // is the new message multi-level threaded?
1374 // even though we don't support it now, preserve the info
1375 // and re-attach to the conversation parent.
1377 if($r[0]['uri'] != $r[0]['parent-uri']) {
1378 $arr['parent-uri'] = $r[0]['parent-uri'];
1379 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1380 ORDER BY `id` ASC LIMIT 1",
1381 dbesc($r[0]['parent-uri']),
1382 dbesc($r[0]['parent-uri']),
1389 $parent_id = $r[0]['id'];
1390 $parent_deleted = $r[0]['deleted'];
1391 $allow_cid = $r[0]['allow_cid'];
1392 $allow_gid = $r[0]['allow_gid'];
1393 $deny_cid = $r[0]['deny_cid'];
1394 $deny_gid = $r[0]['deny_gid'];
1395 $arr['wall'] = $r[0]['wall'];
1396 $notify_type = 'comment-new';
1398 // if the parent is private, force privacy for the entire conversation
1399 // This differs from the above settings as it subtly allows comments from
1400 // email correspondents to be private even if the overall thread is not.
1402 if($r[0]['private'])
1403 $arr['private'] = $r[0]['private'];
1405 // Edge case. We host a public forum that was originally posted to privately.
1406 // The original author commented, but as this is a comment, the permissions
1407 // weren't fixed up so it will still show the comment as private unless we fix it here.
1409 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1410 $arr['private'] = 0;
1413 // If its a post from myself then tag the thread as "mention"
1414 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1415 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1418 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1419 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1420 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1421 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1422 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1428 // Allow one to see reply tweets from status.net even when
1429 // we don't have or can't see the original post.
1432 logger('item_store: $force_parent=true, reply converted to top-level post.');
1434 $arr['parent-uri'] = $arr['uri'];
1435 $arr['gravity'] = 0;
1438 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1442 $parent_deleted = 0;
1446 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
1448 dbesc($arr['network']),
1449 dbesc(NETWORK_DFRN),
1452 if($r && count($r)) {
1453 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1457 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1458 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1459 dbesc($arr['body']),
1460 dbesc($arr['network']),
1461 dbesc($arr['created']),
1462 intval($arr['contact-id']),
1465 if($r && count($r)) {
1466 logger('duplicated item with the same body found. ' . print_r($arr,true));
1470 // Is this item available in the global items (with uid=0)?
1471 if ($arr["uid"] == 0) {
1472 $arr["global"] = true;
1474 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1476 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1478 $arr["global"] = (count($isglobal) > 0);
1481 // Fill the cache field
1482 put_item_in_cache($arr);
1485 call_hooks('post_local',$arr);
1487 call_hooks('post_remote',$arr);
1489 if(x($arr,'cancel')) {
1490 logger('item_store: post cancelled by plugin.');
1494 // Store the unescaped version
1499 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1501 $r = dbq("INSERT INTO `item` (`"
1502 . implode("`, `", array_keys($arr))
1504 . implode("', '", array_values($arr))
1510 // find the item that we just created
1511 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' ORDER BY `id` ASC",
1513 intval($arr['uid']),
1514 dbesc($arr['network'])
1518 // There are duplicates. Keep the oldest one, delete the others
1519 logger('item_store: duplicated post occurred. Removing newer duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1520 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' AND `id` > %d",
1522 intval($arr['uid']),
1523 dbesc($arr['network']),
1527 } elseif(count($r)) {
1529 // Store the guid and other relevant data
1532 $current_post = $r[0]['id'];
1533 logger('item_store: created item ' . $current_post);
1535 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1536 // This can be used to filter for inactive contacts.
1537 // Only do this for public postings to avoid privacy problems, since poco data is public.
1538 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1540 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1542 // Is it a forum? Then we don't care about the rules from above
1543 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1544 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1545 intval($arr['contact-id']));
1551 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1552 dbesc($arr['received']),
1553 dbesc($arr['received']),
1554 intval($arr['contact-id'])
1557 logger('item_store: could not locate created item');
1561 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1562 $parent_id = $current_post;
1564 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1567 $private = $arr['private'];
1569 // Set parent id - and also make sure to inherit the parent's ACLs.
1571 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1572 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1579 intval($parent_deleted),
1580 intval($current_post)
1583 $arr['id'] = $current_post;
1584 $arr['parent'] = $parent_id;
1585 $arr['allow_cid'] = $allow_cid;
1586 $arr['allow_gid'] = $allow_gid;
1587 $arr['deny_cid'] = $deny_cid;
1588 $arr['deny_gid'] = $deny_gid;
1589 $arr['private'] = $private;
1590 $arr['deleted'] = $parent_deleted;
1592 // update the commented timestamp on the parent
1593 // Only update "commented" if it is really a comment
1594 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1595 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1596 dbesc(datetime_convert()),
1597 dbesc(datetime_convert()),
1601 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1602 dbesc(datetime_convert()),
1607 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1608 intval($current_post),
1609 dbesc($dsprsig->signed_text),
1610 dbesc($dsprsig->signature),
1611 dbesc($dsprsig->signer)
1617 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1620 if($arr['last-child']) {
1621 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1623 intval($arr['uid']),
1624 intval($current_post)
1628 $deleted = tag_deliver($arr['uid'],$current_post);
1630 // current post can be deleted if is for a community page and no mention are
1632 if (!$deleted AND !$dontcache) {
1634 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1635 if (count($r) == 1) {
1637 call_hooks('post_local_end', $r[0]);
1639 call_hooks('post_remote_end', $r[0]);
1641 logger('item_store: new item not found in DB, id ' . $current_post);
1644 // Add every contact of the post to the global contact table
1647 create_tags_from_item($current_post);
1648 create_files_from_item($current_post);
1650 // Only check for notifications on start posts
1651 if ($arr['parent-uri'] === $arr['uri']) {
1652 add_thread($current_post);
1653 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1655 // Send a notification for every new post?
1656 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1657 intval($arr['contact-id']),
1660 $send_notification = count($r);
1662 if (!$send_notification) {
1663 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1664 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1667 foreach ($tags AS $tag) {
1668 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1669 normalise_link($tag["url"]), intval($arr['uid']));
1671 $send_notification = true;
1676 if ($send_notification) {
1677 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1678 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1679 intval($arr['uid']));
1681 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1682 intval($current_post),
1688 require_once('include/enotify.php');
1690 'type' => NOTIFY_SHARE,
1691 'notify_flags' => $u[0]['notify-flags'],
1692 'language' => $u[0]['language'],
1693 'to_name' => $u[0]['username'],
1694 'to_email' => $u[0]['email'],
1695 'uid' => $u[0]['uid'],
1697 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1698 'source_name' => $item[0]['author-name'],
1699 'source_link' => $item[0]['author-link'],
1700 'source_photo' => $item[0]['author-avatar'],
1701 'verb' => ACTIVITY_TAG,
1703 'parent' => $arr['parent']
1705 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1708 update_thread($parent_id);
1709 add_shadow_entry($arr);
1713 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1715 return $current_post;
1718 function item_body_set_hashtags(&$item) {
1720 $tags = get_tags($item["body"]);
1726 // This sorting is important when there are hashtags that are part of other hashtags
1727 // Otherwise there could be problems with hashtags like #test and #test2
1732 $URLSearchString = "^\[\]";
1734 // All hashtags should point to the home server
1735 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1736 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1738 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1739 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1741 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1742 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1744 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1747 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1749 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1752 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1754 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1757 // Repair recursive urls
1758 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1759 "#$2", $item["body"]);
1762 foreach($tags as $tag) {
1763 if(strpos($tag,'#') !== 0)
1766 if(strpos($tag,'[url='))
1769 $basetag = str_replace('_',' ',substr($tag,1));
1771 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1773 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1775 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1776 if(strlen($item["tag"]))
1777 $item["tag"] = ','.$item["tag"];
1778 $item["tag"] = $newtag.$item["tag"];
1782 // Convert back the masked hashtags
1783 $item["body"] = str_replace("#", "#", $item["body"]);
1786 function get_item_guid($id) {
1787 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1789 return($r[0]["guid"]);
1794 function get_item_id($guid, $uid = 0) {
1800 $uid == local_user();
1802 // Does the given user have this item?
1804 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1805 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1806 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1809 $nick = $r[0]["nickname"];
1813 // Or is it anywhere on the server?
1815 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1816 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1817 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1818 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1819 AND `item`.`private` = 0 AND `item`.`wall` = 1
1820 AND `item`.`guid` = '%s'", dbesc($guid));
1823 $nick = $r[0]["nickname"];
1826 return(array("nick" => $nick, "id" => $id));
1830 function get_item_contact($item,$contacts) {
1831 if(! count($contacts) || (! is_array($item)))
1833 foreach($contacts as $contact) {
1834 if($contact['id'] == $item['contact-id']) {
1836 break; // NOTREACHED
1843 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1845 * @param int $item_id
1846 * @return bool true if item was deleted, else false
1848 function tag_deliver($uid,$item_id) {
1856 $u = q("select * from user where uid = %d limit 1",
1862 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1863 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1866 $i = q("select * from item where id = %d and uid = %d limit 1",
1875 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1877 // Diaspora uses their own hardwired link URL in @-tags
1878 // instead of the one we supply with webfinger
1880 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1882 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1884 foreach($matches as $mtch) {
1885 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1887 logger('tag_deliver: mention found: ' . $mtch[2]);
1893 if ( ($community_page || $prvgroup) &&
1894 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1895 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1897 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1898 q("DELETE FROM item WHERE id = %d and uid = %d",
1908 // send a notification
1910 // use a local photo if we have one
1912 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1913 intval($u[0]['uid']),
1914 dbesc(normalise_link($item['author-link']))
1916 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1919 require_once('include/enotify.php');
1921 'type' => NOTIFY_TAGSELF,
1922 'notify_flags' => $u[0]['notify-flags'],
1923 'language' => $u[0]['language'],
1924 'to_name' => $u[0]['username'],
1925 'to_email' => $u[0]['email'],
1926 'uid' => $u[0]['uid'],
1928 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1929 'source_name' => $item['author-name'],
1930 'source_link' => $item['author-link'],
1931 'source_photo' => $photo,
1932 'verb' => ACTIVITY_TAG,
1934 'parent' => $item['parent']
1938 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1940 call_hooks('tagged', $arr);
1942 if((! $community_page) && (! $prvgroup))
1946 // tgroup delivery - setup a second delivery chain
1947 // prevent delivery looping - only proceed
1948 // if the message originated elsewhere and is a top-level post
1950 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1953 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1956 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1957 intval($u[0]['uid'])
1962 // also reset all the privacy bits to the forum default permissions
1964 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1966 $forum_mode = (($prvgroup) ? 2 : 1);
1968 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1969 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1970 intval($forum_mode),
1971 dbesc($c[0]['name']),
1972 dbesc($c[0]['url']),
1973 dbesc($c[0]['thumb']),
1975 dbesc($u[0]['allow_cid']),
1976 dbesc($u[0]['allow_gid']),
1977 dbesc($u[0]['deny_cid']),
1978 dbesc($u[0]['deny_gid']),
1981 update_thread($item_id);
1983 proc_run('php','include/notifier.php','tgroup',$item_id);
1989 function tgroup_check($uid,$item) {
1995 // check that the message originated elsewhere and is a top-level post
1997 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
2001 $u = q("select * from user where uid = %d limit 1",
2007 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
2008 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
2011 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
2013 // Diaspora uses their own hardwired link URL in @-tags
2014 // instead of the one we supply with webfinger
2016 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
2018 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
2020 foreach($matches as $mtch) {
2021 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
2023 logger('tgroup_check: mention found: ' . $mtch[2]);
2031 if((! $community_page) && (! $prvgroup))
2045 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
2049 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
2051 if($contact['duplex'] && $contact['dfrn-id'])
2052 $idtosend = '0:' . $orig_id;
2053 if($contact['duplex'] && $contact['issued-id'])
2054 $idtosend = '1:' . $orig_id;
2057 $rino = get_config('system','rino_encrypt');
2058 $rino = intval($rino);
2059 // use RINO1 if mcrypt isn't installed and RINO2 was selected
2060 if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
2062 logger("Local rino version: ". $rino, LOGGER_DEBUG);
2064 $ssl_val = intval(get_config('system','ssl_policy'));
2068 case SSL_POLICY_FULL:
2069 $ssl_policy = 'full';
2071 case SSL_POLICY_SELFSIGN:
2072 $ssl_policy = 'self';
2074 case SSL_POLICY_NONE:
2076 $ssl_policy = 'none';
2080 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
2082 logger('dfrn_deliver: ' . $url);
2084 $xml = fetch_url($url);
2086 $curl_stat = $a->get_curl_code();
2088 return(-1); // timed out
2090 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2095 if(strpos($xml,'<?xml') === false) {
2096 logger('dfrn_deliver: no valid XML returned');
2097 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2101 $res = parse_xml_string($xml);
2103 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2104 return (($res->status) ? $res->status : 3);
2106 $postvars = array();
2107 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2108 $challenge = hex2bin((string) $res->challenge);
2109 $perm = (($res->perm) ? $res->perm : null);
2110 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2111 $rino_remote_version = intval($res->rino);
2112 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2114 logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG);
2116 if($owner['page-flags'] == PAGE_PRVGROUP)
2119 $final_dfrn_id = '';
2122 if((($perm == 'rw') && (! intval($contact['writable'])))
2123 || (($perm == 'r') && (intval($contact['writable'])))) {
2124 q("update contact set writable = %d where id = %d",
2125 intval(($perm == 'rw') ? 1 : 0),
2126 intval($contact['id'])
2128 $contact['writable'] = (string) 1 - intval($contact['writable']);
2132 if(($contact['duplex'] && strlen($contact['pubkey']))
2133 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2134 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2135 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2136 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2139 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2140 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2143 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2145 if(strpos($final_dfrn_id,':') == 1)
2146 $final_dfrn_id = substr($final_dfrn_id,2);
2148 if($final_dfrn_id != $orig_id) {
2149 logger('dfrn_deliver: wrong dfrn_id.');
2150 // did not decode properly - cannot trust this site
2154 $postvars['dfrn_id'] = $idtosend;
2155 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2157 $postvars['dissolve'] = '1';
2160 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2161 $postvars['data'] = $atom;
2162 $postvars['perm'] = 'rw';
2165 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2166 $postvars['perm'] = 'r';
2169 $postvars['ssl_policy'] = $ssl_policy;
2172 $postvars['page'] = $page;
2175 if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
2176 logger('rino version: '. $rino_remote_version);
2178 switch($rino_remote_version) {
2180 // Deprecated rino version!
2181 $key = substr(random_string(),0,16);
2182 $data = aes_encrypt($postvars['data'],$key);
2185 // RINO 2 based on php-encryption
2187 $key = Crypto::createNewRandomKey();
2188 } catch (CryptoTestFailed $ex) {
2189 logger('Cannot safely create a key');
2191 } catch (CannotPerformOperation $ex) {
2192 logger('Cannot safely create a key');
2196 $data = Crypto::encrypt($postvars['data'], $key);
2197 } catch (CryptoTestFailed $ex) {
2198 logger('Cannot safely perform encryption');
2200 } catch (CannotPerformOperation $ex) {
2201 logger('Cannot safely perform encryption');
2206 logger("rino: invalid requested verision '$rino_remote_version'");
2210 $postvars['rino'] = $rino_remote_version;
2211 $postvars['data'] = bin2hex($data);
2213 #logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2216 if($dfrn_version >= 2.1) {
2217 if(($contact['duplex'] && strlen($contact['pubkey']))
2218 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2219 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2221 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2224 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2228 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2229 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2232 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2236 logger('md5 rawkey ' . md5($postvars['key']));
2238 $postvars['key'] = bin2hex($postvars['key']);
2242 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2244 $xml = post_url($contact['notify'],$postvars);
2246 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2248 $curl_stat = $a->get_curl_code();
2249 if((! $curl_stat) || (! strlen($xml)))
2250 return(-1); // timed out
2252 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2255 if(strpos($xml,'<?xml') === false) {
2256 logger('dfrn_deliver: phase 2: no valid XML returned');
2257 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2261 if($contact['term-date'] != '0000-00-00 00:00:00') {
2262 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2263 require_once('include/Contact.php');
2264 unmark_for_death($contact);
2267 $res = parse_xml_string($xml);
2269 return $res->status;
2274 This function returns true if $update has an edited timestamp newer
2275 than $existing, i.e. $update contains new data which should override
2276 what's already there. If there is no timestamp yet, the update is
2277 assumed to be newer. If the update has no timestamp, the existing
2278 item is assumed to be up-to-date. If the timestamps are equal it
2279 assumes the update has been seen before and should be ignored.
2281 function edited_timestamp_is_newer($existing, $update) {
2282 if (!x($existing,'edited') || !$existing['edited']) {
2285 if (!x($update,'edited') || !$update['edited']) {
2288 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2289 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2290 return (strcmp($existing_edited, $update_edited) < 0);
2295 * consume_feed - process atom feed and update anything/everything we might need to update
2297 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2299 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2300 * It is this person's stuff that is going to be updated.
2301 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2302 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2303 * have a contact record.
2304 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2305 * might not) try and subscribe to it.
2306 * $datedir sorts in reverse order
2307 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2308 * imported prior to its children being seen in the stream unless we are certain
2309 * of how the feed is arranged/ordered.
2310 * With $pass = 1, we only pull parent items out of the stream.
2311 * With $pass = 2, we only pull children (comments/likes).
2313 * So running this twice, first with pass 1 and then with pass 2 will do the right
2314 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2315 * model where comments can have sub-threads. That would require some massive sorting
2316 * to get all the feed items into a mostly linear ordering, and might still require
2320 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2321 if ($contact['network'] === NETWORK_OSTATUS) {
2323 logger("Consume OStatus messages ", LOGGER_DEBUG);
2324 ostatus_import($xml,$importer,$contact, $hub);
2329 if ($contact['network'] === NETWORK_FEED) {
2331 logger("Consume feeds", LOGGER_DEBUG);
2332 feed_import($xml,$importer,$contact, $hub);
2337 require_once('library/simplepie/simplepie.inc');
2338 require_once('include/contact_selectors.php');
2340 if(! strlen($xml)) {
2341 logger('consume_feed: empty input');
2345 $feed = new SimplePie();
2346 $feed->set_raw_data($xml);
2348 $feed->enable_order_by_date(true);
2350 $feed->enable_order_by_date(false);
2354 logger('consume_feed: Error parsing XML: ' . $feed->error());
2356 $permalink = $feed->get_permalink();
2358 // Check at the feed level for updated contact name and/or photo
2362 $photo_timestamp = '';
2365 $contact_updated = '';
2367 $hubs = $feed->get_links('hub');
2368 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2371 $hub = implode(',', $hubs);
2373 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2375 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2377 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2378 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2379 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2380 $new_name = $elems['name'][0]['data'];
2382 // Manually checking for changed contact names
2383 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2384 $name_updated = date("c");
2385 $photo_timestamp = date("c");
2388 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2389 if ($photo_timestamp == "")
2390 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2391 $photo_url = $elems['link'][0]['attribs']['']['href'];
2394 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2395 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2399 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2400 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2402 $contact_updated = $photo_timestamp;
2404 require_once("include/Photo.php");
2405 $photos = import_profile_photo($photo_url,$contact['uid'],$contact['id']);
2407 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2408 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
2409 dbesc(datetime_convert()),
2413 intval($contact['uid']),
2414 intval($contact['id'])
2418 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2419 if ($name_updated > $contact_updated)
2420 $contact_updated = $name_updated;
2422 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
2423 intval($contact['uid']),
2424 intval($contact['id'])
2427 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
2428 dbesc(notags(trim($new_name))),
2429 dbesc(datetime_convert()),
2430 intval($contact['uid']),
2431 intval($contact['id']),
2432 dbesc(notags(trim($new_name)))
2435 // do our best to update the name on content items
2437 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
2438 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
2439 dbesc(notags(trim($new_name))),
2440 dbesc($r[0]['name']),
2441 dbesc($r[0]['url']),
2442 intval($contact['uid']),
2443 dbesc(notags(trim($new_name)))
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['object-type'] === ACTIVITY_OBJ_EVENT) {
2543 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
2544 event_delete($item['event-id']);
2547 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2548 $xo = parse_xml_string($item['object'],false);
2549 $xt = parse_xml_string($item['target'],false);
2550 if($xt->type === ACTIVITY_OBJ_NOTE) {
2551 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2553 intval($importer['importer_uid'])
2557 // For tags, the owner cannot remove the tag on the author's copy of the post.
2559 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2560 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2561 $author_copy = (($item['origin']) ? true : false);
2563 if($owner_remove && $author_copy)
2565 if($author_remove || $owner_remove) {
2566 $tags = explode(',',$i[0]['tag']);
2569 foreach($tags as $tag)
2570 if(trim($tag) !== trim($xo->body))
2571 $newtags[] = trim($tag);
2573 q("update item set tag = '%s' where id = %d",
2574 dbesc(implode(',',$newtags)),
2577 create_tags_from_item($i[0]['id']);
2583 if($item['uri'] == $item['parent-uri']) {
2584 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2585 `body` = '', `title` = ''
2586 WHERE `parent-uri` = '%s' AND `uid` = %d",
2588 dbesc(datetime_convert()),
2589 dbesc($item['uri']),
2590 intval($importer['uid'])
2592 create_tags_from_itemuri($item['uri'], $importer['uid']);
2593 create_files_from_itemuri($item['uri'], $importer['uid']);
2594 update_thread_uri($item['uri'], $importer['uid']);
2597 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2598 `body` = '', `title` = ''
2599 WHERE `uri` = '%s' AND `uid` = %d",
2601 dbesc(datetime_convert()),
2603 intval($importer['uid'])
2605 create_tags_from_itemuri($uri, $importer['uid']);
2606 create_files_from_itemuri($uri, $importer['uid']);
2607 if($item['last-child']) {
2608 // ensure that last-child is set in case the comment that had it just got wiped.
2609 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2610 dbesc(datetime_convert()),
2611 dbesc($item['parent-uri']),
2612 intval($item['uid'])
2614 // who is the last child now?
2615 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2616 ORDER BY `created` DESC LIMIT 1",
2617 dbesc($item['parent-uri']),
2618 intval($importer['uid'])
2621 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2632 // Now process the feed
2634 if($feed->get_item_quantity()) {
2636 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2638 // in inverse date order
2640 $items = array_reverse($feed->get_items());
2642 $items = $feed->get_items();
2645 foreach($items as $item) {
2648 $item_id = $item->get_id();
2649 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2650 if(isset($rawthread[0]['attribs']['']['ref'])) {
2652 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2655 if(($is_reply) && is_array($contact)) {
2660 // not allowed to post
2662 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2666 // Have we seen it? If not, import it.
2668 $item_id = $item->get_id();
2669 $datarray = get_atom_elements($feed, $item, $contact);
2671 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2672 $datarray['author-name'] = $contact['name'];
2673 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2674 $datarray['author-link'] = $contact['url'];
2675 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2676 $datarray['author-avatar'] = $contact['thumb'];
2678 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2679 logger('consume_feed: no author information! ' . print_r($datarray,true));
2683 $force_parent = false;
2684 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2685 if($contact['network'] === NETWORK_OSTATUS)
2686 $force_parent = true;
2687 if(strlen($datarray['title']))
2688 unset($datarray['title']);
2689 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2690 dbesc(datetime_convert()),
2692 intval($importer['uid'])
2694 $datarray['last-child'] = 1;
2695 update_thread_uri($parent_uri, $importer['uid']);
2699 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2701 intval($importer['uid'])
2704 // Update content if 'updated' changes
2707 if (edited_timestamp_is_newer($r[0], $datarray)) {
2709 // do not accept (ignore) an earlier edit than one we currently have.
2710 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2713 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2714 dbesc($datarray['title']),
2715 dbesc($datarray['body']),
2716 dbesc($datarray['tag']),
2717 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2718 dbesc(datetime_convert()),
2720 intval($importer['uid'])
2722 create_tags_from_itemuri($item_id, $importer['uid']);
2723 update_thread_uri($item_id, $importer['uid']);
2726 // update last-child if it changes
2728 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2729 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2730 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2731 dbesc(datetime_convert()),
2733 intval($importer['uid'])
2735 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2736 intval($allow[0]['data']),
2737 dbesc(datetime_convert()),
2739 intval($importer['uid'])
2741 update_thread_uri($item_id, $importer['uid']);
2747 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2748 // one way feed - no remote comment ability
2749 $datarray['last-child'] = 0;
2751 $datarray['parent-uri'] = $parent_uri;
2752 $datarray['uid'] = $importer['uid'];
2753 $datarray['contact-id'] = $contact['id'];
2754 if(($datarray['verb'] === ACTIVITY_LIKE)
2755 || ($datarray['verb'] === ACTIVITY_DISLIKE)
2756 || ($datarray['verb'] === ACTIVITY_ATTEND)
2757 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
2758 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
2759 $datarray['type'] = 'activity';
2760 $datarray['gravity'] = GRAVITY_LIKE;
2761 // only one like or dislike per person
2762 // splitted into two queries for performance issues
2763 $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",
2764 intval($datarray['uid']),
2765 intval($datarray['contact-id']),
2766 dbesc($datarray['verb']),
2772 $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",
2773 intval($datarray['uid']),
2774 intval($datarray['contact-id']),
2775 dbesc($datarray['verb']),
2782 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2783 $xo = parse_xml_string($datarray['object'],false);
2784 $xt = parse_xml_string($datarray['target'],false);
2786 if($xt->type == ACTIVITY_OBJ_NOTE) {
2787 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2789 intval($importer['importer_uid'])
2794 // extract tag, if not duplicate, add to parent item
2795 if($xo->id && $xo->content) {
2796 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2797 if(! (stristr($r[0]['tag'],$newtag))) {
2798 q("UPDATE item SET tag = '%s' WHERE id = %d",
2799 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2802 create_tags_from_item($r[0]['id']);
2808 $r = item_store($datarray,$force_parent);
2814 // Head post of a conversation. Have we seen it? If not, import it.
2816 $item_id = $item->get_id();
2818 $datarray = get_atom_elements($feed, $item, $contact);
2820 if(is_array($contact)) {
2821 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2822 $datarray['author-name'] = $contact['name'];
2823 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2824 $datarray['author-link'] = $contact['url'];
2825 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2826 $datarray['author-avatar'] = $contact['thumb'];
2829 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2830 logger('consume_feed: no author information! ' . print_r($datarray,true));
2834 // special handling for events
2836 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2837 $ev = bbtoevent($datarray['body']);
2838 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
2839 $ev['uid'] = $importer['uid'];
2840 $ev['uri'] = $item_id;
2841 $ev['edited'] = $datarray['edited'];
2842 $ev['private'] = $datarray['private'];
2843 $ev['guid'] = $datarray['guid'];
2845 if(is_array($contact))
2846 $ev['cid'] = $contact['id'];
2847 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2849 intval($importer['uid'])
2852 $ev['id'] = $r[0]['id'];
2853 $xyz = event_store($ev);
2858 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2859 if(strlen($datarray['title']))
2860 unset($datarray['title']);
2861 $datarray['last-child'] = 1;
2865 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2867 intval($importer['uid'])
2870 // Update content if 'updated' changes
2873 if (edited_timestamp_is_newer($r[0], $datarray)) {
2875 // do not accept (ignore) an earlier edit than one we currently have.
2876 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2879 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2880 dbesc($datarray['title']),
2881 dbesc($datarray['body']),
2882 dbesc($datarray['tag']),
2883 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2884 dbesc(datetime_convert()),
2886 intval($importer['uid'])
2888 create_tags_from_itemuri($item_id, $importer['uid']);
2889 update_thread_uri($item_id, $importer['uid']);
2892 // update last-child if it changes
2894 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2895 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2896 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2897 intval($allow[0]['data']),
2898 dbesc(datetime_convert()),
2900 intval($importer['uid'])
2902 update_thread_uri($item_id, $importer['uid']);
2907 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2908 logger('consume-feed: New follower');
2909 new_follower($importer,$contact,$datarray,$item);
2912 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2913 lose_follower($importer,$contact,$datarray,$item);
2917 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2918 logger('consume-feed: New friend request');
2919 new_follower($importer,$contact,$datarray,$item,true);
2922 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2923 lose_sharer($importer,$contact,$datarray,$item);
2928 if(! is_array($contact))
2932 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2933 // one way feed - no remote comment ability
2934 $datarray['last-child'] = 0;
2936 if($contact['network'] === NETWORK_FEED)
2937 $datarray['private'] = 2;
2939 $datarray['parent-uri'] = $item_id;
2940 $datarray['uid'] = $importer['uid'];
2941 $datarray['contact-id'] = $contact['id'];
2943 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2944 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2945 // but otherwise there's a possible data mixup on the sender's system.
2946 // the tgroup delivery code called from item_store will correct it if it's a forum,
2947 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2948 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2949 $datarray['owner-name'] = $contact['name'];
2950 $datarray['owner-link'] = $contact['url'];
2951 $datarray['owner-avatar'] = $contact['thumb'];
2954 // We've allowed "followers" to reach this point so we can decide if they are
2955 // posting an @-tag delivery, which followers are allowed to do for certain
2956 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2958 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2961 // This is my contact on another system, but it's really me.
2962 // Turn this into a wall post.
2963 $notify = item_is_remote_self($contact, $datarray);
2965 $r = item_store($datarray, false, $notify);
2966 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2974 function item_is_remote_self($contact, &$datarray) {
2977 if (!$contact['remote_self'])
2980 // Prevent the forwarding of posts that are forwarded
2981 if ($datarray["extid"] == NETWORK_DFRN)
2984 // Prevent to forward already forwarded posts
2985 if ($datarray["app"] == $a->get_hostname())
2988 // Only forward posts
2989 if ($datarray["verb"] != ACTIVITY_POST)
2992 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2995 $datarray2 = $datarray;
2996 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2997 if ($contact['remote_self'] == 2) {
2998 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2999 intval($contact['uid']));
3001 $datarray['contact-id'] = $r[0]["id"];
3003 $datarray['owner-name'] = $r[0]["name"];
3004 $datarray['owner-link'] = $r[0]["url"];
3005 $datarray['owner-avatar'] = $r[0]["thumb"];
3007 $datarray['author-name'] = $datarray['owner-name'];
3008 $datarray['author-link'] = $datarray['owner-link'];
3009 $datarray['author-avatar'] = $datarray['owner-avatar'];
3012 if ($contact['network'] != NETWORK_FEED) {
3013 $datarray["guid"] = get_guid(32);
3014 unset($datarray["plink"]);
3015 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
3016 $datarray["parent-uri"] = $datarray["uri"];
3017 $datarray["extid"] = $contact['network'];
3018 $urlpart = parse_url($datarray2['author-link']);
3019 $datarray["app"] = $urlpart["host"];
3021 $datarray['private'] = 0;
3024 if ($contact['network'] != NETWORK_FEED) {
3025 // Store the original post
3026 $r = item_store($datarray2, false, false);
3027 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
3029 $datarray["app"] = "Feed";
3034 function local_delivery($importer,$data) {
3037 logger(__function__, LOGGER_TRACE);
3039 if($importer['readonly']) {
3040 // We aren't receiving stuff from this person. But we will quietly ignore them
3041 // rather than a blatant "go away" message.
3042 logger('local_delivery: ignoring');
3047 // Consume notification feed. This may differ from consuming a public feed in several ways
3048 // - might contain email or friend suggestions
3049 // - might contain remote followup to our message
3050 // - in which case we need to accept it and then notify other conversants
3051 // - we may need to send various email notifications
3053 $feed = new SimplePie();
3054 $feed->set_raw_data($data);
3055 $feed->enable_order_by_date(false);
3060 logger('local_delivery: Error parsing XML: ' . $feed->error());
3063 // Check at the feed level for updated contact name and/or photo
3067 $photo_timestamp = '';
3069 $contact_updated = '';
3072 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
3074 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
3076 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
3079 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
3080 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
3081 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
3082 $new_name = $elems['name'][0]['data'];
3084 // Manually checking for changed contact names
3085 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
3086 $name_updated = date("c");
3087 $photo_timestamp = date("c");
3090 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
3091 if ($photo_timestamp == "")
3092 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
3093 $photo_url = $elems['link'][0]['attribs']['']['href'];
3097 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3099 $contact_updated = $photo_timestamp;
3101 logger('local_delivery: Updating photo for ' . $importer['name']);
3102 require_once("include/Photo.php");
3104 $photos = import_profile_photo($photo_url,$importer['importer_uid'],$importer['id']);
3106 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3107 WHERE `uid` = %d AND `id` = %d AND NOT `self`",
3108 dbesc(datetime_convert()),
3112 intval($importer['importer_uid']),
3113 intval($importer['id'])
3117 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3118 if ($name_updated > $contact_updated)
3119 $contact_updated = $name_updated;
3121 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
3122 intval($importer['importer_uid']),
3123 intval($importer['id'])
3126 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
3127 dbesc(notags(trim($new_name))),
3128 dbesc(datetime_convert()),
3129 intval($importer['importer_uid']),
3130 intval($importer['id']),
3131 dbesc(notags(trim($new_name)))
3134 // do our best to update the name on content items
3136 if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
3137 q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
3138 dbesc(notags(trim($new_name))),
3139 dbesc($r[0]['name']),
3140 dbesc($r[0]['url']),
3141 intval($importer['importer_uid']),
3142 dbesc(notags(trim($new_name)))
3147 if ($contact_updated AND $new_name AND $photo_url)
3148 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3150 // Currently unsupported - needs a lot of work
3151 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3152 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3153 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3155 $newloc['uid'] = $importer['importer_uid'];
3156 $newloc['cid'] = $importer['id'];
3157 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3158 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3159 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3160 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3161 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3162 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3163 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3164 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3165 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3166 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3167 /** relocated user must have original key pair */
3168 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3169 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3171 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3174 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3175 intval($importer['id']),
3176 intval($importer['importer_uid']));
3181 $x = q("UPDATE contact SET
3192 `site-pubkey` = '%s'
3193 WHERE id=%d AND uid=%d;",
3194 dbesc($newloc['name']),
3195 dbesc($newloc['photo']),
3196 dbesc($newloc['thumb']),
3197 dbesc($newloc['micro']),
3198 dbesc($newloc['url']),
3199 dbesc(normalise_link($newloc['url'])),
3200 dbesc($newloc['request']),
3201 dbesc($newloc['confirm']),
3202 dbesc($newloc['notify']),
3203 dbesc($newloc['poll']),
3204 dbesc($newloc['sitepubkey']),
3205 intval($importer['id']),
3206 intval($importer['importer_uid']));
3212 'owner-link' => array($old['url'], $newloc['url']),
3213 'author-link' => array($old['url'], $newloc['url']),
3214 'owner-avatar' => array($old['photo'], $newloc['photo']),
3215 'author-avatar' => array($old['photo'], $newloc['photo']),
3217 foreach ($fields as $n=>$f){
3218 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3221 intval($importer['importer_uid']));
3227 // merge with current record, current contents have priority
3228 // update record, set url-updated
3229 // update profile photos
3235 // handle friend suggestion notification
3237 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3238 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3239 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3241 $fsugg['uid'] = $importer['importer_uid'];
3242 $fsugg['cid'] = $importer['id'];
3243 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3244 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3245 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3246 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3247 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3249 // Does our member already have a friend matching this description?
3251 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3252 dbesc($fsugg['name']),
3253 dbesc(normalise_link($fsugg['url'])),
3254 intval($fsugg['uid'])
3259 // Do we already have an fcontact record for this person?
3262 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3263 dbesc($fsugg['url']),
3264 dbesc($fsugg['name']),
3265 dbesc($fsugg['request'])
3270 // OK, we do. Do we already have an introduction for this person ?
3271 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3272 intval($fsugg['uid']),
3279 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3280 dbesc($fsugg['name']),
3281 dbesc($fsugg['url']),
3282 dbesc($fsugg['photo']),
3283 dbesc($fsugg['request'])
3285 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3286 dbesc($fsugg['url']),
3287 dbesc($fsugg['name']),
3288 dbesc($fsugg['request'])
3293 // database record did not get created. Quietly give up.
3298 $hash = random_string();
3300 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3301 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3302 intval($fsugg['uid']),
3304 intval($fsugg['cid']),
3305 dbesc($fsugg['body']),
3307 dbesc(datetime_convert()),
3312 'type' => NOTIFY_SUGGEST,
3313 'notify_flags' => $importer['notify-flags'],
3314 'language' => $importer['language'],
3315 'to_name' => $importer['username'],
3316 'to_email' => $importer['email'],
3317 'uid' => $importer['importer_uid'],
3319 'link' => $a->get_baseurl() . '/notifications/intros',
3320 'source_name' => $importer['name'],
3321 'source_link' => $importer['url'],
3322 'source_photo' => $importer['photo'],
3323 'verb' => ACTIVITY_REQ_FRIEND,
3332 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3333 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3335 logger('local_delivery: private message received');
3338 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3341 $msg['uid'] = $importer['importer_uid'];
3342 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3343 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3344 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3345 $msg['contact-id'] = $importer['id'];
3346 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3347 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3349 $msg['replied'] = 0;
3350 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3351 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3352 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3356 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3357 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3359 // send notifications.
3361 require_once('include/enotify.php');
3363 $notif_params = array(
3364 'type' => NOTIFY_MAIL,
3365 'notify_flags' => $importer['notify-flags'],
3366 'language' => $importer['language'],
3367 'to_name' => $importer['username'],
3368 'to_email' => $importer['email'],
3369 'uid' => $importer['importer_uid'],
3371 'source_name' => $msg['from-name'],
3372 'source_link' => $importer['url'],
3373 'source_photo' => $importer['thumb'],
3374 'verb' => ACTIVITY_POST,
3378 notification($notif_params);
3384 $community_page = 0;
3385 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3387 $community_page = intval($rawtags[0]['data']);
3389 if(intval($importer['forum']) != $community_page) {
3390 q("update contact set forum = %d where id = %d",
3391 intval($community_page),
3392 intval($importer['id'])
3394 $importer['forum'] = (string) $community_page;
3397 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3399 // process any deleted entries
3401 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3402 if(is_array($del_entries) && count($del_entries)) {
3403 foreach($del_entries as $dentry) {
3405 if(isset($dentry['attribs']['']['ref'])) {
3406 $uri = $dentry['attribs']['']['ref'];
3408 if(isset($dentry['attribs']['']['when'])) {
3409 $when = $dentry['attribs']['']['when'];
3410 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3413 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3417 // check for relayed deletes to our conversation
3420 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3422 intval($importer['importer_uid'])
3425 $parent_uri = $r[0]['parent-uri'];
3426 if($r[0]['id'] != $r[0]['parent'])
3433 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3436 logger('local_delivery: possible community delete');
3439 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3441 // was the top-level post for this reply written by somebody on this site?
3442 // Specifically, the recipient?
3444 $is_a_remote_delete = false;
3446 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3447 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3448 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3449 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3450 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3451 AND `item`.`uid` = %d
3457 intval($importer['importer_uid'])
3460 $is_a_remote_delete = true;
3462 // Does this have the characteristics of a community or private group comment?
3463 // If it's a reply to a wall post on a community/prvgroup page it's a
3464 // valid community comment. Also forum_mode makes it valid for sure.
3465 // If neither, it's not.
3467 if($is_a_remote_delete && $community) {
3468 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3469 $is_a_remote_delete = false;
3470 logger('local_delivery: not a community delete');
3474 if($is_a_remote_delete) {
3475 logger('local_delivery: received remote delete');
3479 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3480 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3482 intval($importer['importer_uid']),
3483 intval($importer['id'])
3489 if($item['deleted'])
3492 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3494 if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
3495 logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
3496 event_delete($item['event-id']);
3499 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3500 $xo = parse_xml_string($item['object'],false);
3501 $xt = parse_xml_string($item['target'],false);
3503 if($xt->type === ACTIVITY_OBJ_NOTE) {
3504 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3506 intval($importer['importer_uid'])
3510 // For tags, the owner cannot remove the tag on the author's copy of the post.
3512 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3513 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3514 $author_copy = (($item['origin']) ? true : false);
3516 if($owner_remove && $author_copy)
3518 if($author_remove || $owner_remove) {
3519 $tags = explode(',',$i[0]['tag']);
3522 foreach($tags as $tag)
3523 if(trim($tag) !== trim($xo->body))
3524 $newtags[] = trim($tag);
3526 q("update item set tag = '%s' where id = %d",
3527 dbesc(implode(',',$newtags)),
3530 create_tags_from_item($i[0]['id']);
3536 if($item['uri'] == $item['parent-uri']) {
3537 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3538 `body` = '', `title` = ''
3539 WHERE `parent-uri` = '%s' AND `uid` = %d",
3541 dbesc(datetime_convert()),
3542 dbesc($item['uri']),
3543 intval($importer['importer_uid'])
3545 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3546 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3547 update_thread_uri($item['uri'], $importer['importer_uid']);
3550 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3551 `body` = '', `title` = ''
3552 WHERE `uri` = '%s' AND `uid` = %d",
3554 dbesc(datetime_convert()),
3556 intval($importer['importer_uid'])
3558 create_tags_from_itemuri($uri, $importer['importer_uid']);
3559 create_files_from_itemuri($uri, $importer['importer_uid']);
3560 update_thread_uri($uri, $importer['importer_uid']);
3561 if($item['last-child']) {
3562 // ensure that last-child is set in case the comment that had it just got wiped.
3563 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3564 dbesc(datetime_convert()),
3565 dbesc($item['parent-uri']),
3566 intval($item['uid'])
3568 // who is the last child now?
3569 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3570 ORDER BY `created` DESC LIMIT 1",
3571 dbesc($item['parent-uri']),
3572 intval($importer['importer_uid'])
3575 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3580 // if this is a relayed delete, propagate it to other recipients
3582 if($is_a_remote_delete)
3583 proc_run('php',"include/notifier.php","drop",$item['id']);
3591 foreach($feed->get_items() as $item) {
3594 $item_id = $item->get_id();
3595 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3596 if(isset($rawthread[0]['attribs']['']['ref'])) {
3598 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3604 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3607 logger('local_delivery: possible community reply');
3610 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3612 // was the top-level post for this reply written by somebody on this site?
3613 // Specifically, the recipient?
3615 $is_a_remote_comment = false;
3616 $top_uri = $parent_uri;
3618 $r = q("select `item`.`parent-uri` from `item`
3619 WHERE `item`.`uri` = '%s'
3623 if($r && count($r)) {
3624 $top_uri = $r[0]['parent-uri'];
3626 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3627 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3628 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3629 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3630 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3631 AND `item`.`uid` = %d
3637 intval($importer['importer_uid'])
3640 $is_a_remote_comment = true;
3643 // Does this have the characteristics of a community or private group comment?
3644 // If it's a reply to a wall post on a community/prvgroup page it's a
3645 // valid community comment. Also forum_mode makes it valid for sure.
3646 // If neither, it's not.
3648 if($is_a_remote_comment && $community) {
3649 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3650 $is_a_remote_comment = false;
3651 logger('local_delivery: not a community reply');
3655 if($is_a_remote_comment) {
3656 logger('local_delivery: received remote comment');
3658 // remote reply to our post. Import and then notify everybody else.
3660 $datarray = get_atom_elements($feed, $item);
3662 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3664 intval($importer['importer_uid'])
3667 // Update content if 'updated' changes
3671 if (edited_timestamp_is_newer($r[0], $datarray)) {
3673 // do not accept (ignore) an earlier edit than one we currently have.
3674 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3677 logger('received updated comment' , LOGGER_DEBUG);
3678 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3679 dbesc($datarray['title']),
3680 dbesc($datarray['body']),
3681 dbesc($datarray['tag']),
3682 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3683 dbesc(datetime_convert()),
3685 intval($importer['importer_uid'])
3687 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3689 proc_run('php',"include/notifier.php","comment-import",$iid);
3698 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3699 intval($importer['importer_uid'])
3703 $datarray['type'] = 'remote-comment';
3704 $datarray['wall'] = 1;
3705 $datarray['parent-uri'] = $parent_uri;
3706 $datarray['uid'] = $importer['importer_uid'];
3707 $datarray['owner-name'] = $own[0]['name'];
3708 $datarray['owner-link'] = $own[0]['url'];
3709 $datarray['owner-avatar'] = $own[0]['thumb'];
3710 $datarray['contact-id'] = $importer['id'];
3712 if(($datarray['verb'] === ACTIVITY_LIKE)
3713 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3714 || ($datarray['verb'] === ACTIVITY_ATTEND)
3715 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3716 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3718 $datarray['type'] = 'activity';
3719 $datarray['gravity'] = GRAVITY_LIKE;
3720 $datarray['last-child'] = 0;
3721 // only one like or dislike per person
3722 // splitted into two queries for performance issues
3723 $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",
3724 intval($datarray['uid']),
3725 intval($datarray['contact-id']),
3726 dbesc($datarray['verb']),
3727 dbesc($datarray['parent-uri'])
3733 $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",
3734 intval($datarray['uid']),
3735 intval($datarray['contact-id']),
3736 dbesc($datarray['verb']),
3737 dbesc($datarray['parent-uri'])
3744 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3746 $xo = parse_xml_string($datarray['object'],false);
3747 $xt = parse_xml_string($datarray['target'],false);
3749 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3751 // fetch the parent item
3753 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3755 intval($importer['importer_uid'])
3760 // extract tag, if not duplicate, and this user allows tags, add to parent item
3762 if($xo->id && $xo->content) {
3763 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3764 if(! (stristr($tagp[0]['tag'],$newtag))) {
3765 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3766 intval($importer['importer_uid'])
3768 if(count($i) && ! intval($i[0]['blocktags'])) {
3769 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3770 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3771 intval($tagp[0]['id']),
3772 dbesc(datetime_convert()),
3773 dbesc(datetime_convert())
3775 create_tags_from_item($tagp[0]['id']);
3783 $posted_id = item_store($datarray);
3788 $datarray["id"] = $posted_id;
3790 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3792 intval($importer['importer_uid'])
3795 $parent = $r[0]['parent'];
3796 $parent_uri = $r[0]['parent-uri'];
3800 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3801 dbesc(datetime_convert()),
3802 intval($importer['importer_uid']),
3803 intval($r[0]['parent'])
3806 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3807 dbesc(datetime_convert()),
3808 intval($importer['importer_uid']),
3813 if($posted_id && $parent) {
3815 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3817 if((! $is_like) && (! $importer['self'])) {
3819 require_once('include/enotify.php');
3822 'type' => NOTIFY_COMMENT,
3823 'notify_flags' => $importer['notify-flags'],
3824 'language' => $importer['language'],
3825 'to_name' => $importer['username'],
3826 'to_email' => $importer['email'],
3827 'uid' => $importer['importer_uid'],
3828 'item' => $datarray,
3829 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3830 'source_name' => stripslashes($datarray['author-name']),
3831 'source_link' => $datarray['author-link'],
3832 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3833 ? $importer['thumb'] : $datarray['author-avatar']),
3834 'verb' => ACTIVITY_POST,
3836 'parent' => $parent,
3837 'parent_uri' => $parent_uri,
3849 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3851 $item_id = $item->get_id();
3852 $datarray = get_atom_elements($feed,$item);
3854 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3857 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3859 intval($importer['importer_uid'])
3862 // Update content if 'updated' changes
3865 if (edited_timestamp_is_newer($r[0], $datarray)) {
3867 // do not accept (ignore) an earlier edit than one we currently have.
3868 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3871 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3872 dbesc($datarray['title']),
3873 dbesc($datarray['body']),
3874 dbesc($datarray['tag']),
3875 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3876 dbesc(datetime_convert()),
3878 intval($importer['importer_uid'])
3880 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3883 // update last-child if it changes
3885 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3886 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3887 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3888 dbesc(datetime_convert()),
3890 intval($importer['importer_uid'])
3892 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3893 intval($allow[0]['data']),
3894 dbesc(datetime_convert()),
3896 intval($importer['importer_uid'])
3902 $datarray['parent-uri'] = $parent_uri;
3903 $datarray['uid'] = $importer['importer_uid'];
3904 $datarray['contact-id'] = $importer['id'];
3905 if(($datarray['verb'] === ACTIVITY_LIKE)
3906 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3907 || ($datarray['verb'] === ACTIVITY_ATTEND)
3908 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3909 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3910 $datarray['type'] = 'activity';
3911 $datarray['gravity'] = GRAVITY_LIKE;
3912 // only one like or dislike per person
3913 // splitted into two queries for performance issues
3914 $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",
3915 intval($datarray['uid']),
3916 intval($datarray['contact-id']),
3917 dbesc($datarray['verb']),
3923 $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",
3924 intval($datarray['uid']),
3925 intval($datarray['contact-id']),
3926 dbesc($datarray['verb']),
3934 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3936 $xo = parse_xml_string($datarray['object'],false);
3937 $xt = parse_xml_string($datarray['target'],false);
3939 if($xt->type == ACTIVITY_OBJ_NOTE) {
3940 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3942 intval($importer['importer_uid'])
3947 // extract tag, if not duplicate, add to parent item
3949 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3950 q("UPDATE item SET tag = '%s' WHERE id = %d",
3951 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3954 create_tags_from_item($r[0]['id']);
3960 $posted_id = item_store($datarray);
3962 // find out if our user is involved in this conversation and wants to be notified.
3964 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3966 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
3968 intval($importer['importer_uid'])
3971 if(count($myconv)) {
3972 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
3974 // first make sure this isn't our own post coming back to us from a wall-to-wall event
3975 if(! link_compare($datarray['author-link'],$importer_url)) {
3978 foreach($myconv as $conv) {
3980 // now if we find a match, it means we're in this conversation
3982 if(! link_compare($conv['author-link'],$importer_url))
3985 require_once('include/enotify.php');
3987 $conv_parent = $conv['parent'];
3990 'type' => NOTIFY_COMMENT,
3991 'notify_flags' => $importer['notify-flags'],
3992 'language' => $importer['language'],
3993 'to_name' => $importer['username'],
3994 'to_email' => $importer['email'],
3995 'uid' => $importer['importer_uid'],
3996 'item' => $datarray,
3997 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3998 'source_name' => stripslashes($datarray['author-name']),
3999 'source_link' => $datarray['author-link'],
4000 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4001 ? $importer['thumb'] : $datarray['author-avatar']),
4002 'verb' => ACTIVITY_POST,
4004 'parent' => $conv_parent,
4005 'parent_uri' => $parent_uri
4009 // only send one notification
4021 // Head post of a conversation. Have we seen it? If not, import it.
4024 $item_id = $item->get_id();
4025 $datarray = get_atom_elements($feed,$item);
4027 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
4028 $ev = bbtoevent($datarray['body']);
4029 if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
4030 $ev['cid'] = $importer['id'];
4031 $ev['uid'] = $importer['uid'];
4032 $ev['uri'] = $item_id;
4033 $ev['edited'] = $datarray['edited'];
4034 $ev['private'] = $datarray['private'];
4035 $ev['guid'] = $datarray['guid'];
4037 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4039 intval($importer['uid'])
4042 $ev['id'] = $r[0]['id'];
4043 $xyz = event_store($ev);
4048 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4050 intval($importer['importer_uid'])
4053 // Update content if 'updated' changes
4056 if (edited_timestamp_is_newer($r[0], $datarray)) {
4058 // do not accept (ignore) an earlier edit than one we currently have.
4059 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4062 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4063 dbesc($datarray['title']),
4064 dbesc($datarray['body']),
4065 dbesc($datarray['tag']),
4066 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4067 dbesc(datetime_convert()),
4069 intval($importer['importer_uid'])
4071 create_tags_from_itemuri($item_id, $importer['importer_uid']);
4072 update_thread_uri($item_id, $importer['importer_uid']);
4075 // update last-child if it changes
4077 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4078 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4079 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4080 intval($allow[0]['data']),
4081 dbesc(datetime_convert()),
4083 intval($importer['importer_uid'])
4089 $datarray['parent-uri'] = $item_id;
4090 $datarray['uid'] = $importer['importer_uid'];
4091 $datarray['contact-id'] = $importer['id'];
4094 if(! link_compare($datarray['owner-link'],$importer['url'])) {
4095 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4096 // but otherwise there's a possible data mixup on the sender's system.
4097 // the tgroup delivery code called from item_store will correct it if it's a forum,
4098 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4099 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4100 $datarray['owner-name'] = $importer['senderName'];
4101 $datarray['owner-link'] = $importer['url'];
4102 $datarray['owner-avatar'] = $importer['thumb'];
4105 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4108 // This is my contact on another system, but it's really me.
4109 // Turn this into a wall post.
4110 $notify = item_is_remote_self($importer, $datarray);
4112 $posted_id = item_store($datarray, false, $notify);
4114 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4115 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4118 $xo = parse_xml_string($datarray['object'],false);
4120 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4122 // somebody was poked/prodded. Was it me?
4124 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4126 foreach($links->link as $l) {
4127 $atts = $l->attributes();
4128 switch($atts['rel']) {
4130 $Blink = $atts['href'];
4136 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4138 // send a notification
4139 require_once('include/enotify.php');
4142 'type' => NOTIFY_POKE,
4143 'notify_flags' => $importer['notify-flags'],
4144 'language' => $importer['language'],
4145 'to_name' => $importer['username'],
4146 'to_email' => $importer['email'],
4147 'uid' => $importer['importer_uid'],
4148 'item' => $datarray,
4149 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4150 'source_name' => stripslashes($datarray['author-name']),
4151 'source_link' => $datarray['author-link'],
4152 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4153 ? $importer['thumb'] : $datarray['author-avatar']),
4154 'verb' => $datarray['verb'],
4155 'otype' => 'person',
4156 'activity' => $verb,
4157 'parent' => $datarray['parent']
4173 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4174 $url = notags(trim($datarray['author-link']));
4175 $name = notags(trim($datarray['author-name']));
4176 $photo = notags(trim($datarray['author-avatar']));
4178 if (is_object($item)) {
4179 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4180 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4181 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4185 if(is_array($contact)) {
4186 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4187 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4188 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4189 intval(CONTACT_IS_FRIEND),
4190 intval($contact['id']),
4191 intval($importer['uid'])
4194 // send email notification to owner?
4197 // create contact record
4199 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4200 `blocked`, `readonly`, `pending`, `writable`)
4201 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
4202 intval($importer['uid']),
4203 dbesc(datetime_convert()),
4205 dbesc(normalise_link($url)),
4209 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4210 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4212 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4213 intval($importer['uid']),
4217 $contact_record = $r[0];
4219 $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
4221 q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `id` = %d",
4225 intval($contact_record["id"])
4230 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4231 intval($importer['uid'])
4234 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
4236 // create notification
4237 $hash = random_string();
4239 if(is_array($contact_record)) {
4240 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4241 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4242 intval($importer['uid']),
4243 intval($contact_record['id']),
4245 dbesc(datetime_convert())
4249 if(intval($r[0]['def_gid'])) {
4250 require_once('include/group.php');
4251 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4254 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4255 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
4258 'type' => NOTIFY_INTRO,
4259 'notify_flags' => $r[0]['notify-flags'],
4260 'language' => $r[0]['language'],
4261 'to_name' => $r[0]['username'],
4262 'to_email' => $r[0]['email'],
4263 'uid' => $r[0]['uid'],
4264 'link' => $a->get_baseurl() . '/notifications/intro',
4265 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4266 'source_link' => $contact_record['url'],
4267 'source_photo' => $contact_record['photo'],
4268 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4273 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
4274 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
4275 intval($importer['uid']),
4283 function lose_follower($importer,$contact,$datarray,$item) {
4285 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4286 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4287 intval(CONTACT_IS_SHARING),
4288 intval($contact['id'])
4292 contact_remove($contact['id']);
4296 function lose_sharer($importer,$contact,$datarray,$item) {
4298 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4299 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4300 intval(CONTACT_IS_FOLLOWER),
4301 intval($contact['id'])
4305 contact_remove($contact['id']);
4310 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4314 if(is_array($importer)) {
4315 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4316 intval($importer['uid'])
4320 // Diaspora has different message-ids in feeds than they do
4321 // through the direct Diaspora protocol. If we try and use
4322 // the feed, we'll get duplicates. So don't.
4324 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4327 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4329 // Use a single verify token, even if multiple hubs
4331 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4333 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4335 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4337 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
4338 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4339 dbesc($verify_token),
4340 intval($contact['id'])
4344 post_url($url,$params);
4346 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4353 function atom_author($tag,$name,$uri,$h,$w,$photo,$geo) {
4357 $name = xmlify($name);
4358 $uri = xmlify($uri);
4361 $photo = xmlify($photo);
4365 $o .= "\t<name>$name</name>\r\n";
4366 $o .= "\t<uri>$uri</uri>\r\n";
4367 $o .= "\t".'<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4368 $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4370 if ($tag == "author") {
4373 $o .= '<georss:point>'.xmlify($geo).'</georss:point>'."\r\n";
4375 $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
4376 `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
4377 `profile`.`homepage`,`contact`.`nick` FROM `profile`
4378 INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
4379 INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
4380 WHERE `profile`.`is-default` AND `contact`.`self` AND
4381 NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
4382 dbesc(normalise_link($uri)));
4385 if($r[0]['locality'])
4386 $location .= $r[0]['locality'];
4387 if($r[0]['region']) {
4390 $location .= $r[0]['region'];
4392 if($r[0]['country-name']) {
4395 $location .= $r[0]['country-name'];
4398 $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
4399 $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
4400 $o .= "\t<poco:note>".xmlify($r[0]["about"])."</poco:note>\r\n";
4401 $o .= "\t<poco:address>\r\n";
4402 $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
4403 $o .= "\t</poco:address>\r\n";
4404 $o .= "\t<poco:urls>\r\n";
4405 $o .= "\t<poco:type>homepage</poco:type>\r\n";
4406 $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
4407 $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
4408 $o .= "\t</poco:urls>\r\n";
4412 call_hooks('atom_author', $o);
4414 $o .= "</$tag>\r\n";
4418 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4422 if(! $item['parent'])
4425 if($item['deleted'])
4426 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4429 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4430 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4432 $body = $item['body'];
4435 $o = "\r\n\r\n<entry>\r\n";
4437 if(is_array($author))
4438 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb'], $item['coord']);
4440 $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']), $item['coord']);
4441 if(strlen($item['owner-name']))
4442 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar'], $item['coord']);
4444 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4445 $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
4446 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4447 $o .= '<thr:in-reply-to ref="'.xmlify($parent_item).'" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$parent[0]['guid']).'" />'."\r\n";
4452 if ($item['title'] != "")
4453 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4455 $htmlbody = bbcode($htmlbody, false, false, 7);
4457 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4458 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4459 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4460 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4461 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4462 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4463 $o .= '<link rel="alternate" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$item['guid']).'" />'."\r\n";
4465 $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
4468 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4470 if($item['location']) {
4471 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4472 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4476 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4478 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4479 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4482 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4483 if($item['bookmark'])
4484 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4487 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4490 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4492 if($item['signed_text']) {
4493 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4494 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4497 $verb = construct_verb($item);
4498 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4499 $actobj = construct_activity_object($item);
4502 $actarg = construct_activity_target($item);
4506 $tags = item_getfeedtags($item);
4508 foreach($tags as $t)
4509 if (($type != 'html') OR ($t[0] != "@"))
4510 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4514 // To support these elements, the API needs to be enhanced
4515 //$o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
4516 //$o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4517 //$o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4519 // Deactivated since it was meant only for OStatus
4520 //$o .= item_get_attachment($item);
4522 $o .= item_getfeedattach($item);
4524 $mentioned = get_mentions($item);
4528 call_hooks('atom_entry', $o);
4530 $o .= '</entry>' . "\r\n";
4535 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4537 if(get_config('system','disable_embedded'))
4542 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4543 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4548 $img_start = strpos($orig_body, '[img');
4549 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4550 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4551 while( ($img_st_close !== false) && ($img_len !== false) ) {
4553 $img_st_close++; // make it point to AFTER the closing bracket
4554 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4556 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4559 if(stristr($image , $site . '/photo/')) {
4560 // Only embed locally hosted photos
4562 $i = basename($image);
4563 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4564 $x = strpos($i,'-');
4567 $res = substr($i,$x+1);
4568 $i = substr($i,0,$x);
4569 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4576 // Check to see if we should replace this photo link with an embedded image
4577 // 1. No need to do so if the photo is public
4578 // 2. If there's a contact-id provided, see if they're in the access list
4579 // for the photo. If so, embed it.
4580 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4581 // permissions, regardless of order but first check to see if they're an exact
4582 // match to save some processing overhead.
4584 if(has_permissions($r[0])) {
4586 $recips = enumerate_permissions($r[0]);
4587 if(in_array($cid, $recips)) {
4592 if(compare_permissions($item,$r[0]))
4597 $data = $r[0]['data'];
4598 $type = $r[0]['type'];
4600 // If a custom width and height were specified, apply before embedding
4601 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4602 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4604 $width = intval($match[1]);
4605 $height = intval($match[2]);
4607 $ph = new Photo($data, $type);
4608 if($ph->is_valid()) {
4609 $ph->scaleImage(max($width, $height));
4610 $data = $ph->imageString();
4611 $type = $ph->getType();
4615 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4616 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4617 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4623 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4624 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4625 if($orig_body === false)
4628 $img_start = strpos($orig_body, '[img');
4629 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4630 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4633 $new_body = $new_body . $orig_body;
4639 function has_permissions($obj) {
4640 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4645 function compare_permissions($obj1,$obj2) {
4646 // first part is easy. Check that these are exactly the same.
4647 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4648 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4649 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4650 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4653 // This is harder. Parse all the permissions and compare the resulting set.
4655 $recipients1 = enumerate_permissions($obj1);
4656 $recipients2 = enumerate_permissions($obj2);
4659 if($recipients1 == $recipients2)
4664 // returns an array of contact-ids that are allowed to see this object
4666 function enumerate_permissions($obj) {
4667 require_once('include/group.php');
4668 $allow_people = expand_acl($obj['allow_cid']);
4669 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4670 $deny_people = expand_acl($obj['deny_cid']);
4671 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4672 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4673 $deny = array_unique(array_merge($deny_people,$deny_groups));
4674 $recipients = array_diff($recipients,$deny);
4678 function item_getfeedtags($item) {
4681 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4683 for($x = 0; $x < $cnt; $x ++) {
4685 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
4689 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4691 for($x = 0; $x < $cnt; $x ++) {
4693 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4699 function item_get_attachment($item) {
4701 $siteinfo = get_attached_data($item["body"]);
4703 switch($siteinfo["type"]) {
4705 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4708 $imgdata = get_photo_info($siteinfo["image"]);
4709 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
4712 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4721 function item_getfeedattach($item) {
4723 $arr = explode('[/attach],',$item['attach']);
4725 foreach($arr as $r) {
4727 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4729 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4730 if(intval($matches[2]))
4731 $ret .= 'length="' . intval($matches[2]) . '" ';
4732 if($matches[4] !== ' ')
4733 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4734 $ret .= ' />' . "\r\n";
4743 function item_expire($uid, $days, $network = "", $force = false) {
4745 if((! $uid) || ($days < 1))
4748 // $expire_network_only = save your own wall posts
4749 // and just expire conversations started by others
4751 $expire_network_only = get_pconfig($uid,'expire','network_only');
4752 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4754 if ($network != "") {
4755 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4756 // There is an index "uid_network_received" but not "uid_network_created"
4757 // This avoids the creation of another index just for one purpose.
4758 // And it doesn't really matter wether to look at "received" or "created"
4759 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4761 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4763 $r = q("SELECT * FROM `item`
4764 WHERE `uid` = %d $range
4775 $expire_items = get_pconfig($uid, 'expire','items');
4776 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4778 // Forcing expiring of items - but not notes and marked items
4780 $expire_items = true;
4782 $expire_notes = get_pconfig($uid, 'expire','notes');
4783 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4785 $expire_starred = get_pconfig($uid, 'expire','starred');
4786 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4788 $expire_photos = get_pconfig($uid, 'expire','photos');
4789 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4791 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4793 foreach($r as $item) {
4795 // don't expire filed items
4797 if(strpos($item['file'],'[') !== false)
4800 // Only expire posts, not photos and photo comments
4802 if($expire_photos==0 && strlen($item['resource-id']))
4804 if($expire_starred==0 && intval($item['starred']))
4806 if($expire_notes==0 && $item['type']=='note')
4808 if($expire_items==0 && $item['type']!='note')
4811 drop_item($item['id'],false);
4814 proc_run('php',"include/notifier.php","expire","$uid");
4819 function drop_items($items) {
4822 if(! local_user() && ! remote_user())
4826 foreach($items as $item) {
4827 $owner = drop_item($item,false);
4828 if($owner && ! $uid)
4833 // multiple threads may have been deleted, send an expire notification
4836 proc_run('php',"include/notifier.php","expire","$uid");
4840 function drop_item($id,$interactive = true) {
4844 // locate item to be deleted
4846 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4853 notice( t('Item not found.') . EOL);
4854 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4859 $owner = $item['uid'];
4863 // check if logged in user is either the author or owner of this item
4865 if(is_array($_SESSION['remote'])) {
4866 foreach($_SESSION['remote'] as $visitor) {
4867 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4868 $cid = $visitor['cid'];
4875 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4877 // Check if we should do HTML-based delete confirmation
4878 if($_REQUEST['confirm']) {
4879 // <form> can't take arguments in its "action" parameter
4880 // so add any arguments as hidden inputs
4881 $query = explode_querystring($a->query_string);
4883 foreach($query['args'] as $arg) {
4884 if(strpos($arg, 'confirm=') === false) {
4885 $arg_parts = explode('=', $arg);
4886 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4890 return replace_macros(get_markup_template('confirm.tpl'), array(
4892 '$message' => t('Do you really want to delete this item?'),
4893 '$extra_inputs' => $inputs,
4894 '$confirm' => t('Yes'),
4895 '$confirm_url' => $query['base'],
4896 '$confirm_name' => 'confirmed',
4897 '$cancel' => t('Cancel'),
4900 // Now check how the user responded to the confirmation query
4901 if($_REQUEST['canceled']) {
4902 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4905 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4908 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4909 dbesc(datetime_convert()),
4910 dbesc(datetime_convert()),
4913 create_tags_from_item($item['id']);
4914 create_files_from_item($item['id']);
4915 delete_thread($item['id'], $item['parent-uri']);
4917 // clean up categories and tags so they don't end up as orphans
4920 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4922 foreach($matches as $mtch) {
4923 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4929 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4931 foreach($matches as $mtch) {
4932 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4936 // If item is a link to a photo resource, nuke all the associated photos
4937 // (visitors will not have photo resources)
4938 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4939 // generate a resource-id and therefore aren't intimately linked to the item.
4941 if(strlen($item['resource-id'])) {
4942 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4943 dbesc($item['resource-id']),
4944 intval($item['uid'])
4946 // ignore the result
4949 // If item is a link to an event, nuke the event record.
4951 if(intval($item['event-id'])) {
4952 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4953 intval($item['event-id']),
4954 intval($item['uid'])
4956 // ignore the result
4959 // If item has attachments, drop them
4961 foreach(explode(",",$item['attach']) as $attach){
4962 preg_match("|attach/(\d+)|", $attach, $matches);
4963 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4964 intval($matches[1]),
4967 // ignore the result
4971 // clean up item_id and sign meta-data tables
4974 // Old code - caused very long queries and warning entries in the mysql logfiles:
4976 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4977 intval($item['id']),
4978 intval($item['uid'])
4981 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4982 intval($item['id']),
4983 intval($item['uid'])
4987 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
4989 // Creating list of parents
4990 $r = q("select id from item where parent = %d and uid = %d",
4991 intval($item['id']),
4992 intval($item['uid'])
4997 foreach ($r AS $row) {
4998 if ($parentid != "")
5001 $parentid .= $row["id"];
5005 if ($parentid != "") {
5006 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
5008 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
5011 // If it's the parent of a comment thread, kill all the kids
5013 if($item['uri'] == $item['parent-uri']) {
5014 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
5015 WHERE `parent-uri` = '%s' AND `uid` = %d ",
5016 dbesc(datetime_convert()),
5017 dbesc(datetime_convert()),
5018 dbesc($item['parent-uri']),
5019 intval($item['uid'])
5021 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
5022 create_files_from_itemuri($item['parent-uri'], $item['uid']);
5023 delete_thread_uri($item['parent-uri'], $item['uid']);
5024 // ignore the result
5027 // ensure that last-child is set in case the comment that had it just got wiped.
5028 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
5029 dbesc(datetime_convert()),
5030 dbesc($item['parent-uri']),
5031 intval($item['uid'])
5033 // who is the last child now?
5034 $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",
5035 dbesc($item['parent-uri']),
5036 intval($item['uid'])
5039 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
5044 // Add a relayable_retraction signature for Diaspora.
5045 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
5048 $drop_id = intval($item['id']);
5050 // send the notification upstream/downstream as the case may be
5052 proc_run('php',"include/notifier.php","drop","$drop_id");
5056 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5062 notice( t('Permission denied.') . EOL);
5063 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5070 function first_post_date($uid,$wall = false) {
5071 $r = q("select id, created from item
5072 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
5074 order by created asc limit 1",
5076 intval($wall ? 1 : 0)
5079 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
5080 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
5085 /* modified posted_dates() {below} to arrange the list in years */
5086 function list_post_dates($uid, $wall) {
5087 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5089 $dthen = first_post_date($uid, $wall);
5093 // Set the start and end date to the beginning of the month
5094 $dnow = substr($dnow,0,8).'01';
5095 $dthen = substr($dthen,0,8).'01';
5099 // Starting with the current month, get the first and last days of every
5100 // month down to and including the month of the first post
5101 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5102 $dyear = intval(substr($dnow,0,4));
5103 $dstart = substr($dnow,0,8) . '01';
5104 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5105 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5106 $end_month = datetime_convert('','',$dend,'Y-m-d');
5107 $str = day_translate(datetime_convert('','',$dnow,'F'));
5109 $ret[$dyear] = array();
5110 $ret[$dyear][] = array($str,$end_month,$start_month);
5111 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5116 function posted_dates($uid,$wall) {
5117 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5119 $dthen = first_post_date($uid,$wall);
5123 // Set the start and end date to the beginning of the month
5124 $dnow = substr($dnow,0,8).'01';
5125 $dthen = substr($dthen,0,8).'01';
5128 // Starting with the current month, get the first and last days of every
5129 // month down to and including the month of the first post
5130 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5131 $dstart = substr($dnow,0,8) . '01';
5132 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5133 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5134 $end_month = datetime_convert('','',$dend,'Y-m-d');
5135 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5136 $ret[] = array($str,$end_month,$start_month);
5137 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5143 function posted_date_widget($url,$uid,$wall) {
5146 if(! feature_enabled($uid,'archives'))
5149 // For former Facebook folks that left because of "timeline"
5151 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5154 $visible_years = get_pconfig($uid,'system','archive_visible_years');
5155 if(! $visible_years)
5158 $ret = list_post_dates($uid,$wall);
5163 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5164 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5166 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5167 '$title' => t('Archives'),
5168 '$size' => $visible_years,
5169 '$cutoff_year' => $cutoff_year,
5170 '$cutoff' => $cutoff,
5173 '$showmore' => t('show more')
5179 function store_diaspora_retract_sig($item, $user, $baseurl) {
5180 // Note that we can't add a target_author_signature
5181 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5182 // the comment, that means we're the home of the post, and Diaspora will only
5183 // check the parent_author_signature of retractions that it doesn't have to relay further
5185 // I don't think this function gets called for an "unlike," but I'll check anyway
5187 $enabled = intval(get_config('system','diaspora_enabled'));
5189 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5193 logger('drop_item: storing diaspora retraction signature');
5195 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5197 if(local_user() == $item['uid']) {
5199 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5200 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5203 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5204 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5207 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5208 // only handles DFRN deletes
5209 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5210 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5211 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5217 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5218 intval($item['id']),
5219 dbesc($signed_text),