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 // If there is no guid then take the same guid that was taken before for the same uri
1200 if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
1201 logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
1202 $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
1203 dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
1206 $arr['guid'] = $r[0]["guid"];
1207 logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
1211 // If there is no guid then take the same guid that was taken before for the same plink
1212 if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
1213 logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
1214 $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
1215 dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
1218 $arr['guid'] = $r[0]["guid"];
1219 logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
1221 if ($r[0]["uri"] != $arr['uri'])
1222 logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
1226 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
1227 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
1228 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
1229 // $arr['body'] = strip_tags($arr['body']);
1231 item_add_language_opt($arr);
1236 $guid_prefix = $arr['network'];
1238 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
1239 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
1240 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
1241 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
1242 $arr['author-name'] = ((x($arr,'author-name')) ? notags(trim($arr['author-name'])) : '');
1243 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
1244 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
1245 $arr['owner-name'] = ((x($arr,'owner-name')) ? notags(trim($arr['owner-name'])) : '');
1246 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
1247 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
1248 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
1249 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
1250 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
1251 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
1252 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
1253 $arr['title'] = ((x($arr,'title')) ? notags(trim($arr['title'])) : '');
1254 $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
1255 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
1256 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
1257 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
1258 $arr['deleted'] = 0;
1259 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
1260 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
1261 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
1262 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
1263 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
1264 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
1265 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
1266 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
1267 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
1268 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
1269 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
1270 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
1271 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
1272 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
1273 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
1274 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
1275 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
1276 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
1277 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
1278 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
1279 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
1280 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
1281 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
1282 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
1284 if ($arr['plink'] == "") {
1286 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
1289 if ($arr['network'] == "") {
1290 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1291 intval($arr['contact-id']),
1296 $arr['network'] = $r[0]["network"];
1298 // Fallback to friendica (why is it empty in some cases?)
1299 if ($arr['network'] == "")
1300 $arr['network'] = NETWORK_DFRN;
1302 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1305 if ($arr['guid'] != "") {
1306 // Checking if there is already an item with the same guid
1307 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1308 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1309 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1312 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1317 // Check for hashtags in the body and repair or add hashtag links
1318 item_body_set_hashtags($arr);
1320 $arr['thr-parent'] = $arr['parent-uri'];
1321 if($arr['parent-uri'] === $arr['uri']) {
1323 $parent_deleted = 0;
1324 $allow_cid = $arr['allow_cid'];
1325 $allow_gid = $arr['allow_gid'];
1326 $deny_cid = $arr['deny_cid'];
1327 $deny_gid = $arr['deny_gid'];
1328 $notify_type = 'wall-new';
1332 // find the parent and snarf the item id and ACLs
1333 // and anything else we need to inherit
1335 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1336 dbesc($arr['parent-uri']),
1342 // is the new message multi-level threaded?
1343 // even though we don't support it now, preserve the info
1344 // and re-attach to the conversation parent.
1346 if($r[0]['uri'] != $r[0]['parent-uri']) {
1347 $arr['parent-uri'] = $r[0]['parent-uri'];
1348 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1349 ORDER BY `id` ASC LIMIT 1",
1350 dbesc($r[0]['parent-uri']),
1351 dbesc($r[0]['parent-uri']),
1358 $parent_id = $r[0]['id'];
1359 $parent_deleted = $r[0]['deleted'];
1360 $allow_cid = $r[0]['allow_cid'];
1361 $allow_gid = $r[0]['allow_gid'];
1362 $deny_cid = $r[0]['deny_cid'];
1363 $deny_gid = $r[0]['deny_gid'];
1364 $arr['wall'] = $r[0]['wall'];
1365 $notify_type = 'comment-new';
1367 // if the parent is private, force privacy for the entire conversation
1368 // This differs from the above settings as it subtly allows comments from
1369 // email correspondents to be private even if the overall thread is not.
1371 if($r[0]['private'])
1372 $arr['private'] = $r[0]['private'];
1374 // Edge case. We host a public forum that was originally posted to privately.
1375 // The original author commented, but as this is a comment, the permissions
1376 // weren't fixed up so it will still show the comment as private unless we fix it here.
1378 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1379 $arr['private'] = 0;
1382 // If its a post from myself then tag the thread as "mention"
1383 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1384 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1387 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1388 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1389 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1390 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1391 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1397 // Allow one to see reply tweets from status.net even when
1398 // we don't have or can't see the original post.
1401 logger('item_store: $force_parent=true, reply converted to top-level post.');
1403 $arr['parent-uri'] = $arr['uri'];
1404 $arr['gravity'] = 0;
1407 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1411 $parent_deleted = 0;
1415 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1417 dbesc($arr['network']),
1420 if($r && count($r)) {
1421 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1425 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1426 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1427 dbesc($arr['body']),
1428 dbesc($arr['network']),
1429 dbesc($arr['created']),
1430 intval($arr['contact-id']),
1433 if($r && count($r)) {
1434 logger('duplicated item with the same body found. ' . print_r($arr,true));
1438 // Is this item available in the global items (with uid=0)?
1439 if ($arr["uid"] == 0) {
1440 $arr["global"] = true;
1442 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1444 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1446 $arr["global"] = (count($isglobal) > 0);
1449 // Fill the cache field
1450 put_item_in_cache($arr);
1453 call_hooks('post_local',$arr);
1455 call_hooks('post_remote',$arr);
1457 if(x($arr,'cancel')) {
1458 logger('item_store: post cancelled by plugin.');
1462 // Store the unescaped version
1467 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1469 $r = dbq("INSERT INTO `item` (`"
1470 . implode("`, `", array_keys($arr))
1472 . implode("', '", array_values($arr))
1478 // find the item we just created
1479 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1486 // Store the guid and other relevant data
1489 $current_post = $r[0]['id'];
1490 logger('item_store: created item ' . $current_post);
1492 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1493 // This can be used to filter for inactive contacts.
1494 // Only do this for public postings to avoid privacy problems, since poco data is public.
1495 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1497 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1499 // Is it a forum? Then we don't care about the rules from above
1500 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1501 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1502 intval($arr['contact-id']));
1508 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1509 dbesc($arr['received']),
1510 dbesc($arr['received']),
1511 intval($arr['contact-id'])
1514 logger('item_store: could not locate created item');
1518 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1519 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1521 intval($arr['uid']),
1522 intval($current_post)
1526 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1527 $parent_id = $current_post;
1529 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1532 $private = $arr['private'];
1534 // Set parent id - and also make sure to inherit the parent's ACLs.
1536 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1537 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1544 intval($parent_deleted),
1545 intval($current_post)
1548 $arr['id'] = $current_post;
1549 $arr['parent'] = $parent_id;
1550 $arr['allow_cid'] = $allow_cid;
1551 $arr['allow_gid'] = $allow_gid;
1552 $arr['deny_cid'] = $deny_cid;
1553 $arr['deny_gid'] = $deny_gid;
1554 $arr['private'] = $private;
1555 $arr['deleted'] = $parent_deleted;
1557 // update the commented timestamp on the parent
1558 // Only update "commented" if it is really a comment
1559 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1560 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1561 dbesc(datetime_convert()),
1562 dbesc(datetime_convert()),
1566 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1567 dbesc(datetime_convert()),
1572 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1573 intval($current_post),
1574 dbesc($dsprsig->signed_text),
1575 dbesc($dsprsig->signature),
1576 dbesc($dsprsig->signer)
1582 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1585 if($arr['last-child']) {
1586 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1588 intval($arr['uid']),
1589 intval($current_post)
1593 $deleted = tag_deliver($arr['uid'],$current_post);
1595 // current post can be deleted if is for a community page and no mention are
1597 if (!$deleted AND !$dontcache) {
1599 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1600 if (count($r) == 1) {
1602 call_hooks('post_local_end', $r[0]);
1604 call_hooks('post_remote_end', $r[0]);
1606 logger('item_store: new item not found in DB, id ' . $current_post);
1609 // Add every contact of the post to the global contact table
1612 create_tags_from_item($current_post);
1613 create_files_from_item($current_post);
1615 // Only check for notifications on start posts
1616 if ($arr['parent-uri'] === $arr['uri']) {
1617 add_thread($current_post);
1618 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1620 // Send a notification for every new post?
1621 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1622 intval($arr['contact-id']),
1625 $send_notification = count($r);
1627 if (!$send_notification) {
1628 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1629 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1632 foreach ($tags AS $tag) {
1633 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1634 normalise_link($tag["url"]), intval($arr['uid']));
1636 $send_notification = true;
1641 if ($send_notification) {
1642 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1643 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1644 intval($arr['uid']));
1646 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1647 intval($current_post),
1653 require_once('include/enotify.php');
1655 'type' => NOTIFY_SHARE,
1656 'notify_flags' => $u[0]['notify-flags'],
1657 'language' => $u[0]['language'],
1658 'to_name' => $u[0]['username'],
1659 'to_email' => $u[0]['email'],
1660 'uid' => $u[0]['uid'],
1662 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1663 'source_name' => $item[0]['author-name'],
1664 'source_link' => $item[0]['author-link'],
1665 'source_photo' => $item[0]['author-avatar'],
1666 'verb' => ACTIVITY_TAG,
1668 'parent' => $arr['parent']
1670 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1673 update_thread($parent_id);
1674 add_shadow_entry($arr);
1678 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1680 return $current_post;
1683 function item_body_set_hashtags(&$item) {
1685 $tags = get_tags($item["body"]);
1691 // This sorting is important when there are hashtags that are part of other hashtags
1692 // Otherwise there could be problems with hashtags like #test and #test2
1697 $URLSearchString = "^\[\]";
1699 // All hashtags should point to the home server
1700 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1701 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1703 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1704 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1706 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1707 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1709 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1712 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1714 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1717 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1719 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1722 // Repair recursive urls
1723 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1724 "#$2", $item["body"]);
1727 foreach($tags as $tag) {
1728 if(strpos($tag,'#') !== 0)
1731 if(strpos($tag,'[url='))
1734 $basetag = str_replace('_',' ',substr($tag,1));
1736 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1738 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1740 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1741 if(strlen($item["tag"]))
1742 $item["tag"] = ','.$item["tag"];
1743 $item["tag"] = $newtag.$item["tag"];
1747 // Convert back the masked hashtags
1748 $item["body"] = str_replace("#", "#", $item["body"]);
1751 function get_item_guid($id) {
1752 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1754 return($r[0]["guid"]);
1759 function get_item_id($guid, $uid = 0) {
1765 $uid == local_user();
1767 // Does the given user have this item?
1769 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1770 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1771 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1774 $nick = $r[0]["nickname"];
1778 // Or is it anywhere on the server?
1780 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1781 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1782 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1783 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1784 AND `item`.`private` = 0 AND `item`.`wall` = 1
1785 AND `item`.`guid` = '%s'", dbesc($guid));
1788 $nick = $r[0]["nickname"];
1791 return(array("nick" => $nick, "id" => $id));
1795 function get_item_contact($item,$contacts) {
1796 if(! count($contacts) || (! is_array($item)))
1798 foreach($contacts as $contact) {
1799 if($contact['id'] == $item['contact-id']) {
1801 break; // NOTREACHED
1808 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1810 * @param int $item_id
1811 * @return bool true if item was deleted, else false
1813 function tag_deliver($uid,$item_id) {
1821 $u = q("select * from user where uid = %d limit 1",
1827 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1828 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1831 $i = q("select * from item where id = %d and uid = %d limit 1",
1840 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1842 // Diaspora uses their own hardwired link URL in @-tags
1843 // instead of the one we supply with webfinger
1845 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1847 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1849 foreach($matches as $mtch) {
1850 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1852 logger('tag_deliver: mention found: ' . $mtch[2]);
1858 if ( ($community_page || $prvgroup) &&
1859 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1860 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1862 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1863 q("DELETE FROM item WHERE id = %d and uid = %d",
1873 // send a notification
1875 // use a local photo if we have one
1877 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1878 intval($u[0]['uid']),
1879 dbesc(normalise_link($item['author-link']))
1881 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1884 require_once('include/enotify.php');
1886 'type' => NOTIFY_TAGSELF,
1887 'notify_flags' => $u[0]['notify-flags'],
1888 'language' => $u[0]['language'],
1889 'to_name' => $u[0]['username'],
1890 'to_email' => $u[0]['email'],
1891 'uid' => $u[0]['uid'],
1893 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1894 'source_name' => $item['author-name'],
1895 'source_link' => $item['author-link'],
1896 'source_photo' => $photo,
1897 'verb' => ACTIVITY_TAG,
1899 'parent' => $item['parent']
1903 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1905 call_hooks('tagged', $arr);
1907 if((! $community_page) && (! $prvgroup))
1911 // tgroup delivery - setup a second delivery chain
1912 // prevent delivery looping - only proceed
1913 // if the message originated elsewhere and is a top-level post
1915 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1918 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1921 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1922 intval($u[0]['uid'])
1927 // also reset all the privacy bits to the forum default permissions
1929 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1931 $forum_mode = (($prvgroup) ? 2 : 1);
1933 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1934 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1935 intval($forum_mode),
1936 dbesc($c[0]['name']),
1937 dbesc($c[0]['url']),
1938 dbesc($c[0]['thumb']),
1940 dbesc($u[0]['allow_cid']),
1941 dbesc($u[0]['allow_gid']),
1942 dbesc($u[0]['deny_cid']),
1943 dbesc($u[0]['deny_gid']),
1946 update_thread($item_id);
1948 proc_run('php','include/notifier.php','tgroup',$item_id);
1954 function tgroup_check($uid,$item) {
1960 // check that the message originated elsewhere and is a top-level post
1962 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1966 $u = q("select * from user where uid = %d limit 1",
1972 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1973 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1976 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1978 // Diaspora uses their own hardwired link URL in @-tags
1979 // instead of the one we supply with webfinger
1981 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1983 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1985 foreach($matches as $mtch) {
1986 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1988 logger('tgroup_check: mention found: ' . $mtch[2]);
1996 if((! $community_page) && (! $prvgroup))
2010 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
2014 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
2016 if($contact['duplex'] && $contact['dfrn-id'])
2017 $idtosend = '0:' . $orig_id;
2018 if($contact['duplex'] && $contact['issued-id'])
2019 $idtosend = '1:' . $orig_id;
2022 $rino = get_config('system','rino_encrypt');
2023 $rino = intval($rino);
2024 // use RINO1 if mcrypt isn't installed and RINO2 was selected
2025 if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
2027 logger("Local rino version: ". $rino, LOGGER_DEBUG);
2029 $ssl_val = intval(get_config('system','ssl_policy'));
2033 case SSL_POLICY_FULL:
2034 $ssl_policy = 'full';
2036 case SSL_POLICY_SELFSIGN:
2037 $ssl_policy = 'self';
2039 case SSL_POLICY_NONE:
2041 $ssl_policy = 'none';
2045 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
2047 logger('dfrn_deliver: ' . $url);
2049 $xml = fetch_url($url);
2051 $curl_stat = $a->get_curl_code();
2053 return(-1); // timed out
2055 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2060 if(strpos($xml,'<?xml') === false) {
2061 logger('dfrn_deliver: no valid XML returned');
2062 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2066 $res = parse_xml_string($xml);
2068 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2069 return (($res->status) ? $res->status : 3);
2071 $postvars = array();
2072 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2073 $challenge = hex2bin((string) $res->challenge);
2074 $perm = (($res->perm) ? $res->perm : null);
2075 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2076 $rino_remote_version = intval($res->rino);
2077 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2079 logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG);
2081 if($owner['page-flags'] == PAGE_PRVGROUP)
2084 $final_dfrn_id = '';
2087 if((($perm == 'rw') && (! intval($contact['writable'])))
2088 || (($perm == 'r') && (intval($contact['writable'])))) {
2089 q("update contact set writable = %d where id = %d",
2090 intval(($perm == 'rw') ? 1 : 0),
2091 intval($contact['id'])
2093 $contact['writable'] = (string) 1 - intval($contact['writable']);
2097 if(($contact['duplex'] && strlen($contact['pubkey']))
2098 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2099 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2100 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2101 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2104 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2105 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2108 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2110 if(strpos($final_dfrn_id,':') == 1)
2111 $final_dfrn_id = substr($final_dfrn_id,2);
2113 if($final_dfrn_id != $orig_id) {
2114 logger('dfrn_deliver: wrong dfrn_id.');
2115 // did not decode properly - cannot trust this site
2119 $postvars['dfrn_id'] = $idtosend;
2120 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2122 $postvars['dissolve'] = '1';
2125 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2126 $postvars['data'] = $atom;
2127 $postvars['perm'] = 'rw';
2130 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2131 $postvars['perm'] = 'r';
2134 $postvars['ssl_policy'] = $ssl_policy;
2137 $postvars['page'] = $page;
2140 if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
2141 logger('rino version: '. $rino_remote_version);
2143 switch($rino_remote_version) {
2145 // Deprecated rino version!
2146 $key = substr(random_string(),0,16);
2147 $data = aes_encrypt($postvars['data'],$key);
2150 // RINO 2 based on php-encryption
2152 $key = Crypto::createNewRandomKey();
2153 } catch (CryptoTestFailed $ex) {
2154 logger('Cannot safely create a key');
2156 } catch (CannotPerformOperation $ex) {
2157 logger('Cannot safely create a key');
2161 $data = Crypto::encrypt($postvars['data'], $key);
2162 } catch (CryptoTestFailed $ex) {
2163 logger('Cannot safely perform encryption');
2165 } catch (CannotPerformOperation $ex) {
2166 logger('Cannot safely perform encryption');
2171 logger("rino: invalid requested verision '$rino_remote_version'");
2175 $postvars['rino'] = $rino_remote_version;
2176 $postvars['data'] = bin2hex($data);
2178 #logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2181 if($dfrn_version >= 2.1) {
2182 if(($contact['duplex'] && strlen($contact['pubkey']))
2183 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2184 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2186 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2189 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2193 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2194 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2197 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2201 logger('md5 rawkey ' . md5($postvars['key']));
2203 $postvars['key'] = bin2hex($postvars['key']);
2207 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2209 $xml = post_url($contact['notify'],$postvars);
2211 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2213 $curl_stat = $a->get_curl_code();
2214 if((! $curl_stat) || (! strlen($xml)))
2215 return(-1); // timed out
2217 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2220 if(strpos($xml,'<?xml') === false) {
2221 logger('dfrn_deliver: phase 2: no valid XML returned');
2222 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2226 if($contact['term-date'] != '0000-00-00 00:00:00') {
2227 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2228 require_once('include/Contact.php');
2229 unmark_for_death($contact);
2232 $res = parse_xml_string($xml);
2234 return $res->status;
2239 This function returns true if $update has an edited timestamp newer
2240 than $existing, i.e. $update contains new data which should override
2241 what's already there. If there is no timestamp yet, the update is
2242 assumed to be newer. If the update has no timestamp, the existing
2243 item is assumed to be up-to-date. If the timestamps are equal it
2244 assumes the update has been seen before and should be ignored.
2246 function edited_timestamp_is_newer($existing, $update) {
2247 if (!x($existing,'edited') || !$existing['edited']) {
2250 if (!x($update,'edited') || !$update['edited']) {
2253 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2254 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2255 return (strcmp($existing_edited, $update_edited) < 0);
2260 * consume_feed - process atom feed and update anything/everything we might need to update
2262 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2264 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2265 * It is this person's stuff that is going to be updated.
2266 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2267 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2268 * have a contact record.
2269 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2270 * might not) try and subscribe to it.
2271 * $datedir sorts in reverse order
2272 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2273 * imported prior to its children being seen in the stream unless we are certain
2274 * of how the feed is arranged/ordered.
2275 * With $pass = 1, we only pull parent items out of the stream.
2276 * With $pass = 2, we only pull children (comments/likes).
2278 * So running this twice, first with pass 1 and then with pass 2 will do the right
2279 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2280 * model where comments can have sub-threads. That would require some massive sorting
2281 * to get all the feed items into a mostly linear ordering, and might still require
2285 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2286 if ($contact['network'] === NETWORK_OSTATUS) {
2288 logger("Consume OStatus messages ", LOGGER_DEBUG);
2289 ostatus_import($xml,$importer,$contact, $hub);
2294 if ($contact['network'] === NETWORK_FEED) {
2296 logger("Consume feeds", LOGGER_DEBUG);
2297 feed_import($xml,$importer,$contact, $hub);
2302 require_once('library/simplepie/simplepie.inc');
2303 require_once('include/contact_selectors.php');
2305 if(! strlen($xml)) {
2306 logger('consume_feed: empty input');
2310 $feed = new SimplePie();
2311 $feed->set_raw_data($xml);
2313 $feed->enable_order_by_date(true);
2315 $feed->enable_order_by_date(false);
2319 logger('consume_feed: Error parsing XML: ' . $feed->error());
2321 $permalink = $feed->get_permalink();
2323 // Check at the feed level for updated contact name and/or photo
2327 $photo_timestamp = '';
2330 $contact_updated = '';
2332 $hubs = $feed->get_links('hub');
2333 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2336 $hub = implode(',', $hubs);
2338 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2340 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2342 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2343 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2344 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2345 $new_name = $elems['name'][0]['data'];
2347 // Manually checking for changed contact names
2348 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2349 $name_updated = date("c");
2350 $photo_timestamp = date("c");
2353 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2354 if ($photo_timestamp == "")
2355 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2356 $photo_url = $elems['link'][0]['attribs']['']['href'];
2359 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2360 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2364 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2365 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2367 $contact_updated = $photo_timestamp;
2369 require_once("include/Photo.php");
2370 $photo_failure = false;
2371 $have_photo = false;
2373 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2374 intval($contact['id']),
2375 intval($contact['uid'])
2378 $resource_id = $r[0]['resource-id'];
2382 $resource_id = photo_new_resource();
2385 $img_str = fetch_url($photo_url,true);
2386 // guess mimetype from headers or filename
2387 $type = guess_image_type($photo_url,true);
2390 $img = new Photo($img_str, $type);
2391 if($img->is_valid()) {
2393 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2394 dbesc($resource_id),
2395 intval($contact['id']),
2396 intval($contact['uid'])
2400 $img->scaleImageSquare(175);
2402 $hash = $resource_id;
2403 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2405 $img->scaleImage(80);
2406 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2408 $img->scaleImage(48);
2409 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2413 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2414 WHERE `uid` = %d AND `id` = %d",
2415 dbesc(datetime_convert()),
2416 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2417 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2418 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2419 intval($contact['uid']),
2420 intval($contact['id'])
2425 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2426 if ($name_updated > $contact_updated)
2427 $contact_updated = $name_updated;
2429 $r = q("select * from contact where uid = %d and id = %d limit 1",
2430 intval($contact['uid']),
2431 intval($contact['id'])
2434 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2435 dbesc(notags(trim($new_name))),
2436 dbesc(datetime_convert()),
2437 intval($contact['uid']),
2438 intval($contact['id'])
2441 // do our best to update the name on content items
2444 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2445 dbesc(notags(trim($new_name))),
2446 dbesc($r[0]['name']),
2447 dbesc($r[0]['url']),
2448 intval($contact['uid'])
2453 if ($contact_updated AND $new_name AND $photo_url)
2454 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2456 if(strlen($birthday)) {
2457 if(substr($birthday,0,4) != $contact['bdyear']) {
2458 logger('consume_feed: updating birthday: ' . $birthday);
2462 * Add new birthday event for this person
2464 * $bdtext is just a readable placeholder in case the event is shared
2465 * with others. We will replace it during presentation to our $importer
2466 * to contain a sparkle link and perhaps a photo.
2470 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2471 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2474 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2475 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2476 intval($contact['uid']),
2477 intval($contact['id']),
2478 dbesc(datetime_convert()),
2479 dbesc(datetime_convert()),
2480 dbesc(datetime_convert('UTC','UTC', $birthday)),
2481 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2490 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2491 dbesc(substr($birthday,0,4)),
2492 intval($contact['uid']),
2493 intval($contact['id'])
2496 // This function is called twice without reloading the contact
2497 // Make sure we only create one event. This is why &$contact
2498 // is a reference var in this function
2500 $contact['bdyear'] = substr($birthday,0,4);
2504 $community_page = 0;
2505 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2507 $community_page = intval($rawtags[0]['data']);
2509 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2510 q("update contact set forum = %d where id = %d",
2511 intval($community_page),
2512 intval($contact['id'])
2514 $contact['forum'] = (string) $community_page;
2518 // process any deleted entries
2520 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2521 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2522 foreach($del_entries as $dentry) {
2524 if(isset($dentry['attribs']['']['ref'])) {
2525 $uri = $dentry['attribs']['']['ref'];
2527 if(isset($dentry['attribs']['']['when'])) {
2528 $when = $dentry['attribs']['']['when'];
2529 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2532 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2534 if($deleted && is_array($contact)) {
2535 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2536 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2538 intval($importer['uid']),
2539 intval($contact['id'])
2544 if(! $item['deleted'])
2545 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
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,'start')) {
2839 $ev['uid'] = $importer['uid'];
2840 $ev['uri'] = $item_id;
2841 $ev['edited'] = $datarray['edited'];
2842 $ev['private'] = $datarray['private'];
2844 if(is_array($contact))
2845 $ev['cid'] = $contact['id'];
2846 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2848 intval($importer['uid'])
2851 $ev['id'] = $r[0]['id'];
2852 $xyz = event_store($ev);
2857 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2858 if(strlen($datarray['title']))
2859 unset($datarray['title']);
2860 $datarray['last-child'] = 1;
2864 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2866 intval($importer['uid'])
2869 // Update content if 'updated' changes
2872 if (edited_timestamp_is_newer($r[0], $datarray)) {
2874 // do not accept (ignore) an earlier edit than one we currently have.
2875 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2878 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2879 dbesc($datarray['title']),
2880 dbesc($datarray['body']),
2881 dbesc($datarray['tag']),
2882 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2883 dbesc(datetime_convert()),
2885 intval($importer['uid'])
2887 create_tags_from_itemuri($item_id, $importer['uid']);
2888 update_thread_uri($item_id, $importer['uid']);
2891 // update last-child if it changes
2893 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2894 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2895 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2896 intval($allow[0]['data']),
2897 dbesc(datetime_convert()),
2899 intval($importer['uid'])
2901 update_thread_uri($item_id, $importer['uid']);
2906 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2907 logger('consume-feed: New follower');
2908 new_follower($importer,$contact,$datarray,$item);
2911 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2912 lose_follower($importer,$contact,$datarray,$item);
2916 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2917 logger('consume-feed: New friend request');
2918 new_follower($importer,$contact,$datarray,$item,true);
2921 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2922 lose_sharer($importer,$contact,$datarray,$item);
2927 if(! is_array($contact))
2931 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2932 // one way feed - no remote comment ability
2933 $datarray['last-child'] = 0;
2935 if($contact['network'] === NETWORK_FEED)
2936 $datarray['private'] = 2;
2938 $datarray['parent-uri'] = $item_id;
2939 $datarray['uid'] = $importer['uid'];
2940 $datarray['contact-id'] = $contact['id'];
2942 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2943 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2944 // but otherwise there's a possible data mixup on the sender's system.
2945 // the tgroup delivery code called from item_store will correct it if it's a forum,
2946 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2947 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2948 $datarray['owner-name'] = $contact['name'];
2949 $datarray['owner-link'] = $contact['url'];
2950 $datarray['owner-avatar'] = $contact['thumb'];
2953 // We've allowed "followers" to reach this point so we can decide if they are
2954 // posting an @-tag delivery, which followers are allowed to do for certain
2955 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2957 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2960 // This is my contact on another system, but it's really me.
2961 // Turn this into a wall post.
2962 $notify = item_is_remote_self($contact, $datarray);
2964 $r = item_store($datarray, false, $notify);
2965 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2973 function item_is_remote_self($contact, &$datarray) {
2976 if (!$contact['remote_self'])
2979 // Prevent the forwarding of posts that are forwarded
2980 if ($datarray["extid"] == NETWORK_DFRN)
2983 // Prevent to forward already forwarded posts
2984 if ($datarray["app"] == $a->get_hostname())
2987 // Only forward posts
2988 if ($datarray["verb"] != ACTIVITY_POST)
2991 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
2994 $datarray2 = $datarray;
2995 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
2996 if ($contact['remote_self'] == 2) {
2997 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
2998 intval($contact['uid']));
3000 $datarray['contact-id'] = $r[0]["id"];
3002 $datarray['owner-name'] = $r[0]["name"];
3003 $datarray['owner-link'] = $r[0]["url"];
3004 $datarray['owner-avatar'] = $r[0]["thumb"];
3006 $datarray['author-name'] = $datarray['owner-name'];
3007 $datarray['author-link'] = $datarray['owner-link'];
3008 $datarray['author-avatar'] = $datarray['owner-avatar'];
3011 if ($contact['network'] != NETWORK_FEED) {
3012 $datarray["guid"] = get_guid(32);
3013 unset($datarray["plink"]);
3014 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
3015 $datarray["parent-uri"] = $datarray["uri"];
3016 $datarray["extid"] = $contact['network'];
3017 $urlpart = parse_url($datarray2['author-link']);
3018 $datarray["app"] = $urlpart["host"];
3020 $datarray['private'] = 0;
3023 if ($contact['network'] != NETWORK_FEED) {
3024 // Store the original post
3025 $r = item_store($datarray2, false, false);
3026 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
3028 $datarray["app"] = "Feed";
3033 function local_delivery($importer,$data) {
3036 logger(__function__, LOGGER_TRACE);
3038 if($importer['readonly']) {
3039 // We aren't receiving stuff from this person. But we will quietly ignore them
3040 // rather than a blatant "go away" message.
3041 logger('local_delivery: ignoring');
3046 // Consume notification feed. This may differ from consuming a public feed in several ways
3047 // - might contain email or friend suggestions
3048 // - might contain remote followup to our message
3049 // - in which case we need to accept it and then notify other conversants
3050 // - we may need to send various email notifications
3052 $feed = new SimplePie();
3053 $feed->set_raw_data($data);
3054 $feed->enable_order_by_date(false);
3059 logger('local_delivery: Error parsing XML: ' . $feed->error());
3062 // Check at the feed level for updated contact name and/or photo
3066 $photo_timestamp = '';
3068 $contact_updated = '';
3071 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
3073 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
3075 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
3078 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
3079 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
3080 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
3081 $new_name = $elems['name'][0]['data'];
3083 // Manually checking for changed contact names
3084 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
3085 $name_updated = date("c");
3086 $photo_timestamp = date("c");
3089 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
3090 if ($photo_timestamp == "")
3091 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
3092 $photo_url = $elems['link'][0]['attribs']['']['href'];
3096 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3098 $contact_updated = $photo_timestamp;
3100 logger('local_delivery: Updating photo for ' . $importer['name']);
3101 require_once("include/Photo.php");
3102 $photo_failure = false;
3103 $have_photo = false;
3105 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
3106 intval($importer['id']),
3107 intval($importer['importer_uid'])
3110 $resource_id = $r[0]['resource-id'];
3114 $resource_id = photo_new_resource();
3117 $img_str = fetch_url($photo_url,true);
3118 // guess mimetype from headers or filename
3119 $type = guess_image_type($photo_url,true);
3122 $img = new Photo($img_str, $type);
3123 if($img->is_valid()) {
3125 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
3126 dbesc($resource_id),
3127 intval($importer['id']),
3128 intval($importer['importer_uid'])
3132 $img->scaleImageSquare(175);
3134 $hash = $resource_id;
3135 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
3137 $img->scaleImage(80);
3138 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
3140 $img->scaleImage(48);
3141 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3145 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3146 WHERE `uid` = %d AND `id` = %d",
3147 dbesc(datetime_convert()),
3148 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3149 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3150 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3151 intval($importer['importer_uid']),
3152 intval($importer['id'])
3157 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3158 if ($name_updated > $contact_updated)
3159 $contact_updated = $name_updated;
3161 $r = q("select * from contact where uid = %d and id = %d limit 1",
3162 intval($importer['importer_uid']),
3163 intval($importer['id'])
3166 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3167 dbesc(notags(trim($new_name))),
3168 dbesc(datetime_convert()),
3169 intval($importer['importer_uid']),
3170 intval($importer['id'])
3173 // do our best to update the name on content items
3176 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3177 dbesc(notags(trim($new_name))),
3178 dbesc($r[0]['name']),
3179 dbesc($r[0]['url']),
3180 intval($importer['importer_uid'])
3185 if ($contact_updated AND $new_name AND $photo_url)
3186 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3188 // Currently unsupported - needs a lot of work
3189 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3190 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3191 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3193 $newloc['uid'] = $importer['importer_uid'];
3194 $newloc['cid'] = $importer['id'];
3195 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3196 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3197 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3198 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3199 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3200 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3201 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3202 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3203 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3204 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3205 /** relocated user must have original key pair */
3206 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3207 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3209 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3212 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3213 intval($importer['id']),
3214 intval($importer['importer_uid']));
3219 $x = q("UPDATE contact SET
3230 `site-pubkey` = '%s'
3231 WHERE id=%d AND uid=%d;",
3232 dbesc($newloc['name']),
3233 dbesc($newloc['photo']),
3234 dbesc($newloc['thumb']),
3235 dbesc($newloc['micro']),
3236 dbesc($newloc['url']),
3237 dbesc(normalise_link($newloc['url'])),
3238 dbesc($newloc['request']),
3239 dbesc($newloc['confirm']),
3240 dbesc($newloc['notify']),
3241 dbesc($newloc['poll']),
3242 dbesc($newloc['sitepubkey']),
3243 intval($importer['id']),
3244 intval($importer['importer_uid']));
3250 'owner-link' => array($old['url'], $newloc['url']),
3251 'author-link' => array($old['url'], $newloc['url']),
3252 'owner-avatar' => array($old['photo'], $newloc['photo']),
3253 'author-avatar' => array($old['photo'], $newloc['photo']),
3255 foreach ($fields as $n=>$f){
3256 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3259 intval($importer['importer_uid']));
3265 // merge with current record, current contents have priority
3266 // update record, set url-updated
3267 // update profile photos
3273 // handle friend suggestion notification
3275 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3276 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3277 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3279 $fsugg['uid'] = $importer['importer_uid'];
3280 $fsugg['cid'] = $importer['id'];
3281 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3282 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3283 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3284 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3285 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3287 // Does our member already have a friend matching this description?
3289 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3290 dbesc($fsugg['name']),
3291 dbesc(normalise_link($fsugg['url'])),
3292 intval($fsugg['uid'])
3297 // Do we already have an fcontact record for this person?
3300 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3301 dbesc($fsugg['url']),
3302 dbesc($fsugg['name']),
3303 dbesc($fsugg['request'])
3308 // OK, we do. Do we already have an introduction for this person ?
3309 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3310 intval($fsugg['uid']),
3317 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3318 dbesc($fsugg['name']),
3319 dbesc($fsugg['url']),
3320 dbesc($fsugg['photo']),
3321 dbesc($fsugg['request'])
3323 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3324 dbesc($fsugg['url']),
3325 dbesc($fsugg['name']),
3326 dbesc($fsugg['request'])
3331 // database record did not get created. Quietly give up.
3336 $hash = random_string();
3338 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3339 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3340 intval($fsugg['uid']),
3342 intval($fsugg['cid']),
3343 dbesc($fsugg['body']),
3345 dbesc(datetime_convert()),
3350 'type' => NOTIFY_SUGGEST,
3351 'notify_flags' => $importer['notify-flags'],
3352 'language' => $importer['language'],
3353 'to_name' => $importer['username'],
3354 'to_email' => $importer['email'],
3355 'uid' => $importer['importer_uid'],
3357 'link' => $a->get_baseurl() . '/notifications/intros',
3358 'source_name' => $importer['name'],
3359 'source_link' => $importer['url'],
3360 'source_photo' => $importer['photo'],
3361 'verb' => ACTIVITY_REQ_FRIEND,
3370 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3371 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3373 logger('local_delivery: private message received');
3376 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3379 $msg['uid'] = $importer['importer_uid'];
3380 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3381 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3382 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3383 $msg['contact-id'] = $importer['id'];
3384 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3385 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3387 $msg['replied'] = 0;
3388 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3389 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3390 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3394 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3395 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3397 // send notifications.
3399 require_once('include/enotify.php');
3401 $notif_params = array(
3402 'type' => NOTIFY_MAIL,
3403 'notify_flags' => $importer['notify-flags'],
3404 'language' => $importer['language'],
3405 'to_name' => $importer['username'],
3406 'to_email' => $importer['email'],
3407 'uid' => $importer['importer_uid'],
3409 'source_name' => $msg['from-name'],
3410 'source_link' => $importer['url'],
3411 'source_photo' => $importer['thumb'],
3412 'verb' => ACTIVITY_POST,
3416 notification($notif_params);
3422 $community_page = 0;
3423 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3425 $community_page = intval($rawtags[0]['data']);
3427 if(intval($importer['forum']) != $community_page) {
3428 q("update contact set forum = %d where id = %d",
3429 intval($community_page),
3430 intval($importer['id'])
3432 $importer['forum'] = (string) $community_page;
3435 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3437 // process any deleted entries
3439 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3440 if(is_array($del_entries) && count($del_entries)) {
3441 foreach($del_entries as $dentry) {
3443 if(isset($dentry['attribs']['']['ref'])) {
3444 $uri = $dentry['attribs']['']['ref'];
3446 if(isset($dentry['attribs']['']['when'])) {
3447 $when = $dentry['attribs']['']['when'];
3448 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3451 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3455 // check for relayed deletes to our conversation
3458 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3460 intval($importer['importer_uid'])
3463 $parent_uri = $r[0]['parent-uri'];
3464 if($r[0]['id'] != $r[0]['parent'])
3471 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3474 logger('local_delivery: possible community delete');
3477 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3479 // was the top-level post for this reply written by somebody on this site?
3480 // Specifically, the recipient?
3482 $is_a_remote_delete = false;
3484 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3485 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3486 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3487 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3488 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3489 AND `item`.`uid` = %d
3495 intval($importer['importer_uid'])
3498 $is_a_remote_delete = true;
3500 // Does this have the characteristics of a community or private group comment?
3501 // If it's a reply to a wall post on a community/prvgroup page it's a
3502 // valid community comment. Also forum_mode makes it valid for sure.
3503 // If neither, it's not.
3505 if($is_a_remote_delete && $community) {
3506 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3507 $is_a_remote_delete = false;
3508 logger('local_delivery: not a community delete');
3512 if($is_a_remote_delete) {
3513 logger('local_delivery: received remote delete');
3517 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3518 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3520 intval($importer['importer_uid']),
3521 intval($importer['id'])
3527 if($item['deleted'])
3530 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3532 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3533 $xo = parse_xml_string($item['object'],false);
3534 $xt = parse_xml_string($item['target'],false);
3536 if($xt->type === ACTIVITY_OBJ_NOTE) {
3537 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3539 intval($importer['importer_uid'])
3543 // For tags, the owner cannot remove the tag on the author's copy of the post.
3545 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3546 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3547 $author_copy = (($item['origin']) ? true : false);
3549 if($owner_remove && $author_copy)
3551 if($author_remove || $owner_remove) {
3552 $tags = explode(',',$i[0]['tag']);
3555 foreach($tags as $tag)
3556 if(trim($tag) !== trim($xo->body))
3557 $newtags[] = trim($tag);
3559 q("update item set tag = '%s' where id = %d",
3560 dbesc(implode(',',$newtags)),
3563 create_tags_from_item($i[0]['id']);
3569 if($item['uri'] == $item['parent-uri']) {
3570 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3571 `body` = '', `title` = ''
3572 WHERE `parent-uri` = '%s' AND `uid` = %d",
3574 dbesc(datetime_convert()),
3575 dbesc($item['uri']),
3576 intval($importer['importer_uid'])
3578 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3579 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3580 update_thread_uri($item['uri'], $importer['importer_uid']);
3583 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3584 `body` = '', `title` = ''
3585 WHERE `uri` = '%s' AND `uid` = %d",
3587 dbesc(datetime_convert()),
3589 intval($importer['importer_uid'])
3591 create_tags_from_itemuri($uri, $importer['importer_uid']);
3592 create_files_from_itemuri($uri, $importer['importer_uid']);
3593 update_thread_uri($uri, $importer['importer_uid']);
3594 if($item['last-child']) {
3595 // ensure that last-child is set in case the comment that had it just got wiped.
3596 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3597 dbesc(datetime_convert()),
3598 dbesc($item['parent-uri']),
3599 intval($item['uid'])
3601 // who is the last child now?
3602 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3603 ORDER BY `created` DESC LIMIT 1",
3604 dbesc($item['parent-uri']),
3605 intval($importer['importer_uid'])
3608 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3613 // if this is a relayed delete, propagate it to other recipients
3615 if($is_a_remote_delete)
3616 proc_run('php',"include/notifier.php","drop",$item['id']);
3624 foreach($feed->get_items() as $item) {
3627 $item_id = $item->get_id();
3628 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3629 if(isset($rawthread[0]['attribs']['']['ref'])) {
3631 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3637 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3640 logger('local_delivery: possible community reply');
3643 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3645 // was the top-level post for this reply written by somebody on this site?
3646 // Specifically, the recipient?
3648 $is_a_remote_comment = false;
3649 $top_uri = $parent_uri;
3651 $r = q("select `item`.`parent-uri` from `item`
3652 WHERE `item`.`uri` = '%s'
3656 if($r && count($r)) {
3657 $top_uri = $r[0]['parent-uri'];
3659 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3660 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3661 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3662 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3663 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3664 AND `item`.`uid` = %d
3670 intval($importer['importer_uid'])
3673 $is_a_remote_comment = true;
3676 // Does this have the characteristics of a community or private group comment?
3677 // If it's a reply to a wall post on a community/prvgroup page it's a
3678 // valid community comment. Also forum_mode makes it valid for sure.
3679 // If neither, it's not.
3681 if($is_a_remote_comment && $community) {
3682 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3683 $is_a_remote_comment = false;
3684 logger('local_delivery: not a community reply');
3688 if($is_a_remote_comment) {
3689 logger('local_delivery: received remote comment');
3691 // remote reply to our post. Import and then notify everybody else.
3693 $datarray = get_atom_elements($feed, $item);
3695 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3697 intval($importer['importer_uid'])
3700 // Update content if 'updated' changes
3704 if (edited_timestamp_is_newer($r[0], $datarray)) {
3706 // do not accept (ignore) an earlier edit than one we currently have.
3707 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3710 logger('received updated comment' , LOGGER_DEBUG);
3711 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3712 dbesc($datarray['title']),
3713 dbesc($datarray['body']),
3714 dbesc($datarray['tag']),
3715 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3716 dbesc(datetime_convert()),
3718 intval($importer['importer_uid'])
3720 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3722 proc_run('php',"include/notifier.php","comment-import",$iid);
3731 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3732 intval($importer['importer_uid'])
3736 $datarray['type'] = 'remote-comment';
3737 $datarray['wall'] = 1;
3738 $datarray['parent-uri'] = $parent_uri;
3739 $datarray['uid'] = $importer['importer_uid'];
3740 $datarray['owner-name'] = $own[0]['name'];
3741 $datarray['owner-link'] = $own[0]['url'];
3742 $datarray['owner-avatar'] = $own[0]['thumb'];
3743 $datarray['contact-id'] = $importer['id'];
3745 if(($datarray['verb'] === ACTIVITY_LIKE)
3746 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3747 || ($datarray['verb'] === ACTIVITY_ATTEND)
3748 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3749 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3751 $datarray['type'] = 'activity';
3752 $datarray['gravity'] = GRAVITY_LIKE;
3753 $datarray['last-child'] = 0;
3754 // only one like or dislike per person
3755 // splitted into two queries for performance issues
3756 $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",
3757 intval($datarray['uid']),
3758 intval($datarray['contact-id']),
3759 dbesc($datarray['verb']),
3760 dbesc($datarray['parent-uri'])
3766 $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",
3767 intval($datarray['uid']),
3768 intval($datarray['contact-id']),
3769 dbesc($datarray['verb']),
3770 dbesc($datarray['parent-uri'])
3777 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3779 $xo = parse_xml_string($datarray['object'],false);
3780 $xt = parse_xml_string($datarray['target'],false);
3782 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3784 // fetch the parent item
3786 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3788 intval($importer['importer_uid'])
3793 // extract tag, if not duplicate, and this user allows tags, add to parent item
3795 if($xo->id && $xo->content) {
3796 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3797 if(! (stristr($tagp[0]['tag'],$newtag))) {
3798 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3799 intval($importer['importer_uid'])
3801 if(count($i) && ! intval($i[0]['blocktags'])) {
3802 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3803 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3804 intval($tagp[0]['id']),
3805 dbesc(datetime_convert()),
3806 dbesc(datetime_convert())
3808 create_tags_from_item($tagp[0]['id']);
3816 $posted_id = item_store($datarray);
3821 $datarray["id"] = $posted_id;
3823 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3825 intval($importer['importer_uid'])
3828 $parent = $r[0]['parent'];
3829 $parent_uri = $r[0]['parent-uri'];
3833 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3834 dbesc(datetime_convert()),
3835 intval($importer['importer_uid']),
3836 intval($r[0]['parent'])
3839 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3840 dbesc(datetime_convert()),
3841 intval($importer['importer_uid']),
3846 if($posted_id && $parent) {
3848 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3850 if((! $is_like) && (! $importer['self'])) {
3852 require_once('include/enotify.php');
3855 'type' => NOTIFY_COMMENT,
3856 'notify_flags' => $importer['notify-flags'],
3857 'language' => $importer['language'],
3858 'to_name' => $importer['username'],
3859 'to_email' => $importer['email'],
3860 'uid' => $importer['importer_uid'],
3861 'item' => $datarray,
3862 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3863 'source_name' => stripslashes($datarray['author-name']),
3864 'source_link' => $datarray['author-link'],
3865 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3866 ? $importer['thumb'] : $datarray['author-avatar']),
3867 'verb' => ACTIVITY_POST,
3869 'parent' => $parent,
3870 'parent_uri' => $parent_uri,
3882 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3884 $item_id = $item->get_id();
3885 $datarray = get_atom_elements($feed,$item);
3887 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3890 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3892 intval($importer['importer_uid'])
3895 // Update content if 'updated' changes
3898 if (edited_timestamp_is_newer($r[0], $datarray)) {
3900 // do not accept (ignore) an earlier edit than one we currently have.
3901 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3904 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3905 dbesc($datarray['title']),
3906 dbesc($datarray['body']),
3907 dbesc($datarray['tag']),
3908 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3909 dbesc(datetime_convert()),
3911 intval($importer['importer_uid'])
3913 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3916 // update last-child if it changes
3918 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3919 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3920 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3921 dbesc(datetime_convert()),
3923 intval($importer['importer_uid'])
3925 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3926 intval($allow[0]['data']),
3927 dbesc(datetime_convert()),
3929 intval($importer['importer_uid'])
3935 $datarray['parent-uri'] = $parent_uri;
3936 $datarray['uid'] = $importer['importer_uid'];
3937 $datarray['contact-id'] = $importer['id'];
3938 if(($datarray['verb'] === ACTIVITY_LIKE)
3939 || ($datarray['verb'] === ACTIVITY_DISLIKE)
3940 || ($datarray['verb'] === ACTIVITY_ATTEND)
3941 || ($datarray['verb'] === ACTIVITY_ATTENDNO)
3942 || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
3943 $datarray['type'] = 'activity';
3944 $datarray['gravity'] = GRAVITY_LIKE;
3945 // only one like or dislike per person
3946 // splitted into two queries for performance issues
3947 $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",
3948 intval($datarray['uid']),
3949 intval($datarray['contact-id']),
3950 dbesc($datarray['verb']),
3956 $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",
3957 intval($datarray['uid']),
3958 intval($datarray['contact-id']),
3959 dbesc($datarray['verb']),
3967 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3969 $xo = parse_xml_string($datarray['object'],false);
3970 $xt = parse_xml_string($datarray['target'],false);
3972 if($xt->type == ACTIVITY_OBJ_NOTE) {
3973 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3975 intval($importer['importer_uid'])
3980 // extract tag, if not duplicate, add to parent item
3982 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3983 q("UPDATE item SET tag = '%s' WHERE id = %d",
3984 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3987 create_tags_from_item($r[0]['id']);
3993 $posted_id = item_store($datarray);
3995 // find out if our user is involved in this conversation and wants to be notified.
3997 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
3999 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
4001 intval($importer['importer_uid'])
4004 if(count($myconv)) {
4005 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
4007 // first make sure this isn't our own post coming back to us from a wall-to-wall event
4008 if(! link_compare($datarray['author-link'],$importer_url)) {
4011 foreach($myconv as $conv) {
4013 // now if we find a match, it means we're in this conversation
4015 if(! link_compare($conv['author-link'],$importer_url))
4018 require_once('include/enotify.php');
4020 $conv_parent = $conv['parent'];
4023 'type' => NOTIFY_COMMENT,
4024 'notify_flags' => $importer['notify-flags'],
4025 'language' => $importer['language'],
4026 'to_name' => $importer['username'],
4027 'to_email' => $importer['email'],
4028 'uid' => $importer['importer_uid'],
4029 'item' => $datarray,
4030 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4031 'source_name' => stripslashes($datarray['author-name']),
4032 'source_link' => $datarray['author-link'],
4033 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4034 ? $importer['thumb'] : $datarray['author-avatar']),
4035 'verb' => ACTIVITY_POST,
4037 'parent' => $conv_parent,
4038 'parent_uri' => $parent_uri
4042 // only send one notification
4054 // Head post of a conversation. Have we seen it? If not, import it.
4057 $item_id = $item->get_id();
4058 $datarray = get_atom_elements($feed,$item);
4060 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
4061 $ev = bbtoevent($datarray['body']);
4062 if(x($ev,'desc') && x($ev,'start')) {
4063 $ev['cid'] = $importer['id'];
4064 $ev['uid'] = $importer['uid'];
4065 $ev['uri'] = $item_id;
4066 $ev['edited'] = $datarray['edited'];
4067 $ev['private'] = $datarray['private'];
4069 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4071 intval($importer['uid'])
4074 $ev['id'] = $r[0]['id'];
4075 $xyz = event_store($ev);
4080 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4082 intval($importer['importer_uid'])
4085 // Update content if 'updated' changes
4088 if (edited_timestamp_is_newer($r[0], $datarray)) {
4090 // do not accept (ignore) an earlier edit than one we currently have.
4091 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4094 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4095 dbesc($datarray['title']),
4096 dbesc($datarray['body']),
4097 dbesc($datarray['tag']),
4098 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4099 dbesc(datetime_convert()),
4101 intval($importer['importer_uid'])
4103 create_tags_from_itemuri($item_id, $importer['importer_uid']);
4104 update_thread_uri($item_id, $importer['importer_uid']);
4107 // update last-child if it changes
4109 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4110 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4111 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4112 intval($allow[0]['data']),
4113 dbesc(datetime_convert()),
4115 intval($importer['importer_uid'])
4121 $datarray['parent-uri'] = $item_id;
4122 $datarray['uid'] = $importer['importer_uid'];
4123 $datarray['contact-id'] = $importer['id'];
4126 if(! link_compare($datarray['owner-link'],$importer['url'])) {
4127 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4128 // but otherwise there's a possible data mixup on the sender's system.
4129 // the tgroup delivery code called from item_store will correct it if it's a forum,
4130 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4131 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4132 $datarray['owner-name'] = $importer['senderName'];
4133 $datarray['owner-link'] = $importer['url'];
4134 $datarray['owner-avatar'] = $importer['thumb'];
4137 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4140 // This is my contact on another system, but it's really me.
4141 // Turn this into a wall post.
4142 $notify = item_is_remote_self($importer, $datarray);
4144 $posted_id = item_store($datarray, false, $notify);
4146 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4147 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4150 $xo = parse_xml_string($datarray['object'],false);
4152 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4154 // somebody was poked/prodded. Was it me?
4156 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4158 foreach($links->link as $l) {
4159 $atts = $l->attributes();
4160 switch($atts['rel']) {
4162 $Blink = $atts['href'];
4168 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4170 // send a notification
4171 require_once('include/enotify.php');
4174 'type' => NOTIFY_POKE,
4175 'notify_flags' => $importer['notify-flags'],
4176 'language' => $importer['language'],
4177 'to_name' => $importer['username'],
4178 'to_email' => $importer['email'],
4179 'uid' => $importer['importer_uid'],
4180 'item' => $datarray,
4181 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4182 'source_name' => stripslashes($datarray['author-name']),
4183 'source_link' => $datarray['author-link'],
4184 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4185 ? $importer['thumb'] : $datarray['author-avatar']),
4186 'verb' => $datarray['verb'],
4187 'otype' => 'person',
4188 'activity' => $verb,
4189 'parent' => $datarray['parent']
4205 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4206 $url = notags(trim($datarray['author-link']));
4207 $name = notags(trim($datarray['author-name']));
4208 $photo = notags(trim($datarray['author-avatar']));
4210 if (is_object($item)) {
4211 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4212 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4213 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4217 if(is_array($contact)) {
4218 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4219 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4220 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4221 intval(CONTACT_IS_FRIEND),
4222 intval($contact['id']),
4223 intval($importer['uid'])
4226 // send email notification to owner?
4230 // create contact record
4232 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4233 `blocked`, `readonly`, `pending`, `writable` )
4234 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4235 intval($importer['uid']),
4236 dbesc(datetime_convert()),
4238 dbesc(normalise_link($url)),
4242 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4243 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4245 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4246 intval($importer['uid']),
4250 $contact_record = $r[0];
4252 // create notification
4253 $hash = random_string();
4255 if(is_array($contact_record)) {
4256 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4257 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4258 intval($importer['uid']),
4259 intval($contact_record['id']),
4261 dbesc(datetime_convert())
4265 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4266 intval($importer['uid'])
4271 if(intval($r[0]['def_gid'])) {
4272 require_once('include/group.php');
4273 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4276 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4277 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4280 'type' => NOTIFY_INTRO,
4281 'notify_flags' => $r[0]['notify-flags'],
4282 'language' => $r[0]['language'],
4283 'to_name' => $r[0]['username'],
4284 'to_email' => $r[0]['email'],
4285 'uid' => $r[0]['uid'],
4286 'link' => $a->get_baseurl() . '/notifications/intro',
4287 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4288 'source_link' => $contact_record['url'],
4289 'source_photo' => $contact_record['photo'],
4290 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4299 function lose_follower($importer,$contact,$datarray,$item) {
4301 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4302 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4303 intval(CONTACT_IS_SHARING),
4304 intval($contact['id'])
4308 contact_remove($contact['id']);
4312 function lose_sharer($importer,$contact,$datarray,$item) {
4314 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4315 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4316 intval(CONTACT_IS_FOLLOWER),
4317 intval($contact['id'])
4321 contact_remove($contact['id']);
4326 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4330 if(is_array($importer)) {
4331 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4332 intval($importer['uid'])
4336 // Diaspora has different message-ids in feeds than they do
4337 // through the direct Diaspora protocol. If we try and use
4338 // the feed, we'll get duplicates. So don't.
4340 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4343 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4345 // Use a single verify token, even if multiple hubs
4347 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4349 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4351 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4353 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
4354 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4355 dbesc($verify_token),
4356 intval($contact['id'])
4360 post_url($url,$params);
4362 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4369 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4373 $name = xmlify($name);
4374 $uri = xmlify($uri);
4377 $photo = xmlify($photo);
4381 $o .= "\t<name>$name</name>\r\n";
4382 $o .= "\t<uri>$uri</uri>\r\n";
4383 $o .= "\t".'<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4384 $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4386 if ($tag == "author") {
4387 $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
4388 `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
4389 `profile`.`homepage`,`contact`.`nick` FROM `profile`
4390 INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
4391 INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
4392 WHERE `profile`.`is-default` AND `contact`.`self` AND
4393 NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
4394 dbesc(normalise_link($uri)));
4397 if($r[0]['locality'])
4398 $location .= $r[0]['locality'];
4399 if($r[0]['region']) {
4402 $location .= $r[0]['region'];
4404 if($r[0]['country-name']) {
4407 $location .= $r[0]['country-name'];
4410 $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
4411 $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
4412 $o .= "\t<poco:note>".xmlify($r[0]["about"])."</poco:note>\r\n";
4413 $o .= "\t<poco:address>\r\n";
4414 $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
4415 $o .= "\t</poco:address>\r\n";
4416 $o .= "\t<poco:urls>\r\n";
4417 $o .= "\t<poco:type>homepage</poco:type>\r\n";
4418 $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
4419 $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
4420 $o .= "\t</poco:urls>\r\n";
4424 call_hooks('atom_author', $o);
4426 $o .= "</$tag>\r\n";
4430 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4434 if(! $item['parent'])
4437 if($item['deleted'])
4438 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4441 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4442 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4444 $body = $item['body'];
4447 $o = "\r\n\r\n<entry>\r\n";
4449 if(is_array($author))
4450 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4452 $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']));
4453 if(strlen($item['owner-name']))
4454 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4456 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4457 $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
4458 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4459 $o .= '<thr:in-reply-to ref="'.xmlify($parent_item).'" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$parent[0]['guid']).'" />'."\r\n";
4464 if ($item['title'] != "")
4465 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4467 $htmlbody = bbcode($htmlbody, false, false, 7);
4469 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4470 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4471 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4472 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4473 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4474 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4475 $o .= '<link rel="alternate" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$item['guid']).'" />'."\r\n";
4477 $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
4480 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4482 if($item['location']) {
4483 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4484 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4488 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4490 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4491 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4494 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4495 if($item['bookmark'])
4496 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4499 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4502 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4504 if($item['signed_text']) {
4505 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4506 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4509 $verb = construct_verb($item);
4510 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4511 $actobj = construct_activity_object($item);
4514 $actarg = construct_activity_target($item);
4518 $tags = item_getfeedtags($item);
4520 foreach($tags as $t)
4521 if (($type != 'html') OR ($t[0] != "@"))
4522 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4526 // To support these elements, the API needs to be enhanced
4527 //$o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
4528 //$o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4529 //$o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4531 $o .= item_get_attachment($item);
4533 $o .= item_getfeedattach($item);
4535 $mentioned = get_mentions($item);
4539 call_hooks('atom_entry', $o);
4541 $o .= '</entry>' . "\r\n";
4546 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4548 if(get_config('system','disable_embedded'))
4553 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4554 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4559 $img_start = strpos($orig_body, '[img');
4560 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4561 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4562 while( ($img_st_close !== false) && ($img_len !== false) ) {
4564 $img_st_close++; // make it point to AFTER the closing bracket
4565 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4567 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4570 if(stristr($image , $site . '/photo/')) {
4571 // Only embed locally hosted photos
4573 $i = basename($image);
4574 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4575 $x = strpos($i,'-');
4578 $res = substr($i,$x+1);
4579 $i = substr($i,0,$x);
4580 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4587 // Check to see if we should replace this photo link with an embedded image
4588 // 1. No need to do so if the photo is public
4589 // 2. If there's a contact-id provided, see if they're in the access list
4590 // for the photo. If so, embed it.
4591 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4592 // permissions, regardless of order but first check to see if they're an exact
4593 // match to save some processing overhead.
4595 if(has_permissions($r[0])) {
4597 $recips = enumerate_permissions($r[0]);
4598 if(in_array($cid, $recips)) {
4603 if(compare_permissions($item,$r[0]))
4608 $data = $r[0]['data'];
4609 $type = $r[0]['type'];
4611 // If a custom width and height were specified, apply before embedding
4612 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4613 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4615 $width = intval($match[1]);
4616 $height = intval($match[2]);
4618 $ph = new Photo($data, $type);
4619 if($ph->is_valid()) {
4620 $ph->scaleImage(max($width, $height));
4621 $data = $ph->imageString();
4622 $type = $ph->getType();
4626 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4627 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4628 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4634 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4635 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4636 if($orig_body === false)
4639 $img_start = strpos($orig_body, '[img');
4640 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4641 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4644 $new_body = $new_body . $orig_body;
4650 function has_permissions($obj) {
4651 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4656 function compare_permissions($obj1,$obj2) {
4657 // first part is easy. Check that these are exactly the same.
4658 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4659 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4660 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4661 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4664 // This is harder. Parse all the permissions and compare the resulting set.
4666 $recipients1 = enumerate_permissions($obj1);
4667 $recipients2 = enumerate_permissions($obj2);
4670 if($recipients1 == $recipients2)
4675 // returns an array of contact-ids that are allowed to see this object
4677 function enumerate_permissions($obj) {
4678 require_once('include/group.php');
4679 $allow_people = expand_acl($obj['allow_cid']);
4680 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4681 $deny_people = expand_acl($obj['deny_cid']);
4682 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4683 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4684 $deny = array_unique(array_merge($deny_people,$deny_groups));
4685 $recipients = array_diff($recipients,$deny);
4689 function item_getfeedtags($item) {
4692 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4694 for($x = 0; $x < $cnt; $x ++) {
4696 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4700 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4702 for($x = 0; $x < $cnt; $x ++) {
4704 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4710 function item_get_attachment($item) {
4712 $siteinfo = get_attached_data($item["body"]);
4714 switch($siteinfo["type"]) {
4716 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4719 $imgdata = get_photo_info($siteinfo["image"]);
4720 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
4723 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4732 function item_getfeedattach($item) {
4734 $arr = explode('[/attach],',$item['attach']);
4736 foreach($arr as $r) {
4738 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4740 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4741 if(intval($matches[2]))
4742 $ret .= 'length="' . intval($matches[2]) . '" ';
4743 if($matches[4] !== ' ')
4744 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4745 $ret .= ' />' . "\r\n";
4754 function item_expire($uid, $days, $network = "", $force = false) {
4756 if((! $uid) || ($days < 1))
4759 // $expire_network_only = save your own wall posts
4760 // and just expire conversations started by others
4762 $expire_network_only = get_pconfig($uid,'expire','network_only');
4763 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4765 if ($network != "") {
4766 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4767 // There is an index "uid_network_received" but not "uid_network_created"
4768 // This avoids the creation of another index just for one purpose.
4769 // And it doesn't really matter wether to look at "received" or "created"
4770 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4772 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4774 $r = q("SELECT * FROM `item`
4775 WHERE `uid` = %d $range
4786 $expire_items = get_pconfig($uid, 'expire','items');
4787 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4789 // Forcing expiring of items - but not notes and marked items
4791 $expire_items = true;
4793 $expire_notes = get_pconfig($uid, 'expire','notes');
4794 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4796 $expire_starred = get_pconfig($uid, 'expire','starred');
4797 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4799 $expire_photos = get_pconfig($uid, 'expire','photos');
4800 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4802 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4804 foreach($r as $item) {
4806 // don't expire filed items
4808 if(strpos($item['file'],'[') !== false)
4811 // Only expire posts, not photos and photo comments
4813 if($expire_photos==0 && strlen($item['resource-id']))
4815 if($expire_starred==0 && intval($item['starred']))
4817 if($expire_notes==0 && $item['type']=='note')
4819 if($expire_items==0 && $item['type']!='note')
4822 drop_item($item['id'],false);
4825 proc_run('php',"include/notifier.php","expire","$uid");
4830 function drop_items($items) {
4833 if(! local_user() && ! remote_user())
4837 foreach($items as $item) {
4838 $owner = drop_item($item,false);
4839 if($owner && ! $uid)
4844 // multiple threads may have been deleted, send an expire notification
4847 proc_run('php',"include/notifier.php","expire","$uid");
4851 function drop_item($id,$interactive = true) {
4855 // locate item to be deleted
4857 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4864 notice( t('Item not found.') . EOL);
4865 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4870 $owner = $item['uid'];
4874 // check if logged in user is either the author or owner of this item
4876 if(is_array($_SESSION['remote'])) {
4877 foreach($_SESSION['remote'] as $visitor) {
4878 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4879 $cid = $visitor['cid'];
4886 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4888 // Check if we should do HTML-based delete confirmation
4889 if($_REQUEST['confirm']) {
4890 // <form> can't take arguments in its "action" parameter
4891 // so add any arguments as hidden inputs
4892 $query = explode_querystring($a->query_string);
4894 foreach($query['args'] as $arg) {
4895 if(strpos($arg, 'confirm=') === false) {
4896 $arg_parts = explode('=', $arg);
4897 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4901 return replace_macros(get_markup_template('confirm.tpl'), array(
4903 '$message' => t('Do you really want to delete this item?'),
4904 '$extra_inputs' => $inputs,
4905 '$confirm' => t('Yes'),
4906 '$confirm_url' => $query['base'],
4907 '$confirm_name' => 'confirmed',
4908 '$cancel' => t('Cancel'),
4911 // Now check how the user responded to the confirmation query
4912 if($_REQUEST['canceled']) {
4913 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4916 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4919 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4920 dbesc(datetime_convert()),
4921 dbesc(datetime_convert()),
4924 create_tags_from_item($item['id']);
4925 create_files_from_item($item['id']);
4926 delete_thread($item['id'], $item['parent-uri']);
4928 // clean up categories and tags so they don't end up as orphans
4931 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4933 foreach($matches as $mtch) {
4934 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4940 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4942 foreach($matches as $mtch) {
4943 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4947 // If item is a link to a photo resource, nuke all the associated photos
4948 // (visitors will not have photo resources)
4949 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4950 // generate a resource-id and therefore aren't intimately linked to the item.
4952 if(strlen($item['resource-id'])) {
4953 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4954 dbesc($item['resource-id']),
4955 intval($item['uid'])
4957 // ignore the result
4960 // If item is a link to an event, nuke the event record.
4962 if(intval($item['event-id'])) {
4963 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4964 intval($item['event-id']),
4965 intval($item['uid'])
4967 // ignore the result
4970 // If item has attachments, drop them
4972 foreach(explode(",",$item['attach']) as $attach){
4973 preg_match("|attach/(\d+)|", $attach, $matches);
4974 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4975 intval($matches[1]),
4978 // ignore the result
4982 // clean up item_id and sign meta-data tables
4985 // Old code - caused very long queries and warning entries in the mysql logfiles:
4987 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4988 intval($item['id']),
4989 intval($item['uid'])
4992 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4993 intval($item['id']),
4994 intval($item['uid'])
4998 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
5000 // Creating list of parents
5001 $r = q("select id from item where parent = %d and uid = %d",
5002 intval($item['id']),
5003 intval($item['uid'])
5008 foreach ($r AS $row) {
5009 if ($parentid != "")
5012 $parentid .= $row["id"];
5016 if ($parentid != "") {
5017 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
5019 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
5022 // If it's the parent of a comment thread, kill all the kids
5024 if($item['uri'] == $item['parent-uri']) {
5025 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
5026 WHERE `parent-uri` = '%s' AND `uid` = %d ",
5027 dbesc(datetime_convert()),
5028 dbesc(datetime_convert()),
5029 dbesc($item['parent-uri']),
5030 intval($item['uid'])
5032 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
5033 create_files_from_itemuri($item['parent-uri'], $item['uid']);
5034 delete_thread_uri($item['parent-uri'], $item['uid']);
5035 // ignore the result
5038 // ensure that last-child is set in case the comment that had it just got wiped.
5039 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
5040 dbesc(datetime_convert()),
5041 dbesc($item['parent-uri']),
5042 intval($item['uid'])
5044 // who is the last child now?
5045 $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",
5046 dbesc($item['parent-uri']),
5047 intval($item['uid'])
5050 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
5055 // Add a relayable_retraction signature for Diaspora.
5056 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
5059 $drop_id = intval($item['id']);
5061 // send the notification upstream/downstream as the case may be
5063 proc_run('php',"include/notifier.php","drop","$drop_id");
5067 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5073 notice( t('Permission denied.') . EOL);
5074 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5081 function first_post_date($uid,$wall = false) {
5082 $r = q("select id, created from item
5083 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
5085 order by created asc limit 1",
5087 intval($wall ? 1 : 0)
5090 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
5091 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
5096 /* modified posted_dates() {below} to arrange the list in years */
5097 function list_post_dates($uid, $wall) {
5098 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5100 $dthen = first_post_date($uid, $wall);
5104 // Set the start and end date to the beginning of the month
5105 $dnow = substr($dnow,0,8).'01';
5106 $dthen = substr($dthen,0,8).'01';
5110 // Starting with the current month, get the first and last days of every
5111 // month down to and including the month of the first post
5112 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5113 $dyear = intval(substr($dnow,0,4));
5114 $dstart = substr($dnow,0,8) . '01';
5115 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5116 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5117 $end_month = datetime_convert('','',$dend,'Y-m-d');
5118 $str = day_translate(datetime_convert('','',$dnow,'F'));
5120 $ret[$dyear] = array();
5121 $ret[$dyear][] = array($str,$end_month,$start_month);
5122 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5127 function posted_dates($uid,$wall) {
5128 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5130 $dthen = first_post_date($uid,$wall);
5134 // Set the start and end date to the beginning of the month
5135 $dnow = substr($dnow,0,8).'01';
5136 $dthen = substr($dthen,0,8).'01';
5139 // Starting with the current month, get the first and last days of every
5140 // month down to and including the month of the first post
5141 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5142 $dstart = substr($dnow,0,8) . '01';
5143 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5144 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5145 $end_month = datetime_convert('','',$dend,'Y-m-d');
5146 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5147 $ret[] = array($str,$end_month,$start_month);
5148 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5154 function posted_date_widget($url,$uid,$wall) {
5157 if(! feature_enabled($uid,'archives'))
5160 // For former Facebook folks that left because of "timeline"
5162 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5165 $visible_years = get_pconfig($uid,'system','archive_visible_years');
5166 if(! $visible_years)
5169 $ret = list_post_dates($uid,$wall);
5174 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5175 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5177 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5178 '$title' => t('Archives'),
5179 '$size' => $visible_years,
5180 '$cutoff_year' => $cutoff_year,
5181 '$cutoff' => $cutoff,
5184 '$showmore' => t('show more')
5190 function store_diaspora_retract_sig($item, $user, $baseurl) {
5191 // Note that we can't add a target_author_signature
5192 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5193 // the comment, that means we're the home of the post, and Diaspora will only
5194 // check the parent_author_signature of retractions that it doesn't have to relay further
5196 // I don't think this function gets called for an "unlike," but I'll check anyway
5198 $enabled = intval(get_config('system','diaspora_enabled'));
5200 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5204 logger('drop_item: storing diaspora retraction signature');
5206 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5208 if(local_user() == $item['uid']) {
5210 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5211 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5214 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5215 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5218 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5219 // only handles DFRN deletes
5220 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5221 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5222 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5228 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5229 intval($item['id']),
5230 dbesc($signed_text),