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 `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
1291 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
1292 dbesc(normalise_link($arr['author-link'])),
1297 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
1298 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
1299 dbesc(normalise_link($arr['author-link']))
1303 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1304 intval($arr['contact-id']),
1309 $arr['network'] = $r[0]["network"];
1311 // Fallback to friendica (why is it empty in some cases?)
1312 if ($arr['network'] == "")
1313 $arr['network'] = NETWORK_DFRN;
1315 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
1318 if ($arr['guid'] != "") {
1319 // Checking if there is already an item with the same guid
1320 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
1321 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
1322 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
1325 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
1330 // Check for hashtags in the body and repair or add hashtag links
1331 item_body_set_hashtags($arr);
1333 $arr['thr-parent'] = $arr['parent-uri'];
1334 if($arr['parent-uri'] === $arr['uri']) {
1336 $parent_deleted = 0;
1337 $allow_cid = $arr['allow_cid'];
1338 $allow_gid = $arr['allow_gid'];
1339 $deny_cid = $arr['deny_cid'];
1340 $deny_gid = $arr['deny_gid'];
1341 $notify_type = 'wall-new';
1345 // find the parent and snarf the item id and ACLs
1346 // and anything else we need to inherit
1348 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
1349 dbesc($arr['parent-uri']),
1355 // is the new message multi-level threaded?
1356 // even though we don't support it now, preserve the info
1357 // and re-attach to the conversation parent.
1359 if($r[0]['uri'] != $r[0]['parent-uri']) {
1360 $arr['parent-uri'] = $r[0]['parent-uri'];
1361 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
1362 ORDER BY `id` ASC LIMIT 1",
1363 dbesc($r[0]['parent-uri']),
1364 dbesc($r[0]['parent-uri']),
1371 $parent_id = $r[0]['id'];
1372 $parent_deleted = $r[0]['deleted'];
1373 $allow_cid = $r[0]['allow_cid'];
1374 $allow_gid = $r[0]['allow_gid'];
1375 $deny_cid = $r[0]['deny_cid'];
1376 $deny_gid = $r[0]['deny_gid'];
1377 $arr['wall'] = $r[0]['wall'];
1378 $notify_type = 'comment-new';
1380 // if the parent is private, force privacy for the entire conversation
1381 // This differs from the above settings as it subtly allows comments from
1382 // email correspondents to be private even if the overall thread is not.
1384 if($r[0]['private'])
1385 $arr['private'] = $r[0]['private'];
1387 // Edge case. We host a public forum that was originally posted to privately.
1388 // The original author commented, but as this is a comment, the permissions
1389 // weren't fixed up so it will still show the comment as private unless we fix it here.
1391 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
1392 $arr['private'] = 0;
1395 // If its a post from myself then tag the thread as "mention"
1396 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
1397 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
1400 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1401 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
1402 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
1403 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
1404 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
1410 // Allow one to see reply tweets from status.net even when
1411 // we don't have or can't see the original post.
1414 logger('item_store: $force_parent=true, reply converted to top-level post.');
1416 $arr['parent-uri'] = $arr['uri'];
1417 $arr['gravity'] = 0;
1420 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
1424 $parent_deleted = 0;
1428 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
1430 dbesc($arr['network']),
1433 if($r && count($r)) {
1434 logger('duplicated item with the same uri found. ' . print_r($arr,true));
1438 // Check for an existing post with the same content. There seems to be a problem with OStatus.
1439 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
1440 dbesc($arr['body']),
1441 dbesc($arr['network']),
1442 dbesc($arr['created']),
1443 intval($arr['contact-id']),
1446 if($r && count($r)) {
1447 logger('duplicated item with the same body found. ' . print_r($arr,true));
1451 // Is this item available in the global items (with uid=0)?
1452 if ($arr["uid"] == 0) {
1453 $arr["global"] = true;
1455 q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
1457 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
1459 $arr["global"] = (count($isglobal) > 0);
1462 // Fill the cache field
1463 put_item_in_cache($arr);
1466 call_hooks('post_local',$arr);
1468 call_hooks('post_remote',$arr);
1470 if(x($arr,'cancel')) {
1471 logger('item_store: post cancelled by plugin.');
1475 // Store the unescaped version
1480 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
1482 $r = dbq("INSERT INTO `item` (`"
1483 . implode("`, `", array_keys($arr))
1485 . implode("', '", array_values($arr))
1491 // find the item we just created
1492 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC ",
1499 // Store the guid and other relevant data
1502 $current_post = $r[0]['id'];
1503 logger('item_store: created item ' . $current_post);
1505 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
1506 // This can be used to filter for inactive contacts.
1507 // Only do this for public postings to avoid privacy problems, since poco data is public.
1508 // Don't set this value if it isn't from the owner (could be an author that we don't know)
1510 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
1512 // Is it a forum? Then we don't care about the rules from above
1513 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
1514 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
1515 intval($arr['contact-id']));
1521 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
1522 dbesc($arr['received']),
1523 dbesc($arr['received']),
1524 intval($arr['contact-id'])
1527 logger('item_store: could not locate created item');
1531 logger('item_store: duplicated post occurred. Removing duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
1532 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `id` != %d ",
1534 intval($arr['uid']),
1535 intval($current_post)
1539 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
1540 $parent_id = $current_post;
1542 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
1545 $private = $arr['private'];
1547 // Set parent id - and also make sure to inherit the parent's ACLs.
1549 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
1550 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
1557 intval($parent_deleted),
1558 intval($current_post)
1561 $arr['id'] = $current_post;
1562 $arr['parent'] = $parent_id;
1563 $arr['allow_cid'] = $allow_cid;
1564 $arr['allow_gid'] = $allow_gid;
1565 $arr['deny_cid'] = $deny_cid;
1566 $arr['deny_gid'] = $deny_gid;
1567 $arr['private'] = $private;
1568 $arr['deleted'] = $parent_deleted;
1570 // update the commented timestamp on the parent
1571 // Only update "commented" if it is really a comment
1572 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
1573 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
1574 dbesc(datetime_convert()),
1575 dbesc(datetime_convert()),
1579 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
1580 dbesc(datetime_convert()),
1585 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
1586 intval($current_post),
1587 dbesc($dsprsig->signed_text),
1588 dbesc($dsprsig->signature),
1589 dbesc($dsprsig->signer)
1595 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
1598 if($arr['last-child']) {
1599 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
1601 intval($arr['uid']),
1602 intval($current_post)
1606 $deleted = tag_deliver($arr['uid'],$current_post);
1608 // current post can be deleted if is for a community page and no mention are
1610 if (!$deleted AND !$dontcache) {
1612 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
1613 if (count($r) == 1) {
1615 call_hooks('post_local_end', $r[0]);
1617 call_hooks('post_remote_end', $r[0]);
1619 logger('item_store: new item not found in DB, id ' . $current_post);
1622 // Add every contact of the post to the global contact table
1625 create_tags_from_item($current_post);
1626 create_files_from_item($current_post);
1628 // Only check for notifications on start posts
1629 if ($arr['parent-uri'] === $arr['uri']) {
1630 add_thread($current_post);
1631 logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1633 // Send a notification for every new post?
1634 $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
1635 intval($arr['contact-id']),
1638 $send_notification = count($r);
1640 if (!$send_notification) {
1641 $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
1642 intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
1645 foreach ($tags AS $tag) {
1646 $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
1647 normalise_link($tag["url"]), intval($arr['uid']));
1649 $send_notification = true;
1654 if ($send_notification) {
1655 logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1656 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1657 intval($arr['uid']));
1659 $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
1660 intval($current_post),
1666 require_once('include/enotify.php');
1668 'type' => NOTIFY_SHARE,
1669 'notify_flags' => $u[0]['notify-flags'],
1670 'language' => $u[0]['language'],
1671 'to_name' => $u[0]['username'],
1672 'to_email' => $u[0]['email'],
1673 'uid' => $u[0]['uid'],
1675 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
1676 'source_name' => $item[0]['author-name'],
1677 'source_link' => $item[0]['author-link'],
1678 'source_photo' => $item[0]['author-avatar'],
1679 'verb' => ACTIVITY_TAG,
1681 'parent' => $arr['parent']
1683 logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
1686 update_thread($parent_id);
1687 add_shadow_entry($arr);
1691 proc_run('php', "include/notifier.php", $notify_type, $current_post);
1693 return $current_post;
1696 function item_body_set_hashtags(&$item) {
1698 $tags = get_tags($item["body"]);
1704 // This sorting is important when there are hashtags that are part of other hashtags
1705 // Otherwise there could be problems with hashtags like #test and #test2
1710 $URLSearchString = "^\[\]";
1712 // All hashtags should point to the home server
1713 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1714 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
1716 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1717 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
1719 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
1720 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1722 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
1725 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
1727 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
1730 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
1732 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1735 // Repair recursive urls
1736 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1737 "#$2", $item["body"]);
1740 foreach($tags as $tag) {
1741 if(strpos($tag,'#') !== 0)
1744 if(strpos($tag,'[url='))
1747 $basetag = str_replace('_',' ',substr($tag,1));
1749 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1751 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1753 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1754 if(strlen($item["tag"]))
1755 $item["tag"] = ','.$item["tag"];
1756 $item["tag"] = $newtag.$item["tag"];
1760 // Convert back the masked hashtags
1761 $item["body"] = str_replace("#", "#", $item["body"]);
1764 function get_item_guid($id) {
1765 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1767 return($r[0]["guid"]);
1772 function get_item_id($guid, $uid = 0) {
1778 $uid == local_user();
1780 // Does the given user have this item?
1782 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1783 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1784 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1787 $nick = $r[0]["nickname"];
1791 // Or is it anywhere on the server?
1793 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1794 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1795 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1796 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1797 AND `item`.`private` = 0 AND `item`.`wall` = 1
1798 AND `item`.`guid` = '%s'", dbesc($guid));
1801 $nick = $r[0]["nickname"];
1804 return(array("nick" => $nick, "id" => $id));
1808 function get_item_contact($item,$contacts) {
1809 if(! count($contacts) || (! is_array($item)))
1811 foreach($contacts as $contact) {
1812 if($contact['id'] == $item['contact-id']) {
1814 break; // NOTREACHED
1821 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1823 * @param int $item_id
1824 * @return bool true if item was deleted, else false
1826 function tag_deliver($uid,$item_id) {
1834 $u = q("select * from user where uid = %d limit 1",
1840 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1841 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1844 $i = q("select * from item where id = %d and uid = %d limit 1",
1853 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1855 // Diaspora uses their own hardwired link URL in @-tags
1856 // instead of the one we supply with webfinger
1858 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1860 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1862 foreach($matches as $mtch) {
1863 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1865 logger('tag_deliver: mention found: ' . $mtch[2]);
1871 if ( ($community_page || $prvgroup) &&
1872 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1873 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1875 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1876 q("DELETE FROM item WHERE id = %d and uid = %d",
1886 // send a notification
1888 // use a local photo if we have one
1890 $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
1891 intval($u[0]['uid']),
1892 dbesc(normalise_link($item['author-link']))
1894 $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
1897 require_once('include/enotify.php');
1899 'type' => NOTIFY_TAGSELF,
1900 'notify_flags' => $u[0]['notify-flags'],
1901 'language' => $u[0]['language'],
1902 'to_name' => $u[0]['username'],
1903 'to_email' => $u[0]['email'],
1904 'uid' => $u[0]['uid'],
1906 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
1907 'source_name' => $item['author-name'],
1908 'source_link' => $item['author-link'],
1909 'source_photo' => $photo,
1910 'verb' => ACTIVITY_TAG,
1912 'parent' => $item['parent']
1916 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1918 call_hooks('tagged', $arr);
1920 if((! $community_page) && (! $prvgroup))
1924 // tgroup delivery - setup a second delivery chain
1925 // prevent delivery looping - only proceed
1926 // if the message originated elsewhere and is a top-level post
1928 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1931 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1934 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1935 intval($u[0]['uid'])
1940 // also reset all the privacy bits to the forum default permissions
1942 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1944 $forum_mode = (($prvgroup) ? 2 : 1);
1946 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1947 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1948 intval($forum_mode),
1949 dbesc($c[0]['name']),
1950 dbesc($c[0]['url']),
1951 dbesc($c[0]['thumb']),
1953 dbesc($u[0]['allow_cid']),
1954 dbesc($u[0]['allow_gid']),
1955 dbesc($u[0]['deny_cid']),
1956 dbesc($u[0]['deny_gid']),
1959 update_thread($item_id);
1961 proc_run('php','include/notifier.php','tgroup',$item_id);
1967 function tgroup_check($uid,$item) {
1973 // check that the message originated elsewhere and is a top-level post
1975 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1979 $u = q("select * from user where uid = %d limit 1",
1985 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1986 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1989 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1991 // Diaspora uses their own hardwired link URL in @-tags
1992 // instead of the one we supply with webfinger
1994 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1996 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1998 foreach($matches as $mtch) {
1999 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
2001 logger('tgroup_check: mention found: ' . $mtch[2]);
2009 if((! $community_page) && (! $prvgroup))
2023 function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
2027 $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
2029 if($contact['duplex'] && $contact['dfrn-id'])
2030 $idtosend = '0:' . $orig_id;
2031 if($contact['duplex'] && $contact['issued-id'])
2032 $idtosend = '1:' . $orig_id;
2035 $rino = get_config('system','rino_encrypt');
2036 $rino = intval($rino);
2037 // use RINO1 if mcrypt isn't installed and RINO2 was selected
2038 if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
2040 logger("Local rino version: ". $rino, LOGGER_DEBUG);
2042 $ssl_val = intval(get_config('system','ssl_policy'));
2046 case SSL_POLICY_FULL:
2047 $ssl_policy = 'full';
2049 case SSL_POLICY_SELFSIGN:
2050 $ssl_policy = 'self';
2052 case SSL_POLICY_NONE:
2054 $ssl_policy = 'none';
2058 $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
2060 logger('dfrn_deliver: ' . $url);
2062 $xml = fetch_url($url);
2064 $curl_stat = $a->get_curl_code();
2066 return(-1); // timed out
2068 logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
2073 if(strpos($xml,'<?xml') === false) {
2074 logger('dfrn_deliver: no valid XML returned');
2075 logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
2079 $res = parse_xml_string($xml);
2081 if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
2082 return (($res->status) ? $res->status : 3);
2084 $postvars = array();
2085 $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
2086 $challenge = hex2bin((string) $res->challenge);
2087 $perm = (($res->perm) ? $res->perm : null);
2088 $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
2089 $rino_remote_version = intval($res->rino);
2090 $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
2092 logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG);
2094 if($owner['page-flags'] == PAGE_PRVGROUP)
2097 $final_dfrn_id = '';
2100 if((($perm == 'rw') && (! intval($contact['writable'])))
2101 || (($perm == 'r') && (intval($contact['writable'])))) {
2102 q("update contact set writable = %d where id = %d",
2103 intval(($perm == 'rw') ? 1 : 0),
2104 intval($contact['id'])
2106 $contact['writable'] = (string) 1 - intval($contact['writable']);
2110 if(($contact['duplex'] && strlen($contact['pubkey']))
2111 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2112 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2113 openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
2114 openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
2117 openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
2118 openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
2121 $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
2123 if(strpos($final_dfrn_id,':') == 1)
2124 $final_dfrn_id = substr($final_dfrn_id,2);
2126 if($final_dfrn_id != $orig_id) {
2127 logger('dfrn_deliver: wrong dfrn_id.');
2128 // did not decode properly - cannot trust this site
2132 $postvars['dfrn_id'] = $idtosend;
2133 $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
2135 $postvars['dissolve'] = '1';
2138 if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2139 $postvars['data'] = $atom;
2140 $postvars['perm'] = 'rw';
2143 $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
2144 $postvars['perm'] = 'r';
2147 $postvars['ssl_policy'] = $ssl_policy;
2150 $postvars['page'] = $page;
2153 if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
2154 logger('rino version: '. $rino_remote_version);
2156 switch($rino_remote_version) {
2158 // Deprecated rino version!
2159 $key = substr(random_string(),0,16);
2160 $data = aes_encrypt($postvars['data'],$key);
2163 // RINO 2 based on php-encryption
2165 $key = Crypto::createNewRandomKey();
2166 } catch (CryptoTestFailed $ex) {
2167 logger('Cannot safely create a key');
2169 } catch (CannotPerformOperation $ex) {
2170 logger('Cannot safely create a key');
2174 $data = Crypto::encrypt($postvars['data'], $key);
2175 } catch (CryptoTestFailed $ex) {
2176 logger('Cannot safely perform encryption');
2178 } catch (CannotPerformOperation $ex) {
2179 logger('Cannot safely perform encryption');
2184 logger("rino: invalid requested verision '$rino_remote_version'");
2188 $postvars['rino'] = $rino_remote_version;
2189 $postvars['data'] = bin2hex($data);
2191 #logger('rino: sent key = ' . $key, LOGGER_DEBUG);
2194 if($dfrn_version >= 2.1) {
2195 if(($contact['duplex'] && strlen($contact['pubkey']))
2196 || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
2197 || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
2199 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2202 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2206 if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
2207 openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
2210 openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
2214 logger('md5 rawkey ' . md5($postvars['key']));
2216 $postvars['key'] = bin2hex($postvars['key']);
2220 logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
2222 $xml = post_url($contact['notify'],$postvars);
2224 logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
2226 $curl_stat = $a->get_curl_code();
2227 if((! $curl_stat) || (! strlen($xml)))
2228 return(-1); // timed out
2230 if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
2233 if(strpos($xml,'<?xml') === false) {
2234 logger('dfrn_deliver: phase 2: no valid XML returned');
2235 logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
2239 if($contact['term-date'] != '0000-00-00 00:00:00') {
2240 logger("dfrn_deliver: $url back from the dead - removing mark for death");
2241 require_once('include/Contact.php');
2242 unmark_for_death($contact);
2245 $res = parse_xml_string($xml);
2247 return $res->status;
2252 This function returns true if $update has an edited timestamp newer
2253 than $existing, i.e. $update contains new data which should override
2254 what's already there. If there is no timestamp yet, the update is
2255 assumed to be newer. If the update has no timestamp, the existing
2256 item is assumed to be up-to-date. If the timestamps are equal it
2257 assumes the update has been seen before and should be ignored.
2259 function edited_timestamp_is_newer($existing, $update) {
2260 if (!x($existing,'edited') || !$existing['edited']) {
2263 if (!x($update,'edited') || !$update['edited']) {
2266 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
2267 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
2268 return (strcmp($existing_edited, $update_edited) < 0);
2273 * consume_feed - process atom feed and update anything/everything we might need to update
2275 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
2277 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
2278 * It is this person's stuff that is going to be updated.
2279 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
2280 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
2281 * have a contact record.
2282 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
2283 * might not) try and subscribe to it.
2284 * $datedir sorts in reverse order
2285 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
2286 * imported prior to its children being seen in the stream unless we are certain
2287 * of how the feed is arranged/ordered.
2288 * With $pass = 1, we only pull parent items out of the stream.
2289 * With $pass = 2, we only pull children (comments/likes).
2291 * So running this twice, first with pass 1 and then with pass 2 will do the right
2292 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
2293 * model where comments can have sub-threads. That would require some massive sorting
2294 * to get all the feed items into a mostly linear ordering, and might still require
2298 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
2299 if ($contact['network'] === NETWORK_OSTATUS) {
2301 logger("Consume OStatus messages ", LOGGER_DEBUG);
2302 ostatus_import($xml,$importer,$contact, $hub);
2307 if ($contact['network'] === NETWORK_FEED) {
2309 logger("Consume feeds", LOGGER_DEBUG);
2310 feed_import($xml,$importer,$contact, $hub);
2315 require_once('library/simplepie/simplepie.inc');
2316 require_once('include/contact_selectors.php');
2318 if(! strlen($xml)) {
2319 logger('consume_feed: empty input');
2323 $feed = new SimplePie();
2324 $feed->set_raw_data($xml);
2326 $feed->enable_order_by_date(true);
2328 $feed->enable_order_by_date(false);
2332 logger('consume_feed: Error parsing XML: ' . $feed->error());
2334 $permalink = $feed->get_permalink();
2336 // Check at the feed level for updated contact name and/or photo
2340 $photo_timestamp = '';
2343 $contact_updated = '';
2345 $hubs = $feed->get_links('hub');
2346 logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
2349 $hub = implode(',', $hubs);
2351 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
2353 $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
2355 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
2356 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
2357 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
2358 $new_name = $elems['name'][0]['data'];
2360 // Manually checking for changed contact names
2361 if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
2362 $name_updated = date("c");
2363 $photo_timestamp = date("c");
2366 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
2367 if ($photo_timestamp == "")
2368 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
2369 $photo_url = $elems['link'][0]['attribs']['']['href'];
2372 if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
2373 $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
2377 if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
2378 logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
2380 $contact_updated = $photo_timestamp;
2382 require_once("include/Photo.php");
2383 $photo_failure = false;
2384 $have_photo = false;
2386 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
2387 intval($contact['id']),
2388 intval($contact['uid'])
2391 $resource_id = $r[0]['resource-id'];
2395 $resource_id = photo_new_resource();
2398 $img_str = fetch_url($photo_url,true);
2399 // guess mimetype from headers or filename
2400 $type = guess_image_type($photo_url,true);
2403 $img = new Photo($img_str, $type);
2404 if($img->is_valid()) {
2406 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
2407 dbesc($resource_id),
2408 intval($contact['id']),
2409 intval($contact['uid'])
2413 $img->scaleImageSquare(175);
2415 $hash = $resource_id;
2416 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 4);
2418 $img->scaleImage(80);
2419 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 5);
2421 $img->scaleImage(48);
2422 $r = $img->store($contact['uid'], $contact['id'], $hash, basename($photo_url), 'Contact Photos', 6);
2426 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
2427 WHERE `uid` = %d AND `id` = %d",
2428 dbesc(datetime_convert()),
2429 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
2430 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
2431 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
2432 intval($contact['uid']),
2433 intval($contact['id'])
2438 if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
2439 if ($name_updated > $contact_updated)
2440 $contact_updated = $name_updated;
2442 $r = q("select * from contact where uid = %d and id = %d limit 1",
2443 intval($contact['uid']),
2444 intval($contact['id'])
2447 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
2448 dbesc(notags(trim($new_name))),
2449 dbesc(datetime_convert()),
2450 intval($contact['uid']),
2451 intval($contact['id'])
2454 // do our best to update the name on content items
2457 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
2458 dbesc(notags(trim($new_name))),
2459 dbesc($r[0]['name']),
2460 dbesc($r[0]['url']),
2461 intval($contact['uid'])
2466 if ($contact_updated AND $new_name AND $photo_url)
2467 poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
2469 if(strlen($birthday)) {
2470 if(substr($birthday,0,4) != $contact['bdyear']) {
2471 logger('consume_feed: updating birthday: ' . $birthday);
2475 * Add new birthday event for this person
2477 * $bdtext is just a readable placeholder in case the event is shared
2478 * with others. We will replace it during presentation to our $importer
2479 * to contain a sparkle link and perhaps a photo.
2483 $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
2484 $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
2487 $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
2488 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
2489 intval($contact['uid']),
2490 intval($contact['id']),
2491 dbesc(datetime_convert()),
2492 dbesc(datetime_convert()),
2493 dbesc(datetime_convert('UTC','UTC', $birthday)),
2494 dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
2503 q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
2504 dbesc(substr($birthday,0,4)),
2505 intval($contact['uid']),
2506 intval($contact['id'])
2509 // This function is called twice without reloading the contact
2510 // Make sure we only create one event. This is why &$contact
2511 // is a reference var in this function
2513 $contact['bdyear'] = substr($birthday,0,4);
2517 $community_page = 0;
2518 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
2520 $community_page = intval($rawtags[0]['data']);
2522 if(is_array($contact) && intval($contact['forum']) != $community_page) {
2523 q("update contact set forum = %d where id = %d",
2524 intval($community_page),
2525 intval($contact['id'])
2527 $contact['forum'] = (string) $community_page;
2531 // process any deleted entries
2533 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
2534 if(is_array($del_entries) && count($del_entries) && $pass != 2) {
2535 foreach($del_entries as $dentry) {
2537 if(isset($dentry['attribs']['']['ref'])) {
2538 $uri = $dentry['attribs']['']['ref'];
2540 if(isset($dentry['attribs']['']['when'])) {
2541 $when = $dentry['attribs']['']['when'];
2542 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
2545 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
2547 if($deleted && is_array($contact)) {
2548 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
2549 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
2551 intval($importer['uid']),
2552 intval($contact['id'])
2557 if(! $item['deleted'])
2558 logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
2560 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2561 $xo = parse_xml_string($item['object'],false);
2562 $xt = parse_xml_string($item['target'],false);
2563 if($xt->type === ACTIVITY_OBJ_NOTE) {
2564 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
2566 intval($importer['importer_uid'])
2570 // For tags, the owner cannot remove the tag on the author's copy of the post.
2572 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
2573 $author_remove = (($item['origin'] && $item['self']) ? true : false);
2574 $author_copy = (($item['origin']) ? true : false);
2576 if($owner_remove && $author_copy)
2578 if($author_remove || $owner_remove) {
2579 $tags = explode(',',$i[0]['tag']);
2582 foreach($tags as $tag)
2583 if(trim($tag) !== trim($xo->body))
2584 $newtags[] = trim($tag);
2586 q("update item set tag = '%s' where id = %d",
2587 dbesc(implode(',',$newtags)),
2590 create_tags_from_item($i[0]['id']);
2596 if($item['uri'] == $item['parent-uri']) {
2597 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2598 `body` = '', `title` = ''
2599 WHERE `parent-uri` = '%s' AND `uid` = %d",
2601 dbesc(datetime_convert()),
2602 dbesc($item['uri']),
2603 intval($importer['uid'])
2605 create_tags_from_itemuri($item['uri'], $importer['uid']);
2606 create_files_from_itemuri($item['uri'], $importer['uid']);
2607 update_thread_uri($item['uri'], $importer['uid']);
2610 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
2611 `body` = '', `title` = ''
2612 WHERE `uri` = '%s' AND `uid` = %d",
2614 dbesc(datetime_convert()),
2616 intval($importer['uid'])
2618 create_tags_from_itemuri($uri, $importer['uid']);
2619 create_files_from_itemuri($uri, $importer['uid']);
2620 if($item['last-child']) {
2621 // ensure that last-child is set in case the comment that had it just got wiped.
2622 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2623 dbesc(datetime_convert()),
2624 dbesc($item['parent-uri']),
2625 intval($item['uid'])
2627 // who is the last child now?
2628 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
2629 ORDER BY `created` DESC LIMIT 1",
2630 dbesc($item['parent-uri']),
2631 intval($importer['uid'])
2634 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2645 // Now process the feed
2647 if($feed->get_item_quantity()) {
2649 logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
2651 // in inverse date order
2653 $items = array_reverse($feed->get_items());
2655 $items = $feed->get_items();
2658 foreach($items as $item) {
2661 $item_id = $item->get_id();
2662 $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
2663 if(isset($rawthread[0]['attribs']['']['ref'])) {
2665 $parent_uri = $rawthread[0]['attribs']['']['ref'];
2668 if(($is_reply) && is_array($contact)) {
2673 // not allowed to post
2675 if($contact['rel'] == CONTACT_IS_FOLLOWER)
2679 // Have we seen it? If not, import it.
2681 $item_id = $item->get_id();
2682 $datarray = get_atom_elements($feed, $item, $contact);
2684 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2685 $datarray['author-name'] = $contact['name'];
2686 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2687 $datarray['author-link'] = $contact['url'];
2688 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2689 $datarray['author-avatar'] = $contact['thumb'];
2691 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2692 logger('consume_feed: no author information! ' . print_r($datarray,true));
2696 $force_parent = false;
2697 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2698 if($contact['network'] === NETWORK_OSTATUS)
2699 $force_parent = true;
2700 if(strlen($datarray['title']))
2701 unset($datarray['title']);
2702 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2703 dbesc(datetime_convert()),
2705 intval($importer['uid'])
2707 $datarray['last-child'] = 1;
2708 update_thread_uri($parent_uri, $importer['uid']);
2712 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2714 intval($importer['uid'])
2717 // Update content if 'updated' changes
2720 if (edited_timestamp_is_newer($r[0], $datarray)) {
2722 // do not accept (ignore) an earlier edit than one we currently have.
2723 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2726 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2727 dbesc($datarray['title']),
2728 dbesc($datarray['body']),
2729 dbesc($datarray['tag']),
2730 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2731 dbesc(datetime_convert()),
2733 intval($importer['uid'])
2735 create_tags_from_itemuri($item_id, $importer['uid']);
2736 update_thread_uri($item_id, $importer['uid']);
2739 // update last-child if it changes
2741 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2742 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
2743 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
2744 dbesc(datetime_convert()),
2746 intval($importer['uid'])
2748 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2749 intval($allow[0]['data']),
2750 dbesc(datetime_convert()),
2752 intval($importer['uid'])
2754 update_thread_uri($item_id, $importer['uid']);
2760 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2761 // one way feed - no remote comment ability
2762 $datarray['last-child'] = 0;
2764 $datarray['parent-uri'] = $parent_uri;
2765 $datarray['uid'] = $importer['uid'];
2766 $datarray['contact-id'] = $contact['id'];
2767 if((activity_match($datarray['verb'],ACTIVITY_LIKE)) || (activity_match($datarray['verb'],ACTIVITY_DISLIKE))) {
2768 $datarray['type'] = 'activity';
2769 $datarray['gravity'] = GRAVITY_LIKE;
2770 // only one like or dislike per person
2771 // splitted into two queries for performance issues
2772 $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",
2773 intval($datarray['uid']),
2774 intval($datarray['contact-id']),
2775 dbesc($datarray['verb']),
2781 $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",
2782 intval($datarray['uid']),
2783 intval($datarray['contact-id']),
2784 dbesc($datarray['verb']),
2791 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
2792 $xo = parse_xml_string($datarray['object'],false);
2793 $xt = parse_xml_string($datarray['target'],false);
2795 if($xt->type == ACTIVITY_OBJ_NOTE) {
2796 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
2798 intval($importer['importer_uid'])
2803 // extract tag, if not duplicate, add to parent item
2804 if($xo->id && $xo->content) {
2805 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
2806 if(! (stristr($r[0]['tag'],$newtag))) {
2807 q("UPDATE item SET tag = '%s' WHERE id = %d",
2808 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
2811 create_tags_from_item($r[0]['id']);
2817 $r = item_store($datarray,$force_parent);
2823 // Head post of a conversation. Have we seen it? If not, import it.
2825 $item_id = $item->get_id();
2827 $datarray = get_atom_elements($feed, $item, $contact);
2829 if(is_array($contact)) {
2830 if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
2831 $datarray['author-name'] = $contact['name'];
2832 if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
2833 $datarray['author-link'] = $contact['url'];
2834 if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
2835 $datarray['author-avatar'] = $contact['thumb'];
2838 if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
2839 logger('consume_feed: no author information! ' . print_r($datarray,true));
2843 // special handling for events
2845 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
2846 $ev = bbtoevent($datarray['body']);
2847 if(x($ev,'desc') && x($ev,'start')) {
2848 $ev['uid'] = $importer['uid'];
2849 $ev['uri'] = $item_id;
2850 $ev['edited'] = $datarray['edited'];
2851 $ev['private'] = $datarray['private'];
2853 if(is_array($contact))
2854 $ev['cid'] = $contact['id'];
2855 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2857 intval($importer['uid'])
2860 $ev['id'] = $r[0]['id'];
2861 $xyz = event_store($ev);
2866 if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
2867 if(strlen($datarray['title']))
2868 unset($datarray['title']);
2869 $datarray['last-child'] = 1;
2873 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
2875 intval($importer['uid'])
2878 // Update content if 'updated' changes
2881 if (edited_timestamp_is_newer($r[0], $datarray)) {
2883 // do not accept (ignore) an earlier edit than one we currently have.
2884 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
2887 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2888 dbesc($datarray['title']),
2889 dbesc($datarray['body']),
2890 dbesc($datarray['tag']),
2891 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
2892 dbesc(datetime_convert()),
2894 intval($importer['uid'])
2896 create_tags_from_itemuri($item_id, $importer['uid']);
2897 update_thread_uri($item_id, $importer['uid']);
2900 // update last-child if it changes
2902 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
2903 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
2904 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
2905 intval($allow[0]['data']),
2906 dbesc(datetime_convert()),
2908 intval($importer['uid'])
2910 update_thread_uri($item_id, $importer['uid']);
2915 if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
2916 logger('consume-feed: New follower');
2917 new_follower($importer,$contact,$datarray,$item);
2920 if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) {
2921 lose_follower($importer,$contact,$datarray,$item);
2925 if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
2926 logger('consume-feed: New friend request');
2927 new_follower($importer,$contact,$datarray,$item,true);
2930 if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) {
2931 lose_sharer($importer,$contact,$datarray,$item);
2936 if(! is_array($contact))
2940 if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
2941 // one way feed - no remote comment ability
2942 $datarray['last-child'] = 0;
2944 if($contact['network'] === NETWORK_FEED)
2945 $datarray['private'] = 2;
2947 $datarray['parent-uri'] = $item_id;
2948 $datarray['uid'] = $importer['uid'];
2949 $datarray['contact-id'] = $contact['id'];
2951 if(! link_compare($datarray['owner-link'],$contact['url'])) {
2952 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
2953 // but otherwise there's a possible data mixup on the sender's system.
2954 // the tgroup delivery code called from item_store will correct it if it's a forum,
2955 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
2956 logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
2957 $datarray['owner-name'] = $contact['name'];
2958 $datarray['owner-link'] = $contact['url'];
2959 $datarray['owner-avatar'] = $contact['thumb'];
2962 // We've allowed "followers" to reach this point so we can decide if they are
2963 // posting an @-tag delivery, which followers are allowed to do for certain
2964 // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
2966 if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
2969 // This is my contact on another system, but it's really me.
2970 // Turn this into a wall post.
2971 $notify = item_is_remote_self($contact, $datarray);
2973 $r = item_store($datarray, false, $notify);
2974 logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
2982 function item_is_remote_self($contact, &$datarray) {
2985 if (!$contact['remote_self'])
2988 // Prevent the forwarding of posts that are forwarded
2989 if ($datarray["extid"] == NETWORK_DFRN)
2992 // Prevent to forward already forwarded posts
2993 if ($datarray["app"] == $a->get_hostname())
2996 // Only forward posts
2997 if ($datarray["verb"] != ACTIVITY_POST)
3000 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
3003 $datarray2 = $datarray;
3004 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
3005 if ($contact['remote_self'] == 2) {
3006 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
3007 intval($contact['uid']));
3009 $datarray['contact-id'] = $r[0]["id"];
3011 $datarray['owner-name'] = $r[0]["name"];
3012 $datarray['owner-link'] = $r[0]["url"];
3013 $datarray['owner-avatar'] = $r[0]["thumb"];
3015 $datarray['author-name'] = $datarray['owner-name'];
3016 $datarray['author-link'] = $datarray['owner-link'];
3017 $datarray['author-avatar'] = $datarray['owner-avatar'];
3020 if ($contact['network'] != NETWORK_FEED) {
3021 $datarray["guid"] = get_guid(32);
3022 unset($datarray["plink"]);
3023 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
3024 $datarray["parent-uri"] = $datarray["uri"];
3025 $datarray["extid"] = $contact['network'];
3026 $urlpart = parse_url($datarray2['author-link']);
3027 $datarray["app"] = $urlpart["host"];
3029 $datarray['private'] = 0;
3032 if ($contact['network'] != NETWORK_FEED) {
3033 // Store the original post
3034 $r = item_store($datarray2, false, false);
3035 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
3037 $datarray["app"] = "Feed";
3042 function local_delivery($importer,$data) {
3045 logger(__function__, LOGGER_TRACE);
3047 if($importer['readonly']) {
3048 // We aren't receiving stuff from this person. But we will quietly ignore them
3049 // rather than a blatant "go away" message.
3050 logger('local_delivery: ignoring');
3055 // Consume notification feed. This may differ from consuming a public feed in several ways
3056 // - might contain email or friend suggestions
3057 // - might contain remote followup to our message
3058 // - in which case we need to accept it and then notify other conversants
3059 // - we may need to send various email notifications
3061 $feed = new SimplePie();
3062 $feed->set_raw_data($data);
3063 $feed->enable_order_by_date(false);
3068 logger('local_delivery: Error parsing XML: ' . $feed->error());
3071 // Check at the feed level for updated contact name and/or photo
3075 $photo_timestamp = '';
3077 $contact_updated = '';
3080 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
3082 // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
3084 // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
3087 $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
3088 if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
3089 $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
3090 $new_name = $elems['name'][0]['data'];
3092 // Manually checking for changed contact names
3093 if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
3094 $name_updated = date("c");
3095 $photo_timestamp = date("c");
3098 if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
3099 if ($photo_timestamp == "")
3100 $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
3101 $photo_url = $elems['link'][0]['attribs']['']['href'];
3105 if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
3107 $contact_updated = $photo_timestamp;
3109 logger('local_delivery: Updating photo for ' . $importer['name']);
3110 require_once("include/Photo.php");
3111 $photo_failure = false;
3112 $have_photo = false;
3114 $r = q("SELECT `resource-id` FROM `photo` WHERE `contact-id` = %d AND `uid` = %d LIMIT 1",
3115 intval($importer['id']),
3116 intval($importer['importer_uid'])
3119 $resource_id = $r[0]['resource-id'];
3123 $resource_id = photo_new_resource();
3126 $img_str = fetch_url($photo_url,true);
3127 // guess mimetype from headers or filename
3128 $type = guess_image_type($photo_url,true);
3131 $img = new Photo($img_str, $type);
3132 if($img->is_valid()) {
3134 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `contact-id` = %d AND `uid` = %d",
3135 dbesc($resource_id),
3136 intval($importer['id']),
3137 intval($importer['importer_uid'])
3141 $img->scaleImageSquare(175);
3143 $hash = $resource_id;
3144 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 4);
3146 $img->scaleImage(80);
3147 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 5);
3149 $img->scaleImage(48);
3150 $r = $img->store($importer['importer_uid'], $importer['id'], $hash, basename($photo_url), 'Contact Photos', 6);
3154 q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
3155 WHERE `uid` = %d AND `id` = %d",
3156 dbesc(datetime_convert()),
3157 dbesc($a->get_baseurl() . '/photo/' . $hash . '-4.'.$img->getExt()),
3158 dbesc($a->get_baseurl() . '/photo/' . $hash . '-5.'.$img->getExt()),
3159 dbesc($a->get_baseurl() . '/photo/' . $hash . '-6.'.$img->getExt()),
3160 intval($importer['importer_uid']),
3161 intval($importer['id'])
3166 if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
3167 if ($name_updated > $contact_updated)
3168 $contact_updated = $name_updated;
3170 $r = q("select * from contact where uid = %d and id = %d limit 1",
3171 intval($importer['importer_uid']),
3172 intval($importer['id'])
3175 $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d",
3176 dbesc(notags(trim($new_name))),
3177 dbesc(datetime_convert()),
3178 intval($importer['importer_uid']),
3179 intval($importer['id'])
3182 // do our best to update the name on content items
3185 q("update item set `author-name` = '%s' where `author-name` = '%s' and `author-link` = '%s' and uid = %d",
3186 dbesc(notags(trim($new_name))),
3187 dbesc($r[0]['name']),
3188 dbesc($r[0]['url']),
3189 intval($importer['importer_uid'])
3194 if ($contact_updated AND $new_name AND $photo_url)
3195 poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
3197 // Currently unsupported - needs a lot of work
3198 $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
3199 if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
3200 $base = $reloc[0]['child'][NAMESPACE_DFRN];
3202 $newloc['uid'] = $importer['importer_uid'];
3203 $newloc['cid'] = $importer['id'];
3204 $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
3205 $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
3206 $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
3207 $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
3208 $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
3209 $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
3210 $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
3211 $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
3212 $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
3213 $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
3214 /** relocated user must have original key pair */
3215 /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
3216 $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
3218 logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
3221 $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
3222 intval($importer['id']),
3223 intval($importer['importer_uid']));
3228 $x = q("UPDATE contact SET
3239 `site-pubkey` = '%s'
3240 WHERE id=%d AND uid=%d;",
3241 dbesc($newloc['name']),
3242 dbesc($newloc['photo']),
3243 dbesc($newloc['thumb']),
3244 dbesc($newloc['micro']),
3245 dbesc($newloc['url']),
3246 dbesc(normalise_link($newloc['url'])),
3247 dbesc($newloc['request']),
3248 dbesc($newloc['confirm']),
3249 dbesc($newloc['notify']),
3250 dbesc($newloc['poll']),
3251 dbesc($newloc['sitepubkey']),
3252 intval($importer['id']),
3253 intval($importer['importer_uid']));
3259 'owner-link' => array($old['url'], $newloc['url']),
3260 'author-link' => array($old['url'], $newloc['url']),
3261 'owner-avatar' => array($old['photo'], $newloc['photo']),
3262 'author-avatar' => array($old['photo'], $newloc['photo']),
3264 foreach ($fields as $n=>$f){
3265 $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
3268 intval($importer['importer_uid']));
3274 // merge with current record, current contents have priority
3275 // update record, set url-updated
3276 // update profile photos
3282 // handle friend suggestion notification
3284 $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
3285 if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
3286 $base = $sugg[0]['child'][NAMESPACE_DFRN];
3288 $fsugg['uid'] = $importer['importer_uid'];
3289 $fsugg['cid'] = $importer['id'];
3290 $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
3291 $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
3292 $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
3293 $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
3294 $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
3296 // Does our member already have a friend matching this description?
3298 $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
3299 dbesc($fsugg['name']),
3300 dbesc(normalise_link($fsugg['url'])),
3301 intval($fsugg['uid'])
3306 // Do we already have an fcontact record for this person?
3309 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3310 dbesc($fsugg['url']),
3311 dbesc($fsugg['name']),
3312 dbesc($fsugg['request'])
3317 // OK, we do. Do we already have an introduction for this person ?
3318 $r = q("select id from intro where uid = %d and fid = %d limit 1",
3319 intval($fsugg['uid']),
3326 $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
3327 dbesc($fsugg['name']),
3328 dbesc($fsugg['url']),
3329 dbesc($fsugg['photo']),
3330 dbesc($fsugg['request'])
3332 $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
3333 dbesc($fsugg['url']),
3334 dbesc($fsugg['name']),
3335 dbesc($fsugg['request'])
3340 // database record did not get created. Quietly give up.
3345 $hash = random_string();
3347 $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
3348 VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
3349 intval($fsugg['uid']),
3351 intval($fsugg['cid']),
3352 dbesc($fsugg['body']),
3354 dbesc(datetime_convert()),
3359 'type' => NOTIFY_SUGGEST,
3360 'notify_flags' => $importer['notify-flags'],
3361 'language' => $importer['language'],
3362 'to_name' => $importer['username'],
3363 'to_email' => $importer['email'],
3364 'uid' => $importer['importer_uid'],
3366 'link' => $a->get_baseurl() . '/notifications/intros',
3367 'source_name' => $importer['name'],
3368 'source_link' => $importer['url'],
3369 'source_photo' => $importer['photo'],
3370 'verb' => ACTIVITY_REQ_FRIEND,
3379 $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
3380 if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
3382 logger('local_delivery: private message received');
3385 $base = $rawmail[0]['child'][NAMESPACE_DFRN];
3388 $msg['uid'] = $importer['importer_uid'];
3389 $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
3390 $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
3391 $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
3392 $msg['contact-id'] = $importer['id'];
3393 $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
3394 $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
3396 $msg['replied'] = 0;
3397 $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
3398 $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
3399 $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
3403 $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
3404 . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
3406 // send notifications.
3408 require_once('include/enotify.php');
3410 $notif_params = array(
3411 'type' => NOTIFY_MAIL,
3412 'notify_flags' => $importer['notify-flags'],
3413 'language' => $importer['language'],
3414 'to_name' => $importer['username'],
3415 'to_email' => $importer['email'],
3416 'uid' => $importer['importer_uid'],
3418 'source_name' => $msg['from-name'],
3419 'source_link' => $importer['url'],
3420 'source_photo' => $importer['thumb'],
3421 'verb' => ACTIVITY_POST,
3425 notification($notif_params);
3431 $community_page = 0;
3432 $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
3434 $community_page = intval($rawtags[0]['data']);
3436 if(intval($importer['forum']) != $community_page) {
3437 q("update contact set forum = %d where id = %d",
3438 intval($community_page),
3439 intval($importer['id'])
3441 $importer['forum'] = (string) $community_page;
3444 logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
3446 // process any deleted entries
3448 $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
3449 if(is_array($del_entries) && count($del_entries)) {
3450 foreach($del_entries as $dentry) {
3452 if(isset($dentry['attribs']['']['ref'])) {
3453 $uri = $dentry['attribs']['']['ref'];
3455 if(isset($dentry['attribs']['']['when'])) {
3456 $when = $dentry['attribs']['']['when'];
3457 $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
3460 $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
3464 // check for relayed deletes to our conversation
3467 $r = q("select * from item where uri = '%s' and uid = %d limit 1",
3469 intval($importer['importer_uid'])
3472 $parent_uri = $r[0]['parent-uri'];
3473 if($r[0]['id'] != $r[0]['parent'])
3480 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3483 logger('local_delivery: possible community delete');
3486 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3488 // was the top-level post for this reply written by somebody on this site?
3489 // Specifically, the recipient?
3491 $is_a_remote_delete = false;
3493 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3494 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3495 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3496 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3497 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3498 AND `item`.`uid` = %d
3504 intval($importer['importer_uid'])
3507 $is_a_remote_delete = true;
3509 // Does this have the characteristics of a community or private group comment?
3510 // If it's a reply to a wall post on a community/prvgroup page it's a
3511 // valid community comment. Also forum_mode makes it valid for sure.
3512 // If neither, it's not.
3514 if($is_a_remote_delete && $community) {
3515 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3516 $is_a_remote_delete = false;
3517 logger('local_delivery: not a community delete');
3521 if($is_a_remote_delete) {
3522 logger('local_delivery: received remote delete');
3526 $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
3527 WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
3529 intval($importer['importer_uid']),
3530 intval($importer['id'])
3536 if($item['deleted'])
3539 logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
3541 if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3542 $xo = parse_xml_string($item['object'],false);
3543 $xt = parse_xml_string($item['target'],false);
3545 if($xt->type === ACTIVITY_OBJ_NOTE) {
3546 $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
3548 intval($importer['importer_uid'])
3552 // For tags, the owner cannot remove the tag on the author's copy of the post.
3554 $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
3555 $author_remove = (($item['origin'] && $item['self']) ? true : false);
3556 $author_copy = (($item['origin']) ? true : false);
3558 if($owner_remove && $author_copy)
3560 if($author_remove || $owner_remove) {
3561 $tags = explode(',',$i[0]['tag']);
3564 foreach($tags as $tag)
3565 if(trim($tag) !== trim($xo->body))
3566 $newtags[] = trim($tag);
3568 q("update item set tag = '%s' where id = %d",
3569 dbesc(implode(',',$newtags)),
3572 create_tags_from_item($i[0]['id']);
3578 if($item['uri'] == $item['parent-uri']) {
3579 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3580 `body` = '', `title` = ''
3581 WHERE `parent-uri` = '%s' AND `uid` = %d",
3583 dbesc(datetime_convert()),
3584 dbesc($item['uri']),
3585 intval($importer['importer_uid'])
3587 create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
3588 create_files_from_itemuri($item['uri'], $importer['importer_uid']);
3589 update_thread_uri($item['uri'], $importer['importer_uid']);
3592 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
3593 `body` = '', `title` = ''
3594 WHERE `uri` = '%s' AND `uid` = %d",
3596 dbesc(datetime_convert()),
3598 intval($importer['importer_uid'])
3600 create_tags_from_itemuri($uri, $importer['importer_uid']);
3601 create_files_from_itemuri($uri, $importer['importer_uid']);
3602 update_thread_uri($uri, $importer['importer_uid']);
3603 if($item['last-child']) {
3604 // ensure that last-child is set in case the comment that had it just got wiped.
3605 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
3606 dbesc(datetime_convert()),
3607 dbesc($item['parent-uri']),
3608 intval($item['uid'])
3610 // who is the last child now?
3611 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
3612 ORDER BY `created` DESC LIMIT 1",
3613 dbesc($item['parent-uri']),
3614 intval($importer['importer_uid'])
3617 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
3622 // if this is a relayed delete, propagate it to other recipients
3624 if($is_a_remote_delete)
3625 proc_run('php',"include/notifier.php","drop",$item['id']);
3633 foreach($feed->get_items() as $item) {
3636 $item_id = $item->get_id();
3637 $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
3638 if(isset($rawthread[0]['attribs']['']['ref'])) {
3640 $parent_uri = $rawthread[0]['attribs']['']['ref'];
3646 if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
3649 logger('local_delivery: possible community reply');
3652 $sql_extra = " and contact.self = 1 and item.wall = 1 ";
3654 // was the top-level post for this reply written by somebody on this site?
3655 // Specifically, the recipient?
3657 $is_a_remote_comment = false;
3658 $top_uri = $parent_uri;
3660 $r = q("select `item`.`parent-uri` from `item`
3661 WHERE `item`.`uri` = '%s'
3665 if($r && count($r)) {
3666 $top_uri = $r[0]['parent-uri'];
3668 // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
3669 $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
3670 `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
3671 INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
3672 WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
3673 AND `item`.`uid` = %d
3679 intval($importer['importer_uid'])
3682 $is_a_remote_comment = true;
3685 // Does this have the characteristics of a community or private group comment?
3686 // If it's a reply to a wall post on a community/prvgroup page it's a
3687 // valid community comment. Also forum_mode makes it valid for sure.
3688 // If neither, it's not.
3690 if($is_a_remote_comment && $community) {
3691 if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
3692 $is_a_remote_comment = false;
3693 logger('local_delivery: not a community reply');
3697 if($is_a_remote_comment) {
3698 logger('local_delivery: received remote comment');
3700 // remote reply to our post. Import and then notify everybody else.
3702 $datarray = get_atom_elements($feed, $item);
3704 $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3706 intval($importer['importer_uid'])
3709 // Update content if 'updated' changes
3713 if (edited_timestamp_is_newer($r[0], $datarray)) {
3715 // do not accept (ignore) an earlier edit than one we currently have.
3716 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3719 logger('received updated comment' , LOGGER_DEBUG);
3720 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3721 dbesc($datarray['title']),
3722 dbesc($datarray['body']),
3723 dbesc($datarray['tag']),
3724 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3725 dbesc(datetime_convert()),
3727 intval($importer['importer_uid'])
3729 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3731 proc_run('php',"include/notifier.php","comment-import",$iid);
3740 $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
3741 intval($importer['importer_uid'])
3745 $datarray['type'] = 'remote-comment';
3746 $datarray['wall'] = 1;
3747 $datarray['parent-uri'] = $parent_uri;
3748 $datarray['uid'] = $importer['importer_uid'];
3749 $datarray['owner-name'] = $own[0]['name'];
3750 $datarray['owner-link'] = $own[0]['url'];
3751 $datarray['owner-avatar'] = $own[0]['thumb'];
3752 $datarray['contact-id'] = $importer['id'];
3754 if(($datarray['verb'] === ACTIVITY_LIKE) || ($datarray['verb'] === ACTIVITY_DISLIKE)) {
3756 $datarray['type'] = 'activity';
3757 $datarray['gravity'] = GRAVITY_LIKE;
3758 $datarray['last-child'] = 0;
3759 // only one like or dislike per person
3760 // splitted into two queries for performance issues
3761 $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",
3762 intval($datarray['uid']),
3763 intval($datarray['contact-id']),
3764 dbesc($datarray['verb']),
3765 dbesc($datarray['parent-uri'])
3771 $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",
3772 intval($datarray['uid']),
3773 intval($datarray['contact-id']),
3774 dbesc($datarray['verb']),
3775 dbesc($datarray['parent-uri'])
3782 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3784 $xo = parse_xml_string($datarray['object'],false);
3785 $xt = parse_xml_string($datarray['target'],false);
3787 if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
3789 // fetch the parent item
3791 $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
3793 intval($importer['importer_uid'])
3798 // extract tag, if not duplicate, and this user allows tags, add to parent item
3800 if($xo->id && $xo->content) {
3801 $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
3802 if(! (stristr($tagp[0]['tag'],$newtag))) {
3803 $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
3804 intval($importer['importer_uid'])
3806 if(count($i) && ! intval($i[0]['blocktags'])) {
3807 q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
3808 dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
3809 intval($tagp[0]['id']),
3810 dbesc(datetime_convert()),
3811 dbesc(datetime_convert())
3813 create_tags_from_item($tagp[0]['id']);
3821 $posted_id = item_store($datarray);
3826 $datarray["id"] = $posted_id;
3828 $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
3830 intval($importer['importer_uid'])
3833 $parent = $r[0]['parent'];
3834 $parent_uri = $r[0]['parent-uri'];
3838 $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
3839 dbesc(datetime_convert()),
3840 intval($importer['importer_uid']),
3841 intval($r[0]['parent'])
3844 $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
3845 dbesc(datetime_convert()),
3846 intval($importer['importer_uid']),
3851 if($posted_id && $parent) {
3853 proc_run('php',"include/notifier.php","comment-import","$posted_id");
3855 if((! $is_like) && (! $importer['self'])) {
3857 require_once('include/enotify.php');
3860 'type' => NOTIFY_COMMENT,
3861 'notify_flags' => $importer['notify-flags'],
3862 'language' => $importer['language'],
3863 'to_name' => $importer['username'],
3864 'to_email' => $importer['email'],
3865 'uid' => $importer['importer_uid'],
3866 'item' => $datarray,
3867 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
3868 'source_name' => stripslashes($datarray['author-name']),
3869 'source_link' => $datarray['author-link'],
3870 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
3871 ? $importer['thumb'] : $datarray['author-avatar']),
3872 'verb' => ACTIVITY_POST,
3874 'parent' => $parent,
3875 'parent_uri' => $parent_uri,
3887 // regular comment that is part of this total conversation. Have we seen it? If not, import it.
3889 $item_id = $item->get_id();
3890 $datarray = get_atom_elements($feed,$item);
3892 if($importer['rel'] == CONTACT_IS_FOLLOWER)
3895 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
3897 intval($importer['importer_uid'])
3900 // Update content if 'updated' changes
3903 if (edited_timestamp_is_newer($r[0], $datarray)) {
3905 // do not accept (ignore) an earlier edit than one we currently have.
3906 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
3909 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3910 dbesc($datarray['title']),
3911 dbesc($datarray['body']),
3912 dbesc($datarray['tag']),
3913 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
3914 dbesc(datetime_convert()),
3916 intval($importer['importer_uid'])
3918 create_tags_from_itemuri($item_id, $importer['importer_uid']);
3921 // update last-child if it changes
3923 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
3924 if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
3925 $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
3926 dbesc(datetime_convert()),
3928 intval($importer['importer_uid'])
3930 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
3931 intval($allow[0]['data']),
3932 dbesc(datetime_convert()),
3934 intval($importer['importer_uid'])
3940 $datarray['parent-uri'] = $parent_uri;
3941 $datarray['uid'] = $importer['importer_uid'];
3942 $datarray['contact-id'] = $importer['id'];
3943 if(($datarray['verb'] == ACTIVITY_LIKE) || ($datarray['verb'] == ACTIVITY_DISLIKE)) {
3944 $datarray['type'] = 'activity';
3945 $datarray['gravity'] = GRAVITY_LIKE;
3946 // only one like or dislike per person
3947 // splitted into two queries for performance issues
3948 $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",
3949 intval($datarray['uid']),
3950 intval($datarray['contact-id']),
3951 dbesc($datarray['verb']),
3957 $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",
3958 intval($datarray['uid']),
3959 intval($datarray['contact-id']),
3960 dbesc($datarray['verb']),
3968 if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
3970 $xo = parse_xml_string($datarray['object'],false);
3971 $xt = parse_xml_string($datarray['target'],false);
3973 if($xt->type == ACTIVITY_OBJ_NOTE) {
3974 $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
3976 intval($importer['importer_uid'])
3981 // extract tag, if not duplicate, add to parent item
3983 if(! (stristr($r[0]['tag'],trim($xo->content)))) {
3984 q("UPDATE item SET tag = '%s' WHERE id = %d",
3985 dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
3988 create_tags_from_item($r[0]['id']);
3994 $posted_id = item_store($datarray);
3996 // find out if our user is involved in this conversation and wants to be notified.
3998 if(!x($datarray['type']) || $datarray['type'] != 'activity') {
4000 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
4002 intval($importer['importer_uid'])
4005 if(count($myconv)) {
4006 $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
4008 // first make sure this isn't our own post coming back to us from a wall-to-wall event
4009 if(! link_compare($datarray['author-link'],$importer_url)) {
4012 foreach($myconv as $conv) {
4014 // now if we find a match, it means we're in this conversation
4016 if(! link_compare($conv['author-link'],$importer_url))
4019 require_once('include/enotify.php');
4021 $conv_parent = $conv['parent'];
4024 'type' => NOTIFY_COMMENT,
4025 'notify_flags' => $importer['notify-flags'],
4026 'language' => $importer['language'],
4027 'to_name' => $importer['username'],
4028 'to_email' => $importer['email'],
4029 'uid' => $importer['importer_uid'],
4030 'item' => $datarray,
4031 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4032 'source_name' => stripslashes($datarray['author-name']),
4033 'source_link' => $datarray['author-link'],
4034 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4035 ? $importer['thumb'] : $datarray['author-avatar']),
4036 'verb' => ACTIVITY_POST,
4038 'parent' => $conv_parent,
4039 'parent_uri' => $parent_uri
4043 // only send one notification
4055 // Head post of a conversation. Have we seen it? If not, import it.
4058 $item_id = $item->get_id();
4059 $datarray = get_atom_elements($feed,$item);
4061 if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
4062 $ev = bbtoevent($datarray['body']);
4063 if(x($ev,'desc') && x($ev,'start')) {
4064 $ev['cid'] = $importer['id'];
4065 $ev['uid'] = $importer['uid'];
4066 $ev['uri'] = $item_id;
4067 $ev['edited'] = $datarray['edited'];
4068 $ev['private'] = $datarray['private'];
4070 $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4072 intval($importer['uid'])
4075 $ev['id'] = $r[0]['id'];
4076 $xyz = event_store($ev);
4081 $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
4083 intval($importer['importer_uid'])
4086 // Update content if 'updated' changes
4089 if (edited_timestamp_is_newer($r[0], $datarray)) {
4091 // do not accept (ignore) an earlier edit than one we currently have.
4092 if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
4095 $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4096 dbesc($datarray['title']),
4097 dbesc($datarray['body']),
4098 dbesc($datarray['tag']),
4099 dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
4100 dbesc(datetime_convert()),
4102 intval($importer['importer_uid'])
4104 create_tags_from_itemuri($item_id, $importer['importer_uid']);
4105 update_thread_uri($item_id, $importer['importer_uid']);
4108 // update last-child if it changes
4110 $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
4111 if($allow && $allow[0]['data'] != $r[0]['last-child']) {
4112 $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
4113 intval($allow[0]['data']),
4114 dbesc(datetime_convert()),
4116 intval($importer['importer_uid'])
4122 $datarray['parent-uri'] = $item_id;
4123 $datarray['uid'] = $importer['importer_uid'];
4124 $datarray['contact-id'] = $importer['id'];
4127 if(! link_compare($datarray['owner-link'],$importer['url'])) {
4128 // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
4129 // but otherwise there's a possible data mixup on the sender's system.
4130 // the tgroup delivery code called from item_store will correct it if it's a forum,
4131 // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
4132 logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
4133 $datarray['owner-name'] = $importer['senderName'];
4134 $datarray['owner-link'] = $importer['url'];
4135 $datarray['owner-avatar'] = $importer['thumb'];
4138 if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
4141 // This is my contact on another system, but it's really me.
4142 // Turn this into a wall post.
4143 $notify = item_is_remote_self($importer, $datarray);
4145 $posted_id = item_store($datarray, false, $notify);
4147 if(stristr($datarray['verb'],ACTIVITY_POKE)) {
4148 $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
4151 $xo = parse_xml_string($datarray['object'],false);
4153 if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
4155 // somebody was poked/prodded. Was it me?
4157 $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
4159 foreach($links->link as $l) {
4160 $atts = $l->attributes();
4161 switch($atts['rel']) {
4163 $Blink = $atts['href'];
4169 if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
4171 // send a notification
4172 require_once('include/enotify.php');
4175 'type' => NOTIFY_POKE,
4176 'notify_flags' => $importer['notify-flags'],
4177 'language' => $importer['language'],
4178 'to_name' => $importer['username'],
4179 'to_email' => $importer['email'],
4180 'uid' => $importer['importer_uid'],
4181 'item' => $datarray,
4182 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
4183 'source_name' => stripslashes($datarray['author-name']),
4184 'source_link' => $datarray['author-link'],
4185 'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
4186 ? $importer['thumb'] : $datarray['author-avatar']),
4187 'verb' => $datarray['verb'],
4188 'otype' => 'person',
4189 'activity' => $verb,
4190 'parent' => $datarray['parent']
4206 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
4207 $url = notags(trim($datarray['author-link']));
4208 $name = notags(trim($datarray['author-name']));
4209 $photo = notags(trim($datarray['author-avatar']));
4211 if (is_object($item)) {
4212 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
4213 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
4214 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
4218 if(is_array($contact)) {
4219 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
4220 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
4221 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
4222 intval(CONTACT_IS_FRIEND),
4223 intval($contact['id']),
4224 intval($importer['uid'])
4227 // send email notification to owner?
4231 // create contact record
4233 $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
4234 `blocked`, `readonly`, `pending`, `writable` )
4235 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1 ) ",
4236 intval($importer['uid']),
4237 dbesc(datetime_convert()),
4239 dbesc(normalise_link($url)),
4243 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
4244 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
4246 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
4247 intval($importer['uid']),
4251 $contact_record = $r[0];
4253 // create notification
4254 $hash = random_string();
4256 if(is_array($contact_record)) {
4257 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
4258 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
4259 intval($importer['uid']),
4260 intval($contact_record['id']),
4262 dbesc(datetime_convert())
4266 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
4267 intval($importer['uid'])
4272 if(intval($r[0]['def_gid'])) {
4273 require_once('include/group.php');
4274 group_add_member($r[0]['uid'],'',$contact_record['id'],$r[0]['def_gid']);
4277 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
4278 in_array($r[0]['page-flags'], array(PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE))) {
4281 'type' => NOTIFY_INTRO,
4282 'notify_flags' => $r[0]['notify-flags'],
4283 'language' => $r[0]['language'],
4284 'to_name' => $r[0]['username'],
4285 'to_email' => $r[0]['email'],
4286 'uid' => $r[0]['uid'],
4287 'link' => $a->get_baseurl() . '/notifications/intro',
4288 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
4289 'source_link' => $contact_record['url'],
4290 'source_photo' => $contact_record['photo'],
4291 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
4300 function lose_follower($importer,$contact,$datarray,$item) {
4302 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
4303 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4304 intval(CONTACT_IS_SHARING),
4305 intval($contact['id'])
4309 contact_remove($contact['id']);
4313 function lose_sharer($importer,$contact,$datarray,$item) {
4315 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
4316 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
4317 intval(CONTACT_IS_FOLLOWER),
4318 intval($contact['id'])
4322 contact_remove($contact['id']);
4327 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
4331 if(is_array($importer)) {
4332 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
4333 intval($importer['uid'])
4337 // Diaspora has different message-ids in feeds than they do
4338 // through the direct Diaspora protocol. If we try and use
4339 // the feed, we'll get duplicates. So don't.
4341 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
4344 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
4346 // Use a single verify token, even if multiple hubs
4348 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
4350 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
4352 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
4354 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
4355 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
4356 dbesc($verify_token),
4357 intval($contact['id'])
4361 post_url($url,$params);
4363 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
4370 function atom_author($tag,$name,$uri,$h,$w,$photo) {
4374 $name = xmlify($name);
4375 $uri = xmlify($uri);
4378 $photo = xmlify($photo);
4382 $o .= "\t<name>$name</name>\r\n";
4383 $o .= "\t<uri>$uri</uri>\r\n";
4384 $o .= "\t".'<link rel="photo" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4385 $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
4387 if ($tag == "author") {
4388 $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
4389 `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
4390 `profile`.`homepage`,`contact`.`nick` FROM `profile`
4391 INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
4392 INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
4393 WHERE `profile`.`is-default` AND `contact`.`self` AND
4394 NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
4395 dbesc(normalise_link($uri)));
4398 if($r[0]['locality'])
4399 $location .= $r[0]['locality'];
4400 if($r[0]['region']) {
4403 $location .= $r[0]['region'];
4405 if($r[0]['country-name']) {
4408 $location .= $r[0]['country-name'];
4411 $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
4412 $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
4413 $o .= "\t<poco:note>".xmlify($r[0]["about"])."</poco:note>\r\n";
4414 $o .= "\t<poco:address>\r\n";
4415 $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
4416 $o .= "\t</poco:address>\r\n";
4417 $o .= "\t<poco:urls>\r\n";
4418 $o .= "\t<poco:type>homepage</poco:type>\r\n";
4419 $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
4420 $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
4421 $o .= "\t</poco:urls>\r\n";
4425 call_hooks('atom_author', $o);
4427 $o .= "</$tag>\r\n";
4431 function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
4435 if(! $item['parent'])
4438 if($item['deleted'])
4439 return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
4442 if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
4443 $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
4445 $body = $item['body'];
4448 $o = "\r\n\r\n<entry>\r\n";
4450 if(is_array($author))
4451 $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb']);
4453 $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']));
4454 if(strlen($item['owner-name']))
4455 $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar']);
4457 if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
4458 $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
4459 $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
4460 $o .= '<thr:in-reply-to ref="'.xmlify($parent_item).'" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$parent[0]['guid']).'" />'."\r\n";
4465 if ($item['title'] != "")
4466 $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
4468 $htmlbody = bbcode($htmlbody, false, false, 7);
4470 $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
4471 $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
4472 $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
4473 $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
4474 $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
4475 $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
4476 $o .= '<link rel="alternate" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$item['guid']).'" />'."\r\n";
4478 $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
4481 $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
4483 if($item['location']) {
4484 $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
4485 $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
4489 $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
4491 if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
4492 $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
4495 $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
4496 if($item['bookmark'])
4497 $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
4500 $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
4503 $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
4505 if($item['signed_text']) {
4506 $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
4507 $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
4510 $verb = construct_verb($item);
4511 $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
4512 $actobj = construct_activity_object($item);
4515 $actarg = construct_activity_target($item);
4519 $tags = item_getfeedtags($item);
4521 foreach($tags as $t)
4522 if (($type != 'html') OR ($t[0] != "@"))
4523 $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
4527 // To support these elements, the API needs to be enhanced
4528 //$o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
4529 //$o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4530 //$o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
4532 $o .= item_get_attachment($item);
4534 $o .= item_getfeedattach($item);
4536 $mentioned = get_mentions($item);
4540 call_hooks('atom_entry', $o);
4542 $o .= '</entry>' . "\r\n";
4547 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
4549 if(get_config('system','disable_embedded'))
4554 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
4555 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
4560 $img_start = strpos($orig_body, '[img');
4561 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4562 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4563 while( ($img_st_close !== false) && ($img_len !== false) ) {
4565 $img_st_close++; // make it point to AFTER the closing bracket
4566 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
4568 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
4571 if(stristr($image , $site . '/photo/')) {
4572 // Only embed locally hosted photos
4574 $i = basename($image);
4575 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
4576 $x = strpos($i,'-');
4579 $res = substr($i,$x+1);
4580 $i = substr($i,0,$x);
4581 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
4588 // Check to see if we should replace this photo link with an embedded image
4589 // 1. No need to do so if the photo is public
4590 // 2. If there's a contact-id provided, see if they're in the access list
4591 // for the photo. If so, embed it.
4592 // 3. Otherwise, if we have an item, see if the item permissions match the photo
4593 // permissions, regardless of order but first check to see if they're an exact
4594 // match to save some processing overhead.
4596 if(has_permissions($r[0])) {
4598 $recips = enumerate_permissions($r[0]);
4599 if(in_array($cid, $recips)) {
4604 if(compare_permissions($item,$r[0]))
4609 $data = $r[0]['data'];
4610 $type = $r[0]['type'];
4612 // If a custom width and height were specified, apply before embedding
4613 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
4614 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
4616 $width = intval($match[1]);
4617 $height = intval($match[2]);
4619 $ph = new Photo($data, $type);
4620 if($ph->is_valid()) {
4621 $ph->scaleImage(max($width, $height));
4622 $data = $ph->imageString();
4623 $type = $ph->getType();
4627 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
4628 $image = 'data:' . $type . ';base64,' . base64_encode($data);
4629 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
4635 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
4636 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
4637 if($orig_body === false)
4640 $img_start = strpos($orig_body, '[img');
4641 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
4642 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
4645 $new_body = $new_body . $orig_body;
4651 function has_permissions($obj) {
4652 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
4657 function compare_permissions($obj1,$obj2) {
4658 // first part is easy. Check that these are exactly the same.
4659 if(($obj1['allow_cid'] == $obj2['allow_cid'])
4660 && ($obj1['allow_gid'] == $obj2['allow_gid'])
4661 && ($obj1['deny_cid'] == $obj2['deny_cid'])
4662 && ($obj1['deny_gid'] == $obj2['deny_gid']))
4665 // This is harder. Parse all the permissions and compare the resulting set.
4667 $recipients1 = enumerate_permissions($obj1);
4668 $recipients2 = enumerate_permissions($obj2);
4671 if($recipients1 == $recipients2)
4676 // returns an array of contact-ids that are allowed to see this object
4678 function enumerate_permissions($obj) {
4679 require_once('include/group.php');
4680 $allow_people = expand_acl($obj['allow_cid']);
4681 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
4682 $deny_people = expand_acl($obj['deny_cid']);
4683 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
4684 $recipients = array_unique(array_merge($allow_people,$allow_groups));
4685 $deny = array_unique(array_merge($deny_people,$deny_groups));
4686 $recipients = array_diff($recipients,$deny);
4690 function item_getfeedtags($item) {
4693 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4695 for($x = 0; $x < $cnt; $x ++) {
4697 $ret[] = array('#',$matches[1][$x], $matches[2][$x]);
4701 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
4703 for($x = 0; $x < $cnt; $x ++) {
4705 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
4711 function item_get_attachment($item) {
4713 $siteinfo = get_attached_data($item["body"]);
4715 switch($siteinfo["type"]) {
4717 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4720 $imgdata = get_photo_info($siteinfo["image"]);
4721 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
4724 $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
4733 function item_getfeedattach($item) {
4735 $arr = explode('[/attach],',$item['attach']);
4737 foreach($arr as $r) {
4739 $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
4741 $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
4742 if(intval($matches[2]))
4743 $ret .= 'length="' . intval($matches[2]) . '" ';
4744 if($matches[4] !== ' ')
4745 $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
4746 $ret .= ' />' . "\r\n";
4755 function item_expire($uid, $days, $network = "", $force = false) {
4757 if((! $uid) || ($days < 1))
4760 // $expire_network_only = save your own wall posts
4761 // and just expire conversations started by others
4763 $expire_network_only = get_pconfig($uid,'expire','network_only');
4764 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
4766 if ($network != "") {
4767 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
4768 // There is an index "uid_network_received" but not "uid_network_created"
4769 // This avoids the creation of another index just for one purpose.
4770 // And it doesn't really matter wether to look at "received" or "created"
4771 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4773 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
4775 $r = q("SELECT * FROM `item`
4776 WHERE `uid` = %d $range
4787 $expire_items = get_pconfig($uid, 'expire','items');
4788 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
4790 // Forcing expiring of items - but not notes and marked items
4792 $expire_items = true;
4794 $expire_notes = get_pconfig($uid, 'expire','notes');
4795 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
4797 $expire_starred = get_pconfig($uid, 'expire','starred');
4798 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
4800 $expire_photos = get_pconfig($uid, 'expire','photos');
4801 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
4803 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
4805 foreach($r as $item) {
4807 // don't expire filed items
4809 if(strpos($item['file'],'[') !== false)
4812 // Only expire posts, not photos and photo comments
4814 if($expire_photos==0 && strlen($item['resource-id']))
4816 if($expire_starred==0 && intval($item['starred']))
4818 if($expire_notes==0 && $item['type']=='note')
4820 if($expire_items==0 && $item['type']!='note')
4823 drop_item($item['id'],false);
4826 proc_run('php',"include/notifier.php","expire","$uid");
4831 function drop_items($items) {
4834 if(! local_user() && ! remote_user())
4838 foreach($items as $item) {
4839 $owner = drop_item($item,false);
4840 if($owner && ! $uid)
4845 // multiple threads may have been deleted, send an expire notification
4848 proc_run('php',"include/notifier.php","expire","$uid");
4852 function drop_item($id,$interactive = true) {
4856 // locate item to be deleted
4858 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
4865 notice( t('Item not found.') . EOL);
4866 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4871 $owner = $item['uid'];
4875 // check if logged in user is either the author or owner of this item
4877 if(is_array($_SESSION['remote'])) {
4878 foreach($_SESSION['remote'] as $visitor) {
4879 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
4880 $cid = $visitor['cid'];
4887 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
4889 // Check if we should do HTML-based delete confirmation
4890 if($_REQUEST['confirm']) {
4891 // <form> can't take arguments in its "action" parameter
4892 // so add any arguments as hidden inputs
4893 $query = explode_querystring($a->query_string);
4895 foreach($query['args'] as $arg) {
4896 if(strpos($arg, 'confirm=') === false) {
4897 $arg_parts = explode('=', $arg);
4898 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
4902 return replace_macros(get_markup_template('confirm.tpl'), array(
4904 '$message' => t('Do you really want to delete this item?'),
4905 '$extra_inputs' => $inputs,
4906 '$confirm' => t('Yes'),
4907 '$confirm_url' => $query['base'],
4908 '$confirm_name' => 'confirmed',
4909 '$cancel' => t('Cancel'),
4912 // Now check how the user responded to the confirmation query
4913 if($_REQUEST['canceled']) {
4914 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
4917 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
4920 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
4921 dbesc(datetime_convert()),
4922 dbesc(datetime_convert()),
4925 create_tags_from_item($item['id']);
4926 create_files_from_item($item['id']);
4927 delete_thread($item['id'], $item['parent-uri']);
4929 // clean up categories and tags so they don't end up as orphans
4932 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
4934 foreach($matches as $mtch) {
4935 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
4941 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
4943 foreach($matches as $mtch) {
4944 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
4948 // If item is a link to a photo resource, nuke all the associated photos
4949 // (visitors will not have photo resources)
4950 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
4951 // generate a resource-id and therefore aren't intimately linked to the item.
4953 if(strlen($item['resource-id'])) {
4954 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
4955 dbesc($item['resource-id']),
4956 intval($item['uid'])
4958 // ignore the result
4961 // If item is a link to an event, nuke the event record.
4963 if(intval($item['event-id'])) {
4964 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
4965 intval($item['event-id']),
4966 intval($item['uid'])
4968 // ignore the result
4971 // If item has attachments, drop them
4973 foreach(explode(",",$item['attach']) as $attach){
4974 preg_match("|attach/(\d+)|", $attach, $matches);
4975 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
4976 intval($matches[1]),
4979 // ignore the result
4983 // clean up item_id and sign meta-data tables
4986 // Old code - caused very long queries and warning entries in the mysql logfiles:
4988 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
4989 intval($item['id']),
4990 intval($item['uid'])
4993 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
4994 intval($item['id']),
4995 intval($item['uid'])
4999 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
5001 // Creating list of parents
5002 $r = q("select id from item where parent = %d and uid = %d",
5003 intval($item['id']),
5004 intval($item['uid'])
5009 foreach ($r AS $row) {
5010 if ($parentid != "")
5013 $parentid .= $row["id"];
5017 if ($parentid != "") {
5018 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
5020 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
5023 // If it's the parent of a comment thread, kill all the kids
5025 if($item['uri'] == $item['parent-uri']) {
5026 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
5027 WHERE `parent-uri` = '%s' AND `uid` = %d ",
5028 dbesc(datetime_convert()),
5029 dbesc(datetime_convert()),
5030 dbesc($item['parent-uri']),
5031 intval($item['uid'])
5033 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
5034 create_files_from_itemuri($item['parent-uri'], $item['uid']);
5035 delete_thread_uri($item['parent-uri'], $item['uid']);
5036 // ignore the result
5039 // ensure that last-child is set in case the comment that had it just got wiped.
5040 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
5041 dbesc(datetime_convert()),
5042 dbesc($item['parent-uri']),
5043 intval($item['uid'])
5045 // who is the last child now?
5046 $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",
5047 dbesc($item['parent-uri']),
5048 intval($item['uid'])
5051 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
5056 // Add a relayable_retraction signature for Diaspora.
5057 store_diaspora_retract_sig($item, $a->user, $a->get_baseurl());
5060 $drop_id = intval($item['id']);
5062 // send the notification upstream/downstream as the case may be
5064 proc_run('php',"include/notifier.php","drop","$drop_id");
5068 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5074 notice( t('Permission denied.') . EOL);
5075 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
5082 function first_post_date($uid,$wall = false) {
5083 $r = q("select id, created from item
5084 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
5086 order by created asc limit 1",
5088 intval($wall ? 1 : 0)
5091 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
5092 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
5097 /* modified posted_dates() {below} to arrange the list in years */
5098 function list_post_dates($uid, $wall) {
5099 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5101 $dthen = first_post_date($uid, $wall);
5105 // Set the start and end date to the beginning of the month
5106 $dnow = substr($dnow,0,8).'01';
5107 $dthen = substr($dthen,0,8).'01';
5111 // Starting with the current month, get the first and last days of every
5112 // month down to and including the month of the first post
5113 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5114 $dyear = intval(substr($dnow,0,4));
5115 $dstart = substr($dnow,0,8) . '01';
5116 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5117 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5118 $end_month = datetime_convert('','',$dend,'Y-m-d');
5119 $str = day_translate(datetime_convert('','',$dnow,'F'));
5121 $ret[$dyear] = array();
5122 $ret[$dyear][] = array($str,$end_month,$start_month);
5123 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5128 function posted_dates($uid,$wall) {
5129 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
5131 $dthen = first_post_date($uid,$wall);
5135 // Set the start and end date to the beginning of the month
5136 $dnow = substr($dnow,0,8).'01';
5137 $dthen = substr($dthen,0,8).'01';
5140 // Starting with the current month, get the first and last days of every
5141 // month down to and including the month of the first post
5142 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
5143 $dstart = substr($dnow,0,8) . '01';
5144 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
5145 $start_month = datetime_convert('','',$dstart,'Y-m-d');
5146 $end_month = datetime_convert('','',$dend,'Y-m-d');
5147 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
5148 $ret[] = array($str,$end_month,$start_month);
5149 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
5155 function posted_date_widget($url,$uid,$wall) {
5158 if(! feature_enabled($uid,'archives'))
5161 // For former Facebook folks that left because of "timeline"
5163 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
5166 $visible_years = get_pconfig($uid,'system','archive_visible_years');
5167 if(! $visible_years)
5170 $ret = list_post_dates($uid,$wall);
5175 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
5176 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
5178 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
5179 '$title' => t('Archives'),
5180 '$size' => $visible_years,
5181 '$cutoff_year' => $cutoff_year,
5182 '$cutoff' => $cutoff,
5185 '$showmore' => t('show more')
5191 function store_diaspora_retract_sig($item, $user, $baseurl) {
5192 // Note that we can't add a target_author_signature
5193 // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting
5194 // the comment, that means we're the home of the post, and Diaspora will only
5195 // check the parent_author_signature of retractions that it doesn't have to relay further
5197 // I don't think this function gets called for an "unlike," but I'll check anyway
5199 $enabled = intval(get_config('system','diaspora_enabled'));
5201 logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG);
5205 logger('drop_item: storing diaspora retraction signature');
5207 $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
5209 if(local_user() == $item['uid']) {
5211 $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3);
5212 $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256'));
5215 $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1",
5216 $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id']
5219 // The below handle only works for NETWORK_DFRN. I think that's ok, because this function
5220 // only handles DFRN deletes
5221 $handle_baseurl_start = strpos($r['url'],'://') + 3;
5222 $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start;
5223 $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length);
5229 q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
5230 intval($item['id']),
5231 dbesc($signed_text),